github-data-mcp
Provides tools to retrieve your recent GitHub activity, list open pull requests, and get repository summaries from GitHub.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@github-data-mcpwhat did I work on today?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
github-data-mcp
A single-user remote MCP server that exposes your own GitHub activity to Claude as a custom connector. Three read-only tools, Streamable HTTP on one endpoint, deployed as one Vercel function.
POST https://<your-project>.vercel.app/mcp
Authorization: Bearer <MCP_AUTH_TOKEN>Tool | Answers | Arguments |
| "what did I work on today?" |
|
| "what's waiting on me?" | none |
| "how is owner/name doing?" |
|
Quick start
npm install
cp .env.example .env # then fill in GITHUB_TOKEN and MCP_AUTH_TOKEN
npm run smokenpm run smoke boots the server in-process on a random port, drives it through
a real MCP client over Streamable HTTP, and calls all three tools against
the live GitHub API. It also checks the guards (missing bearer → 401, wrong
bearer → 401, unexpected Origin → 403) and that error payloads never contain
your token. Run this before wiring anything into Claude.
npm run smoke -- --repo owner/name --since 2026-08-01T00:00:00ZTo run the server for real:
npm run dev # tsx watch, http://127.0.0.1:3000/mcpA hand-rolled request (note that the MCP spec requires both Accept types, even though this server only ever answers with JSON):
curl -sS http://127.0.0.1:3000/mcp -H "Authorization: Bearer $MCP_AUTH_TOKEN" -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Related MCP server: GitHub MCP Server
The GitHub token
Create a fine-grained PAT at https://github.com/settings/personal-access-tokens:
Resource owner: your own account
Repository access: all repositories (or just the ones you care about)
Repository permissions, all read-only:
Contents— commit history forget_recent_activityMetadata— mandatory, auto-selectedIssues— open issue count forget_repo_summaryPull requests—list_open_prsCommit statuses— CI state on PRs
Two things that silently return less data rather than erroring, so check them if a result looks empty:
Private contributions. Turn on Settings → Public profile → Include private contributions on my profile. Without it the contributions API omits private-repo commits from
get_recent_activityentirely.Organisation approval. A fine-grained PAT only reaches an org's repos if that org has approved fine-grained tokens and you selected it as the resource owner. Unapproved org repos come back empty, not 403.
Configuration
Variable | Required | Purpose |
| yes | Fine-grained PAT, read-only |
| yes | Static bearer callers must present to reach |
| no | Comma-separated browser origins allowed to call the endpoint |
| no | Include org-owned repos in |
| no | Include forks in |
| no | Local server port (default |
Generate the bearer with real entropy — it is the only thing between the open internet and your GitHub data:
node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"Origin handling (DNS-rebinding guard)
A browser always attaches an Origin header; a server-to-server MCP client
normally does not. So the rule is:
No
Originheader → allowed. This is the normal path for Claude, curl, and the smoke test.Originpresent → it must appear inALLOWED_ORIGINS, otherwise403. WithALLOWED_ORIGINSunset, every request carrying anOriginis rejected.
That is what stops a page on evil.com from driving this endpoint through your
browser while your session cookies ride along. Set ALLOWED_ORIGINS only if a
browser app of yours genuinely needs to call the server. * is accepted as a
wildcard and disables the guard — don't.
Scope of the data
Two deliberate limits, worth knowing before you trust an empty result:
get_recent_activity sees default branches only. It issues one GraphQL
request: contributionsCollection names the repos you committed to in the
window, and the same query walks each repo's defaultBranchRef history filtered
to your author id. Getting per-commit detail off every branch would mean
fanning out per ref — several requests instead of one — so commits sitting on an
unmerged feature branch will not appear. The tool description tells the model
this explicitly, so it reports "nothing landed on a default branch" rather than
"you did no work". (One extra request happens on a cold process: the viewer's
node id is fetched once and cached, because history(author:) needs it.)
Repo ownership filters apply to get_recent_activity only. By default it
covers repos you own, private included, excluding forks and org-owned repos —
flip INCLUDE_ORG_REPOS / INCLUDE_FORKS to widen it. list_open_prs is
deliberately not filtered this way: review requests are by definition mostly
on repos you don't own, so filtering them out would defeat the tool.
Output and error shape
Every tool returns compact JSON in a single text block, with null/empty fields
omitted rather than serialised as null:
{"since":"2026-08-10T13:00:00.000Z","until":"2026-08-11T13:00:00.000Z","commit_count":2,"repo_count":1,
"commits":[{"repo":"me/thing","branch":"main","message":"Fix the parser","at":"2026-08-11T09:12:44Z","additions":31,"deletions":4,"sha":"a1b2c3d","private":true}]}get_recent_activity is capped at 50 commits, newest first. When more exist
the response carries "truncated": true plus a truncated_note saying how many
were available, so the model can say so instead of presenting a partial list as
complete.
Nothing throws past the tool boundary. Failures come back as isError: true
with one consistent shape:
{"error":{"code":"github_rate_limit","message":"GitHub API rate limit exhausted. Retry after the reset time; no data was returned.","reset_at":"2026-08-11T14:03:11.000Z","retry_after_seconds":1847,"limit":5000}}
| Meaning |
| Malformed |
| A required env var is unset; the message names it |
| HTTP 401 from GitHub — names |
| Valid token, missing permission or org approval |
| Includes |
| Repo missing, renamed, or not visible to the token |
| Everything else |
Token values are never echoed. Messages name the environment variable, and a
final redaction pass scrubs both configured secrets and anything matching
GitHub's token formats (ghp_…, github_pat_…) before a message leaves the
process.
Deploying to Vercel
Deployment uses Vercel's captured Node.js server mode, so there is no
vercel.json and no api/ directory to keep in sync. Vercel's Node runtime
looks for a reserved entrypoint name — src/server.ts is one of them — captures
the HTTP server that file starts, and routes every request to it. Our own router
then serves POST /mcp, so the public endpoint is
https://<project>.vercel.app/mcp.
The upshot is that production and npm run dev run the same code path: both
start src/server.ts, and the smoke test drives the same router on an ephemeral
port.
npm i -g vercel
vercel link
vercel env add GITHUB_TOKEN production
vercel env add MCP_AUTH_TOKEN production
vercel deploy --prodThen confirm the deployed endpoint is reachable and rejecting anonymous callers:
curl -sS -o /dev/null -w "%{http_code}\n" -X POST https://<project>.vercel.app/mcp401 is the correct answer. Anything else — especially 200 with HTML — means
you are not hitting the function.
Four Vercel-specific things:
Deployment Protection. If Vercel Authentication is enabled on the project, every request gets bounced to an SSO login page and Claude will see HTML instead of JSON-RPC. Turn it off for production (Project → Settings → Deployment Protection), or the connector cannot reach you. This is the single most common reason a deployed MCP server "works locally and not in Claude".
Use the production URL. Preview deployments get a fresh URL per commit; only the production domain is stable enough to register as a connector.
src/server.tsis load-bearing, in both directions. The reserved entrypoint names (server,app,index,main, at the root or insrc/) are matched by filename, and the matched file must calllisten()during module startup. Two failure modes follow, and this project hit both:A file with a reserved name that does not call
listen()still gets captured, and every request — including paths that should 404 — dies withFUNCTION_INVOCATION_FAILED. This is why the MCP server factory is namedsrc/mcp-server.tsand notsrc/server.ts.No file with a reserved name at all, and the build fails outright with "No entrypoint found". So
src/server.tsmust exist and must listen.
If you rename or move
src/server.ts, the deployment breaks one way or the other. It is a five-line file for exactly this reason.Deleting
vercel.jsonwas deliberate. In captured-server mode the routing is ours, so rewrites are unnecessary; afunctionsblock pointing at anapi/path that no longer exists would fail the build. Timeouts and memory come from project settings instead.Stateless by design. Each request builds its own
McpServerand transport withsessionIdGenerator: undefined, so no state is assumed to survive between invocations — which is exactly right for serverless, where it wouldn't.
Connecting it to Claude
Claude Code / Claude Desktop, which support custom headers:
claude mcp add --transport http github-data https://<project>.vercel.app/mcp --header "Authorization: Bearer <MCP_AUTH_TOKEN>"One constraint to be aware of: claude.ai's web "Add custom connector"
dialog takes a URL and optional OAuth client credentials — it has no field for
an arbitrary Authorization header. A static bearer therefore works with Claude
Code and Claude Desktop, but cannot be presented by the claude.ai web connector
UI. If you later want this server in the web UI, you would either add the OAuth
flow described below, or move the secret into the URL path (a secret in a URL
gets logged in more places, so it is the worse of the two).
Why there is no OAuth here
The MCP authorization spec describes OAuth 2.1: the MCP server acts as a Resource Server, a separate Authorization Server issues access tokens, clients register dynamically, and the server publishes protected-resource metadata for discovery.
All of that machinery exists to solve problems this deployment does not have:
Delegation. OAuth lets a server act on behalf of users whose credentials it must not hold. Here the server holds my own PAT, which I issued to myself and can revoke in GitHub's UI at any time. There is no third party to delegate from.
Multi-tenancy. OAuth's access tokens distinguish which user is calling. With exactly one user, the caller's identity is a constant — there is nothing for a token to assert beyond "you know the shared secret", which a bearer already does.
Per-client consent and revocation. Meaningful when many clients hold grants of varying scope. With one client, revocation is "rotate one env var and redeploy".
So OAuth would add an authorization server, a token endpoint, refresh handling, and dynamic client registration in order to authenticate one person against themselves. The static bearer is the honest version of the same guarantee.
What that costs, stated plainly: the bearer never expires, is not bound to an audience, and grants read access to everything the PAT can see. It is mitigated by being 32 random bytes, HTTPS-only, on a single endpoint, in front of read-only tools, backed by a read-only PAT — and rotated by changing one environment variable. That is a reasonable trade for a personal deployment and an unreasonable one for anything shared.
Exactly what would change for multi-user
Become a Resource Server. Serve
/.well-known/oauth-protected-resource(RFC 9728) and return401withWWW-Authenticate: Bearer resource_metadata="…"so clients can discover the authorization server. The SDK ships helpers for this.Stand up an Authorization Server. GitHub is an OAuth provider but does not support Dynamic Client Registration (RFC 7591), which MCP clients expect, so you need your own AS in front of it — or a static client registry — that performs the GitHub OAuth App / GitHub App flow and issues your tokens.
Validate the audience on every request. Reject tokens that were not issued for this resource. The spec is explicit that a server must not accept pass-through tokens minted for someone else; skipping this is the confused deputy vulnerability.
Resolve a GitHub token per request instead of per process. Today
src/http.tsreads oneGITHUB_TOKENout ofConfigand hands it tobuildServer. It would instead map the validated access token to that user's stored GitHub credentials — the SDK surfaces this asAuthInfoon the tool callback'sextraargument — and pass a per-request context down to the tools, whose signatures already take a config object rather than reading globals.Store and refresh per-user GitHub tokens encrypted at rest (Vercel KV, Postgres, whatever), with refresh-token handling. This is the part that turns a stateless function into a service with a database.
Fix the viewer cache — this one is a real bug waiting to happen.
getViewerinsrc/github.tsmemoises the authenticated user's node id and login in a module-level variable. For one user that is a free request saved; for many it is a cross-user data leak the moment two people share a warm Lambda. It must become keyed by user, or be dropped.Per-user rate limiting and quotas. GitHub's 5000 points/hour is currently yours alone. Shared, you need per-user accounting and backpressure.
Steps 1–3 are protocol work, 4–6 are this codebase, and 7 is operations. The
Origin guard and the error shapes need no changes.
Layout
src/server.ts Entrypoint: starts the HTTP server (Vercel captures this)
src/node-server.ts node:http routing — POST /mcp, GET /healthz, 404
src/http.ts Origin guard, bearer check, stateless transport wiring
src/mcp-server.ts McpServer + the three tool registrations
src/github.ts GraphQL client, HTTP/GraphQL error mapping, viewer cache
src/tools/ One file per tool: query, shaping, and its description
src/config.ts Env loading (names may appear in errors, values never)
src/errors.ts ToolError, structured payloads, redaction
scripts/smoke.ts End-to-end verification against the real APIThe tool descriptions live at the bottom of each src/tools/*.ts file. They
are the part a model actually reads, so they carry the argument examples, the
"call this when…" triggers, the "don't call this for…" boundaries, and the
caveats needed to interpret an empty result correctly. Edit them with more care
than the code.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Alicense-qualityDmaintenanceAn MCP server that gives Claude Desktop complete intelligence about any public GitHub repository. Research libraries, compare packages, audit dependencies, and explore codebases through natural conversation.1MIT
- Flicense-qualityDmaintenanceA Model Context Protocol (MCP) server that connects Claude AI directly to the GitHub API, enabling natural language queries for live repository data, issues, PRs, and contributions.1
- Flicense-qualityDmaintenanceA working MCP server that connects to the real GitHub API, enabling users to manage repositories, issues, pull requests, and more through natural language in Claude Desktop.
- Flicense-qualityCmaintenanceA read-only MCP server that exposes GitHub user profiles, repository info, and search via tools for AI assistants like Claude.
Related MCP Connectors
Hosted MCP server connecting claude.ai, ChatGPT and other AI apps to your own computer
An MCP server that gives your AI access to the source code and docs of all public github repos
A MCP server built for developers enabling Git based project management with project and personal…
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ElOleksii/github-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server