Skip to main content
Glama
ElOleksii

github-data-mcp

by ElOleksii

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

get_recent_activity

"what did I work on today?"

since (optional ISO 8601, default 24h ago)

list_open_prs

"what's waiting on me?"

none

get_repo_summary

"how is owner/name doing?"

repo (required, "owner/name")


Quick start

npm install
cp .env.example .env    # then fill in GITHUB_TOKEN and MCP_AUTH_TOKEN
npm run smoke

npm 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:00Z

To run the server for real:

npm run dev      # tsx watch, http://127.0.0.1:3000/mcp

A 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 for get_recent_activity

    • Metadata — mandatory, auto-selected

    • Issues — open issue count for get_repo_summary

    • Pull requestslist_open_prs

    • Commit statuses — CI state on PRs

Two things that silently return less data rather than erroring, so check them if a result looks empty:

  1. 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_activity entirely.

  2. 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

GITHUB_TOKEN

yes

Fine-grained PAT, read-only

MCP_AUTH_TOKEN

yes

Static bearer callers must present to reach POST /mcp

ALLOWED_ORIGINS

no

Comma-separated browser origins allowed to call the endpoint

INCLUDE_ORG_REPOS

no

Include org-owned repos in get_recent_activity (default false)

INCLUDE_FORKS

no

Include forks in get_recent_activity (default false)

PORT

no

Local server port (default 3000, ignored on Vercel)

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 Origin header → allowed. This is the normal path for Claude, curl, and the smoke test.

  • Origin present → it must appear in ALLOWED_ORIGINS, otherwise 403. With ALLOWED_ORIGINS unset, every request carrying an Origin is 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}}

code

Meaning

bad_argument

Malformed since or repo — message says what was expected

config_missing

A required env var is unset; the message names it

github_auth

HTTP 401 from GitHub — names GITHUB_TOKEN as the thing to fix

github_forbidden

Valid token, missing permission or org approval

github_rate_limit

Includes reset_at and retry_after_seconds

github_not_found

Repo missing, renamed, or not visible to the token

github_error / network_error / internal_error

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 --prod

Then 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/mcp

401 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.ts is load-bearing, in both directions. The reserved entrypoint names (server, app, index, main, at the root or in src/) are matched by filename, and the matched file must call listen() 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 with FUNCTION_INVOCATION_FAILED. This is why the MCP server factory is named src/mcp-server.ts and not src/server.ts.

    • No file with a reserved name at all, and the build fails outright with "No entrypoint found". So src/server.ts must 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.json was deliberate. In captured-server mode the routing is ours, so rewrites are unnecessary; a functions block pointing at an api/ 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 McpServer and transport with sessionIdGenerator: 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:

  1. 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.

  2. 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.

  3. 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

  1. Become a Resource Server. Serve /.well-known/oauth-protected-resource (RFC 9728) and return 401 with WWW-Authenticate: Bearer resource_metadata="…" so clients can discover the authorization server. The SDK ships helpers for this.

  2. 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.

  3. 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.

  4. Resolve a GitHub token per request instead of per process. Today src/http.ts reads one GITHUB_TOKEN out of Config and hands it to buildServer. It would instead map the validated access token to that user's stored GitHub credentials — the SDK surfaces this as AuthInfo on the tool callback's extra argument — and pass a per-request context down to the tools, whose signatures already take a config object rather than reading globals.

  5. 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.

  6. Fix the viewer cache — this one is a real bug waiting to happen. getViewer in src/github.ts memoises 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.

  7. 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 API

The 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.

F
license - not found
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

  • A
    license
    -
    quality
    D
    maintenance
    An 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.
    1
    MIT
  • F
    license
    -
    quality
    D
    maintenance
    A 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
  • F
    license
    -
    quality
    D
    maintenance
    A 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.
  • F
    license
    -
    quality
    C
    maintenance
    A read-only MCP server that exposes GitHub user profiles, repository info, and search via tools for AI assistants like Claude.

View all related MCP servers

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…

View all MCP Connectors

Latest Blog Posts

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