Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

Noon Sight

БесплатноНе проверен

Aggregates site analytics and published content from Google Analytics, Search Console, Cloudflare, and Buffer into MCP tools for traffic, search, request, and c

GitHubEmbed

Описание

Aggregates site analytics and published content from Google Analytics, Search Console, Cloudflare, and Buffer into MCP tools for traffic, search, request, and content reports.

README

An MCP server on Cloudflare Workers that aggregates what a site publishes and how it is read. Four sources answer four questions, and Cloudflare Zero Trust guards the door.

Deliberately small: it carries the tools that are actually used, not a full API surface.

What each source can see

    published  ────▶  shown  ────▶  requested  ────▶  rendered
        │               │              │                 │
     Buffer      Search Console    Cloudflare     Google Analytics
        │               │              │                 │
  content_report   search_report  request_report    traffic_report

A source earns its place by answering what the others structurally cannot.

Source Sees Blind to
Buffer what was published, before anyone read it anything posted outside Buffer
Search Console everyone shown the site, whether they came or not everyone who arrived from anywhere but Google
Cloudflare every request at the edge, JavaScript or not what happened inside the page, and last week
Google Analytics whoever arrived and rendered the page anyone who ran no JavaScript

Every figure is what one measurement system saw, not what happened. Two sources disagreeing does not make either wrong.

Tools

Tool Source Answers with breakdown (up to 2) Other input Default window
traffic_report Google Analytics sessions, users, engagement, duration, key events channel source page country language device visitor_type where days until limit 28 days, paired
search_report Google Search Console clicks, impressions, CTR, average position query page country device where days until limit 28 days, paired
request_report Cloudflare zone analytics requests and bytes at the edge bot page country device status host where days until limit 7 days, paired if kept
content_report Buffer, and the pages themselves the posts themselves, with what each page declares it is about kind days until limit 28 days, unpaired

The first three count, and every row is a pair: the requested period against the equally long one before it, because whether a number rose is what a count is for. Where a source no longer holds the earlier period, that side reads null — no record, rather than no traffic.

content_report does not count. A single post is already the whole of what it says, so it hands back the posts themselves and leaves the counting to whoever asked.

How to read any of it arrives with the handshake, as MCP instructions.

How a request flows

        ┌──────────────┐
        │  MCP client  │   Claude, or anything that speaks MCP
        └──────┬───────┘
               │  401 challenge → OAuth → Access policy evaluated
        ┌──────▼──────────────────────┐
        │  Cloudflare Access          │   Managed OAuth, one application
        └──────┬──────────────────────┘
               │  POST /mcp  +  Cf-Access-Jwt-Assertion
        ┌──────▼──────────────────────────────────────────────┐
        │  Worker — noon-sight                                │
        │  Hono /mcp → jose verifies iss + aud → MCP server   │
        └───┬──────────┬───────────┬────────────┬─────────────┘
            │          │           │            │
      Analytics    Search      Cloudflare   Buffer GraphQL
      Data API     Console     GraphQL      + the site's own pages
            └── Service ───┘   API token    API key
                Account JWT

An MCP client is not a browser and cannot follow Access's 302. Managed OAuth answers it with a 401 challenge instead, resolves the token itself, and still forwards Cf-Access-Jwt-Assertion — so the Worker implements no OAuth of its own.

Two KV namespaces, both provisioned by wrangler and named after the Worker:

Binding Holds Gone after
TOKEN_CACHE the Google access token its lifetime less five minutes
CONTENT_CACHE what a page declared it is about 30 days; one that declared nothing, a day

Setup

Access cannot be named before it exists, so the order matters at exactly one point:

 pnpm deploy  ────▶  Access application  ────▶  TEAM_DOMAIN + POLICY_AUD  ────▶  sources
      ▲                on that hostname              Access now enforced        one at a time,
      │                                                                         in any order
 403 "Access is not configured" until the secrets land — the intended state, not a fault

Each source stands alone. A tool whose source is unconfigured stays registered and says what is missing, so one configured source is already useful — and a server half set up is told apart from a site with nothing to report.

1. The Worker

pnpm install
pnpm deploy

The route in wrangler.jsonc is a custom domain; change it to your own hostname. workers.dev and preview URLs are deliberately not minted: Access is enabled per hostname, and an unguarded entrance is the failure mode.

2. Cloudflare Access

Step Where What
Add the application Zero Trust → Access → Applications Self-hosted, on the Worker's hostname
Add a policy the same application Whoever may read the site's numbers
Turn on Managed OAuth the application → Advanced settings Turns Access into an OAuth server for this application, so a non-browser client can log in
Allow the client's callback the same tab → Allowed redirect URIs https://claude.ai/api/mcp/auth_callback for the hosted client, a loopback URI for a local one
Read the AUD the application's overview The Application Audience tag
Secret Value
TEAM_DOMAIN https://<team>.cloudflareaccess.com, scheme included — it is both the JWKS host and the expected iss
POLICY_AUD The Application Audience tag
pnpm wrangler secret put TEAM_DOMAIN
pnpm wrangler secret put POLICY_AUD

A further hostname joins this same application as an additional public hostname. A second application would mint a second AUD, and one POLICY_AUD cannot match both.

Access registers each client dynamically and keeps a refused registration, so allowlist the callback before the client first tries. Afterwards, it has to be removed and added again.

