MPF
Redumper/Aaru/DiscImageCreator GUI를 C#로 구현
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Your own remote MCP server for Google Search Console and Google Analytics 4, over a single Google sign-in. It is also its own OAuth 2.1 authorization server, which is what claude.ai requires before it will accept a custom connector, and it delegates the actual sign-in to Google.
Zero dependencies — Node 20+ built-ins only. No node_modules, no database.
claude.ai ──OAuth──▶ this server ──OAuth──▶ Google
│ │
│ ├──────────────▶ Search Console API
└────────MCP───────────▶└──────────────▶ Analytics Data + Admin API
Both products run through one connector deliberately. Search Console knows how people found a page; GA4 knows what they did next. Keeping them on one connection is what lets a single question — "clicks held up, so why are signups down?" — be answered without jumping between two tools.
You need a Google Cloud project with the Search Console API enabled and an OAuth client. About five minutes, once.
Go to https://console.cloud.google.com/projectcreate and create a project
(name it anything — gsc-mcp is fine).
Enable the APIs this server uses:
Skip the last two if you set GA4_ENABLED=false. If a call later fails with
"API has not been used in project … before or is disabled", enable whatever
service that error names — Google has shuffled these entries around and the
older webmasters alias still exists in some projects. The error always
says which one it wants.
Configure the consent screen at APIs & Services → OAuth consent screen:
APIs & Services → Credentials → Create credentials → OAuth client ID:
https://YOUR-DOMAIN/oauth/google/callbackScopes are requested automatically: openid, email,
webmasters.readonly (or webmasters if you turn on write mode), and
analytics.readonly unless GA4 is disabled. One analytics.readonly scope
covers both GA4 reporting and property discovery, and GA4 is read-only here
regardless of what GSC_ENABLE_WRITE is set to.
cp .env.example .env
npm run secret # paste the output into SERVER_SECRET
Four values are required:
| Variable | What it is |
|---|---|
BASE_URL | The public https origin, no trailing slash. This is the OAuth issuer, so it must match what the outside world sees exactly. |
SERVER_SECRET | Signs and encrypts every token the server issues. 32+ bytes. |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET | From step 1. |
ALLOWED_EMAILS | Comma-separated Google accounts permitted to connect. |
ALLOWED_EMAILS is not optional in spirit. The server is on the public internet
and accepts dynamic client registration from anyone, by design — that is how
Claude connects without you pre-registering anything. The email allowlist is
what stops a stranger who finds the URL from attaching it to their Claude
account and reading your data. The server refuses to start without it.
Everything else has a working default; see .env.example.
The server listens on PORT (default 8080) and needs to be reachable over
HTTPS. Anything that runs a Node process works.
Fly.io — fly.toml is included:
fly launch --no-deploy
fly secrets set SERVER_SECRET=... GOOGLE_CLIENT_ID=... GOOGLE_CLIENT_SECRET=... \
[email protected] BASE_URL=https://your-app.fly.dev
fly volumes create gsc_data --size 1 # optional, see State below
fly deploy
Railway / Render — point at the repo, set the env vars, start command
node server.js. Set BASE_URL to the URL the platform gives you.
Docker anywhere:
docker build -t gsc-mcp .
docker run -p 8080:8080 --env-file .env gsc-mcp
A VPS behind Caddy — node server.js under systemd, with:
gsc.example.com {
reverse_proxy localhost:8080
}
Then set the redirect URI from step 1 to https://YOUR-DOMAIN/oauth/google/callback
if you had not already, and open https://YOUR-DOMAIN/ — it shows the connector
URL and the tools it is serving.
BASE_URLmust usehttps://. The server refuses to start otherwise, except on localhost. OAuth over plain HTTP would expose every token in transit.
claude.ai (web, desktop, mobile) — Settings → Connectors → Add custom connector. Enter:
https://YOUR-DOMAIN/mcp
Leave the OAuth client ID and secret blank; the server registers Claude automatically. You will be sent to Google to sign in, and then straight back.
Enable the connector per conversation from the + menu beside the message box.
Claude Code:
claude mcp add --transport http gsc https://YOUR-DOMAIN/mcp
It opens the same sign-in in your browser and returns to a loopback port. Claude Code gets an extra confirmation screen before the redirect, because a local port is something any program on the machine could be listening on.
Search Console — how people found the site in Google Search:
| Tool | What it answers |
|---|---|
list_properties | Which properties this account can see, and at what permission level |
search_analytics | Clicks, impressions, CTR and position for one period, broken down by query, page, country, device, search appearance, date or hour |
compare_periods | What changed between two periods, totals plus per-row deltas |
top_movers | The queries or pages that gained or lost the most — the traffic-drop investigation tool |
inspect_url | Index status, canonical, last crawl, mobile usability and rich results for one URL |
list_sitemaps / get_sitemap | Submitted sitemaps, with URL counts and errors |
submit_sitemap / delete_sitemap | Only when GSC_ENABLE_WRITE=true |
Analytics 4 — what they did once they arrived:
| Tool | What it answers |
|---|---|
ga4_list_properties | Every GA4 property the account can see, with its numeric ID |
ga4_run_report | Any combination of GA4 dimensions and metrics over a date range, with filters and sorting |
ga4_compare_periods | Two periods side by side, with per-row deltas and percentage change |
ga4_realtime_report | Active users in the last 30 minutes, by page, country, device or source |
ga4_metadata | Which dimension and metric names this property accepts, custom definitions included |
A few things they do that the raw APIs do not:
example.com and it finds
sc-domain:example.com among your properties. A name that matches nothing
comes back as an error listing what you do have, rather than an empty result
set that looks like zero traffic.28daysAgo, 3monthsAgo, yesterday alongside
YYYY-MM-DD, so the model never has to know today's date to ask for last
month.search_analytics makes the second call for you. Worth knowing:
query-level clicks never sum to the site total, because Google drops queries
made by very few people. Compare totals to totals.isLost rather than silently dropping out.positionDelta is an improvement. Every response that includes one says so,
because models reliably get this backwards otherwise.properties/987654321. A name matching more than one property is an
error listing the candidates, never a guess — reporting on the wrong property
silently is worse than failing.conversions to keyEvents,
and every property has its own custom dimensions. When the API rejects a
field, the error points at ga4_metadata, which lists what that specific
property accepts.Search Console reports a full URL in its page dimension; GA4 reports a
path in landingPage. To line them up, compare paths rather than whole
URLs. The pairing worth knowing: a page whose clicks and position held steady
while its GA4 conversions fell is a landing-page problem, not a ranking one —
and the reverse, impressions up but sessions flat, usually means the ranking
gain landed on a query that does not convert.
Worth understanding if you ever have to debug it.
/.well-known/oauth-protected-resource, which names this
server as its own authorization server, and
/.well-known/oauth-authorization-server for the endpoints./register (RFC 7591). Instead of a client table,
the registered metadata is sealed into the client_id itself, so
registration costs nothing and survives restarts./authorize with PKCE. The server validates the
redirect target twice — against what that client registered, and against a
server-wide policy that only permits the Claude callbacks and loopback. The
second check is what stops an open redirect, since anyone can register.ALLOWED_EMAILS and mints a 60-second authorization code./token verifies PKCE, marks the code spent, and issues an access token and
a refresh token. Both are AES-256-GCM sealed blobs carrying the Google
refresh token inside them, keyed separately per purpose — so an access token
cannot be replayed as a refresh token, or either as an authorization code.Refresh tokens rotate on every use, as OAuth 2.1 requires for public clients.
The server keeps essentially nothing. The only server-side state is a list of
spent authorization codes and rotated-out refresh tokens, both expiring. In
memory that is fine: after a restart the worst case is that a refresh token
which had already been rotated out keeps working until it expires on its own.
Set STATE_DIR to a writable path to persist it.
Nothing else is stored. No database, no session table, no Google tokens at rest — the Google refresh token only ever exists encrypted inside a token held by Claude.
npm test
119 checks against a stub that impersonates Google: the full connector lifecycle, plus the cases that matter — authorization code replay, PKCE mismatch, open-redirect attempts, non-allowlisted accounts, refresh rotation, and token type confusion. On the GA4 side it asserts the request bodies the server builds, not just the responses it parses: filter trees, string-typed limits, and two date ranges in one call. No network access needed.
"Couldn't reach the MCP server" — Claude never found the discovery
documents. Check that https://YOUR-DOMAIN/.well-known/oauth-protected-resource
returns JSON from the public internet, and that its resource field is exactly
the URL you typed into Claude, trailing slash and all. A WAF or Cloudflare
rule that blocks /.well-known/* will do this too; Anthropic's requests come
from 160.79.104.0/21.
"Authorization with the MCP server failed" — usually the Google redirect URI.
It must be https://YOUR-DOMAIN/oauth/google/callback exactly, in the same
OAuth client whose ID you configured. Set LOG_LEVEL=debug and watch the logs
through a connect attempt; every rejection is logged with a reason.
"This Google account is not permitted" — the email Google returned is not in
ALLOWED_EMAILS. The rejection is logged with the address it saw.
"Google did not return a refresh token" — Google only re-issues one on explicit consent. Remove the app at https://myaccount.google.com/permissions and connect again.
A Google OAuth app left in Testing has its refresh tokens expire after seven days, which shows up as the connector going dead once a week. Two fixes:
webmasters is — this normally prompts verification, but an
app with no other users can be published and used while unverified; you will
see an "unverified app" interstitial on sign-in that you click through once.403 from the API — either the Google account genuinely lacks access to that property, or the relevant API is not enabled in the Cloud project. The error text distinguishes them, and when it is the latter it names the exact service to enable.
"The Google sign-in behind this connection does not cover this API" — the
stored Google grant predates a scope the server now needs, which happens if you
turn on GA4_ENABLED or GSC_ENABLE_WRITE after connecting. Disconnect the
connector in Claude and connect it again; the sign-in re-consents to the full
current scope set.
A GA4 field name is rejected — call ga4_metadata with a search term.
conversions is now keyEvents, and custom dimensions are named per property
(customUser:plan_tier and the like).
SERVER_SECRET is the key to every token the server has ever issued. Rotating
it invalidates all connections — harmless, you reconnect once.webmasters.readonly Google scope, so even a total compromise cannot change
anything in Search Console. GA4 is read-only unconditionally — there is no
setting that grants this server write access to Analytics.GA4_ENABLED=false removes the Analytics scope from the sign-in entirely,
rather than merely hiding the tools./register, /authorize and /token are rate limited per IP.