github-directory-mcp
README.md
# github-directory-mcp
An MCP (Model Context Protocol) server, written in TypeScript, that gives an AI assistant two tools
for querying GitHub: listing a user/org's repositories, and listing the directories inside a repo.
This README covers MCP from first principles through to this project's internals, running it, and
extending it.
---
## Table of contents
1. [What is MCP](#1-what-is-mcp)
2. [How the protocol actually works](#2-how-the-protocol-actually-works)
3. [Transports: stdio vs HTTP](#3-transports-stdio-vs-http)
4. [What this project does](#4-what-this-project-does)
5. [Project layout](#5-project-layout)
6. [Setup from scratch](#6-setup-from-scratch)
7. [Tool reference](#7-tool-reference)
8. [Accessing the server](#8-accessing-the-server)
9. [Troubleshooting](#9-troubleshooting)
10. [Advanced: extending this server](#10-advanced-extending-this-server)
11. [Security notes](#11-security-notes)
---
## Quick command reference
Every command actually needed to build, run, register, and debug this server, in one place. Run
these from a normal terminal (VS Code integrated terminal or system PowerShell) in the project
folder, `C:\Users\banda\OneDrive\Desktop\Mcp` — not a sandboxed/CI shell.
These are wired up as real `npm run` scripts in `package.json` — not just copy-paste text:
```powershell
# One-time setup
npm install
cp .env.example .env # then paste your GITHUB_TOKEN into .env
# Build (re-run after any src/index.ts edit)
npm run build
# Register with Claude Code (one-time; restart the session afterwards)
npm run register
# Manual visual testing via MCP Inspector
npm run inspector
# Same, but skip the session-token requirement (fixes stale-tab proxy errors)
npm run inspector:noauth
# Raw run (server just listens on stdio; used for scripted JSON-RPC testing)
npm start
# Verify GITHUB_TOKEN is valid
npm run check-token
# Find + stop whatever is holding the Inspector's ports (6274 UI / 6277 proxy)
netstat -ano | findstr "6274 6277"
Stop-Process -Id <PID> -Force
```
| Script | What it runs |
|---|---|
| `npm run build` | `tsc` — compiles `src/index.ts` → `dist/index.js` |
| `npm start` | `node dist/index.js` — raw stdio server |
| `npm run dev` | build then start, in one step |
| `npm run inspector` | Launches MCP Inspector against the built server |
| `npm run inspector:noauth` | Same, with `DANGEROUSLY_OMIT_AUTH=true` (via `cross-env`, so it works on Windows too) |
| `npm run register` | `claude mcp add github-directory -- node dist/index.js` |
| `npm run check-token` | Hits `GET /user` with your `GITHUB_TOKEN` and prints the resolved account or the raw error |
---
## 1. What is MCP
**Model Context Protocol** is an open standard (created by Anthropic, now used across the industry)
that defines a common way for an AI application — a chat client, an IDE assistant, an agent — to
discover and call external capabilities, without every AI vendor and every tool vendor needing a
custom integration for each other.
Before MCP: if you wanted Claude to talk to GitHub, Slack, a database, and a filesystem, someone had
to write four bespoke integrations *inside* the AI application, each with its own auth handling,
its own API shape, its own way of describing what it can do.
With MCP: each of those four things (GitHub, Slack, DB, filesystem) is wrapped in a small standalone
program called an **MCP server**. Any MCP-compatible client (Claude Code, Claude Desktop, other
agent frameworks) can talk to any MCP server using the same protocol. The server describes its own
capabilities at connection time — the client doesn't need prior knowledge of what a "GitHub server"
looks like.
The analogy people reach for is **USB-C for AI**: one physical/logical connector, many devices
behind it, no per-device custom cable.
### The three primitives MCP servers expose
| Primitive | What it is | Example in this project |
|---|---|---|
| **Tools** | Functions the model can call, with typed inputs/outputs. The model decides when to call them. | `list_github_repositories`, `list_github_directories` |
| **Resources** | Read-only data the client can attach to context (files, query results). Not used in this project. | — |
| **Prompts** | Reusable prompt templates the server offers. Not used in this project. | — |
This project only implements **tools**, which is the most common case — "let the model call a
function and get a result back."
---
## 2. How the protocol actually works
Under the hood, MCP is **JSON-RPC 2.0** — a simple, transport-agnostic RPC format where every
message is a JSON object with a `method`, optional `params`, and an `id` to match requests to
responses.
A session looks like this:
```
Client Server (this project)
|--- initialize ------------------------->| client says "here's my protocol version/capabilities"
|<-- initialize result -------------------| server replies with its name/version/capabilities
|--- notifications/initialized ---------->| client confirms handshake is done
|
|--- tools/list -------------------------->| client asks "what tools do you have?"
|<-- tools/list result -------------------| server returns tool names + JSON Schema for each input
|
|--- tools/call (list_github_repositories)->| client invokes a tool with arguments
|<-- tools/call result --------------------| server runs it, returns text/structured content
```
Every tool advertises an **input schema** (we use [Zod](https://zod.dev) to define it, which the SDK
converts to JSON Schema automatically). This is what lets the calling model know it must supply
`owner` and `repo` as strings for `list_github_directories`, for instance — the schema is enforced
before your tool code even runs (see `src/index.ts`'s `.min(1)` validators, which reject empty
strings with a clear error instead of letting a malformed request reach the GitHub API).
---
## 3. Transports: stdio vs HTTP
MCP defines the *message format* (JSON-RPC) separately from the *transport* (how bytes physically
move between client and server). Two transports matter in practice:
- **stdio** (what this project uses): the client spawns your server as a child process and talks to
it over its stdin/stdout. Zero networking, zero ports, zero auth needed — the client owns the
process. This is the right choice for a local tool a single user runs on their own machine, which
is what `claude mcp add ... -- node dist/index.js` does.
- **Streamable HTTP**: the server runs independently (its own process, possibly remote), and clients
connect to it over HTTP. Needed when the server is shared, long-running, or not co-located with
the client. Requires you to think about auth, since now it's a network-reachable service.
This project only implements stdio (see `StdioServerTransport` in `src/index.ts`). If you ever need
to expose it to multiple remote clients, that's the piece you'd swap out — the tool logic itself
wouldn't change.
---
## 4. What this project does
Two tools, both backed directly by the [GitHub REST API](https://docs.github.com/en/rest) (no
GitHub SDK dependency — just `fetch`):
- **`list_github_repositories`** — calls `GET /users/{owner}/repos` (falling back to
`GET /orgs/{owner}/repos` if that 404s), or `GET /user/repos` when `owner` is omitted and a token
is set. Returns `full_name`, description, privacy/fork flags, and URL for each repo.
- **`list_github_directories`** — calls `GET /repos/{owner}/{repo}/contents/{path}` for a shallow
listing, or `GET /repos/{owner}/{repo}/git/trees/{ref}?recursive=1` (filtered to `type: "tree"`)
for a full recursive directory walk.
Both accept an optional `GITHUB_TOKEN` (read from `.env` via `dotenv`) which is sent as a
`Bearer` token — required for your own private repos / own-account listing, and generally useful to
avoid GitHub's 60 req/hour unauthenticated rate limit (jumps to 5000 req/hour authenticated).
---
## 5. Project layout
```
Mcp/
├── src/index.ts Server source: tool definitions, GitHub API calls, stdio bootstrap
├── dist/index.js Compiled output — this is what actually gets executed
├── .env Your real GITHUB_TOKEN (git-ignored, never commit)
├── .env.example Template for .env (safe to commit)
├── .gitignore Excludes node_modules/, dist/, .env, Repository_token.txt
├── repositories.txt A saved snapshot of a list_github_repositories run
├── package.json Scripts: build (tsc), start, dev
└── tsconfig.json TypeScript compiler config (NodeNext modules, ES2022 target)
```
---
## 6. Setup from scratch
Requirements: Node.js 18+ (this was built and tested on Node 25).
```powershell
cd "C:\Users\banda\OneDrive\Desktop\Mcp"
npm install
cp .env.example .env
```
Edit `.env` and paste in a GitHub token:
```
GITHUB_TOKEN=github_pat_xxxxxxxxxxxxxxxxxxxx
```
(Generate one at github.com → Settings → Developer settings → Personal access tokens. Fine-grained,
read-only "Contents" + "Metadata" repo permissions are enough for these two tools.)
Build:
```powershell
npm run build
```
This compiles `src/index.ts` → `dist/index.js` via `tsc`. Re-run this any time you edit `src/index.ts`.
---
## 7. Tool reference
### `list_github_repositories`
| Param | Type | Required | Notes |
|---|---|---|---|
| `owner` | string | no | Username or org. Omit to list the authenticated user's own repos (needs `GITHUB_TOKEN`). |
| `perPage` | number 1-100 | no | Default 30. |
| `page` | number | no | Default 1. |
### `list_github_directories`
| Param | Type | Required | Notes |
|---|---|---|---|
| `owner` | string | **yes** | e.g. `"anthropics"`. Must be non-empty (validated). |
| `repo` | string | **yes** | e.g. `"claude-code"`. Must be non-empty (validated). |
| `path` | string | no | Default: repo root. |
| `ref` | string | no | Branch/tag/SHA. Default: repo's default branch. |
| `recursive` | boolean | no | Default false. `true` walks the entire tree via the Git Trees API. |
**Note:** both `owner` and `repo` must actually be provided — if you ask something vague like "list
my repo's directories" without naming a repo, the calling model may send empty strings, which now
fails fast with a validation error instead of silently hitting a malformed GitHub URL.
---
## 8. Accessing the server
There are two ways to actually *use* this server, depending on your goal.
### A. Through Claude Code (the real use case)
Register it once, from a normal terminal (not a sandboxed/CI one):
```powershell
claude mcp add github-directory -- node "C:\Users\banda\OneDrive\Desktop\Mcp\dist\index.js"
```
Restart your Claude Code session (MCP servers load at session start), then just ask, naming the repo
explicitly:
> "List directories in Productdeveloper5192/MCP"
> "List my github repositories"
Claude reads the tool schemas via `tools/list`, decides when a request warrants calling one, and
calls it via `tools/call` — all of that JSON-RPC exchange happens invisibly.
### B. MCP Inspector (manual/visual testing, no client needed)
```powershell
npx @modelcontextprotocol/inspector node dist/index.js
```
This starts two things: a **proxy** on port 6277 (bridges the browser to your stdio process) and a
**UI** on port 6274. It prints a URL with a session token baked in — open that exact URL.
1. Click **Connect**.
2. **Tools** tab → **List Tools**.
3. Pick a tool, fill the form, **Run Tool**.
If you keep hitting "Error Connecting to MCP Inspector Proxy" from stale browser tabs/tokens, skip
the token entirely for local testing:
```powershell
$env:DANGEROUSLY_OMIT_AUTH="true"
npx @modelcontextprotocol/inspector node dist/index.js
```
### C. Raw JSON-RPC over stdio (for scripting/debugging)
```powershell
node dist/index.js
```
Feed it JSON-RPC lines on stdin, one per line — this is what's used internally to smoke-test the
server. Example sequence: `initialize` → `notifications/initialized` → `tools/call`.
---
## 9. Troubleshooting
**`GitHub API 404 for /repos//`**
`owner`/`repo` were empty when the tool was called — usually because the prompt to Claude didn't
name a specific repo. Now blocked at the schema level with a clear validation error instead (see
`.min(1)` in `src/index.ts`). Name the repo explicitly in your prompt.
**"Error Connecting to MCP Inspector Proxy"**
The Inspector mints a new session token every time it starts. This almost always means an old
browser tab is using a stale token from a previous run. Close all old Inspector tabs and open the
exact URL (with token) printed by the *current* run.
**`MCP Inspector PORT IS IN USE at http://localhost:6274`**
A leftover `node` process from a prior Inspector run is still holding the port. Find and stop it:
```powershell
netstat -ano | findstr 6274
Stop-Process -Id <PID> -Force
```
**`claude: command not found`**
You're not in a terminal where Claude Code's CLI is on `PATH` — use VS Code's integrated terminal or
your normal system terminal, not a restricted/sandboxed shell.
**Rate limited / can't see private repos / `owner` omitted fails**
Check `GITHUB_TOKEN` is actually set in `.env` and non-empty. Verify it directly:
```powershell
curl -H "Authorization: Bearer $env:GITHUB_TOKEN" https://api.github.com/user
```
A `200` with your account info confirms the token is valid.
---
## 10. Advanced: extending this server
To add a new tool, follow the pattern already in `src/index.ts`:
```ts
server.registerTool(
"your_tool_name",
{
title: "Human-readable title",
description: "What it does — this is what the model reads to decide when to call it.",
inputSchema: {
someParam: z.string().min(1).describe("Shown to the model as field docs"),
},
},
async ({ someParam }) => {
// ... do work, call APIs, etc.
return { content: [{ type: "text", text: "result" }] };
}
);
```
Ideas in scope for this GitHub-focused server: list branches, list commits on a path, search code,
read a single file's content, list pull requests. Each is one more `githubFetch` call plus one more
`registerTool` block — no architectural changes needed.
If you ever need this reachable by more than one local client (e.g. a shared team server), swap
`StdioServerTransport` for the SDK's Streamable HTTP transport — at that point also add real auth in
front of it, since it stops being "only reachable by whoever can spawn the process" and becomes a
network service.
---
## 11. Security notes
- `.env` holds a live GitHub token — it is git-ignored. Never commit it, never paste its contents
into chat, issues, or logs.
- `Repository_token.txt` is also git-ignored as a safety net, but the token has already been moved
into `.env` — that plaintext file is redundant and can be deleted.
- Fine-grained PATs scoped to only the repos/permissions you need are safer than classic PATs with
broad scopes — regenerate and re-scope if the current token was created broadly.
- This server only ever reads from GitHub (no write/delete endpoints are implemented), so the blast
radius of a leaked token is limited to whatever *that token itself* can do on your account — treat
it with the same care as a password.
TDQS
A3.9/5.0
Scored across 2 tools
Disambiguation5/5
Each tool targets a distinct resource: one lists directories within a repository, the other lists repositories for a user/org. No overlap in purpose.
Naming Consistency5/5
Both tools follow a consistent 'list_github_<plural>' pattern, using snake_case and similar structure.
Tool Count3/5
With only 2 tools, the server feels thin for a GitHub utility. It may be intentionally scoped, but the count is borderline low.
Completeness2/5
The server only covers listing of directories and repositories, missing essential operations like file content access, creation, update, or deletion for either resource.
Maintenance
ActivityStale
ResponsivenessNo issues