3. Google Analytics and Search Console

One Service Account serves both, and asks for nothing wider than the two read scopes.

Step Where What
Enable the APIs Google Cloud → APIs & Services Google Analytics Data API, Google Search Console API
Create the account and a JSON key Google Cloud → IAM & Admin → Service Accounts The downloaded JSON file is the secret, whole
Grant it the GA4 property GA4 → Admin → Property access management Its e-mail, as Viewer
Grant it the Search Console property Search Console → Settings → Users and permissions The same e-mail, Full or Restricted. Google Cloud grants nothing here — the property's own user list does
Secret Value
GOOGLE_SERVICE_ACCOUNT The whole Service Account JSON, verbatim — one secret rather than a field per value, because the private key is a PEM whose newlines survive JSON but not a shell
GA_PROPERTY_ID The numeric GA4 property id, from Admin → Property details
GSC_SITE_URL Whatever string sites.list returns, verbatim — sc-domain:example.com or a trailing-slash URL. A near miss answers 403

Workers carry no Node crypto, so the official Google SDK cannot run here. jose signs the assertion, Google exchanges it for a bearer token, and KV holds that token for its hour.

4. Cloudflare zone analytics

Step Where What
Create the token My Profile → API Tokens → Create Token → Custom Zone / Analytics / Read, scoped to the one zone. Logs / Read opens raw events nothing here reads
Read the zone id the zone's Overview page, under API
Secret Value
CLOUDFLARE_API_TOKEN The token as it stands — it is the credential rather than a claim to be exchanged for one, so nothing is signed or cached
CLOUDFLARE_ZONE_ID The zone the site lives in
CLOUDFLARE_SITE_HOST The hostname this server answers for, e.g. blog.example.com

A zone is a billing boundary carrying every hostname under it, so this source takes two identifiers where the others take one. CLOUDFLARE_SITE_HOST narrows a request report to one site, and tells a link to this site from a link anywhere else. Left unset, the report covers the whole zone.

Refusals arrive as 200 with an errors array, so a token without the permission looks like success to anything reading the status. The tool reads the array and repeats what Cloudflare said.

5. Buffer

Step Where What
Create the API key https://publish.buffer.com/settings/api
Find the organization id one query, below An account may hold several, and every Buffer query names one
curl -s https://api.buffer.com \
  -H "Authorization: Bearer $BUFFER_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"query":"{ account { organizations { id name } } }"}'
Secret Value
BUFFER_API_KEY The key as it stands, like Cloudflare's
BUFFER_ORGANIZATION_ID The id the query above answers with

Naming the organization here makes it the same kind of value as the property and the zone, and spares every report the round trip that would otherwise start it.

content_report then asks each linked page what it is about — Accept: text/markdown first, then schema.org in the HTML — which is why it also needs CLOUDFLARE_SITE_HOST. A page declaring nothing is reported as declaring nothing.

Every secret at a glance

Secret Unlocks Without it
TEAM_DOMAIN POLICY_AUD every request 403 Access is not configured
GOOGLE_SERVICE_ACCOUNT traffic_report search_report both refuse, saying Google credentials are not configured
GA_PROPERTY_ID traffic_report it refuses, saying no property is configured
GSC_SITE_URL search_report it refuses, saying no property is configured
CLOUDFLARE_API_TOKEN CLOUDFLARE_ZONE_ID request_report it refuses, naming the one that is missing
CLOUDFLARE_SITE_HOST request_report content_report the zone answers whole, and no post is recognised as this site's
BUFFER_API_KEY BUFFER_ORGANIZATION_ID content_report it refuses, naming the one that is missing

Secrets live in wrangler secret and .dev.vars, never in wrangler.jsonc. Cloudflare classes the first two as vars rather than secrets, but keeping them out of the repository publishes neither the team nor the application being guarded.

Local development

.dev.vars carries the same names, plus DEBUG=true, which skips Access verification — local development has no Access in front of it. Production reads no such flag and therefore denies; vitest pins it off, so no local file can disarm the suite.

Command Does
pnpm dev Runs locally on workerd, reading .dev.vars
pnpm test Runs the suite on workerd, outbound requests answered by MSW
pnpm typecheck Typechecks src and test as separate projects
pnpm format Prettier; format:check verifies without writing
pnpm deploy Deploys to Cloudflare
pnpm cf-typegen Regenerates binding types; run after changing wrangler.jsonc

Connecting a client

Add https://<your-hostname>/mcp as a remote MCP server. The first call is answered with a 401 naming Access's OAuth endpoints; the client registers itself and walks the flow, and Access evaluates its policy before anything reaches the Worker.

License

Apache-2.0

from github.com/elct9620/noon-sight

Установка Noon Sight

У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.

▸ github.com/elct9620/noon-sight

FAQ

Noon Sight MCP бесплатный?

Да, Noon Sight MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для Noon Sight?

Нет, Noon Sight работает без API-ключей и переменных окружения.

Noon Sight — hosted или self-hosted?

Доступен hosted-вариант: Unyly запускает сервер в облаке, локальная установка не обязательна.

Как установить Noon Sight в Claude Desktop, Claude Code или Cursor?

Открой Noon Sight на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

Compare Noon Sight with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории development