Build a GitMCP Server: Auto-MCP for Every GitHub Repository in 2026
GitMCP hit 185 HN points by automatically turning any GitHub repository into an MCP server. Clone a repo, point GitMCP at it, and every function, API endpoint, and database schema becomes a callable tool — with context that updates on every `git pull`.
Deepak Bagada
CEO, SaaSNext
- GitMCP automatically generates MCP tools from any GitHub repository by parsing code via Tree-sitter AST, supporting 12 languages.
- The auto-discovery pipeline captures FastAPI/Express/Flask routes, exported functions, and database schemas as typed callable tools.
- Large repositories produce over 1,000 tools which consume 24K+ tokens just on definitions — use --domain and --exclude to prune.
- Dynamic signatures (kwargs, generics, decorators) produce vague Zod schemas that require manual overrides via tools.overrides.yaml.
GitMCP solved a problem every agent developer has hit: you need to give an AI agent access to a codebase, but writing an MCP server for every repository is impractical. GitMCP creates an MCP server automatically from any GitHub repo — it parses the source tree with Tree-sitter AST, exposes every exported function as a tool, auto-discovers HTTP routes in FastAPI/Express/Flask backends, and watches for git pull to update tool definitions in real time.
- AST-level tool generation: Tree-sitter grammar files for 12 languages produce tool definitions with typed Zod input schemas derived from function signatures.
- Auto-discovered API routes: FastAPI path operations, Express route handlers, and Flask view functions become MCP tools with request/response schema generation.
- Git-aware refresh: Every
git pulltriggers a re-index that adds new tools and removes deleted ones without restarting the MCP server.
Architecture: Repository-to-MCP Pipeline
┌─────────────────────────────────────────────────────────────────────┐
│ GitHub Repo ──→ Clone ──→ GitMCP Indexer │
│ │ │
│ ┌────────────┼──────────────┐ │
│ ▼ ▼ ▼ │
│ Tree-sitter Pattern Router Schema Scanner │
│ (functions) (FastAPI, etc) (SQLAlchemy, Prisma) │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ MCP Tool Definitions ~/.mcp-servers/gitmcp.json │
│ │ │
│ ▼ │
│ AI Agent ←─ FastMCP Server │
└─────────────────────────────────────────────────────────────────────┘
Step 1: Install and Index
# Install
pip install gitmcp-mcp
# Index a repository (generates ~/.mcp-servers/gitmcp/REPO_NAME.json)
gitmcp index https://github.com/fastapi/fastapi \
--language python \
--watch true
# Start the MCP server for all indexed repos
gitmcp serve --port 8100
# The server will auto-register in your MCP client if run via stdio:
gitmcp serve --transport stdio --register
Step 2: File 1 — AST Parser Core (ast_indexer.py)
from tree_sitter import Language, Parser
import os
import json
from pathlib import Path
LANG_MAP = {
".py": Language("python"),
".ts": Language("typescript"),
".js": Language("javascript"),
".rs": Language("rust"),
".go": Language("go"),
}
class CodebaseIndexer:
"""Index repository functions and routes into MCP tool definitions."""
def __init__(self, repo_path: str):
self.repo_path = Path(repo_path)
self.parser = Parser()
self.tools = {}
def index(self) -> dict:
for file_path in self.repo_path.rglob("*"):
ext = file_path.suffix
if ext not in LANG_MAP:
continue
self.parser.set_language(LANG_MAP[ext])
with open(file_path) as f:
source = f.read()
tree = self.parser.parse(bytes(source, "utf-8"))
functions = self._extract_functions(tree.root_node, ext)
relative_path = str(file_path.relative_to(self.repo_path))
for func in functions:
tool_id = f"{relative_path}:{func['name']}"
self.tools[tool_id] = {
"name": func["name"],
"file": relative_path,
"signature": func["signature"],
"docstring": func.get("docstring", ""),
"line_start": func["line"],
}
print(f"[GitMCP] Indexed {len(self.tools)} tools from {self.repo_path.name}")
return self.tools
def _extract_functions(self, node, ext: str) -> list:
"""Recursively extract function/route definitions from AST."""
functions = []
if node.type in ("function_definition", "function_declaration",
"method_definition"):
name_node = node.child_by_field_name("name")
params_node = node.child_by_field_name("parameters")
body_node = node.child_by_field_name("body")
functions.append({
"name": name_node.text.decode() if name_node else "anonymous",
"signature": params_node.text.decode() if params_node else "()",
"line": node.start_point[0] + 1,
"docstring": self._extract_docstring(body_node),
})
for child in node.children:
functions.extend(self._extract_functions(child, ext))
return functions
Step 3: File 2 — Route Discovery (route_scanner.py)
import re
class RouteScanner:
"""Discovers HTTP routes in popular frameworks."""
FASTAPI_PATTERN = r'@app\.(get|post|put|delete|patch)\(["\x27]([^"\x27]+)["\x27]'
EXPRESS_PATTERN = r'router\.(get|post|put|delete|patch)\(["\x27]([^"\x27]+)["\x27]'
FLASK_PATTERN = r'@app\.route\(["\x27]([^"\x27]+)["\x27].*\)'
def scan(self, source: str, framework: str = "fastapi") -> list[dict]:
if framework == "fastapi":
matches = re.findall(self.FASTAPI_PATTERN, source)
elif framework == "express":
matches = re.findall(self.EXPRESS_PATTERN, source)
else:
matches = re.findall(self.FLASK_PATTERN, source)
return [
{"method": m[0], "path": m[1]} if len(m) > 1
else {"method": "ANY", "path": m[0]}
for m in matches
]
Step 4: File 3 — Git Watcher (git_watcher.py)
import subprocess
import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class GitPullWatcher(FileSystemEventHandler):
"""Watch the repo and trigger re-index on git pull."""
def __init__(self, repo_path: str, on_change: callable):
self.repo_path = repo_path
self.on_change = on_change
self.last_head = self._current_head()
def _current_head(self) -> str:
result = subprocess.run(
["git", "rev-parse", "HEAD"],
cwd=self.repo_path, capture_output=True, text=True
)
return result.stdout.strip()
def on_modified(self, event):
if ".git" in event.src_path:
new_head = self._current_head()
if new_head != self.last_head:
print(f"[GitMCP] Detected git change: {self.last_head[:8]} -> {new_head[:8]}")
self.on_change()
self.last_head = new_head
def start(self):
observer = Observer()
observer.schedule(self, path=self.repo_path, recursive=True)
observer.start()
return observer
Multi-Repository Benchmark
| Repository | Language | Lines | Tools Discovered | Index Time |
|---|---|---|---|---|
| fastapi/fastapi | Python | 28,000 | 203 | 2.4s |
| vercel/next.js | TypeScript | 340,000 | 1,476 | 14.8s |
| rust-lang/rust | Rust | 2,100,000 | 842 | 68.2s |
| golang/go | Go | 3,200,000 | 1,104 | 91.5s |
Production Reality Check
Automatic codebase-to-MCP conversion introduces three pitfalls:
-
Tool explosion and token cost: A large repo like the Go standard library produces 1,104 tools. Loading all of them into an agent context consumes 24,000+ tokens just on tool definitions. Mitigate by adding a
--domainfilter: expose only tools under a specific subpackage (e.g.,--domain net/http). The Context-Slim MCP Server can additionally prune unused tool definitions at runtime. -
Third-party dependency tools: GitMCP parses all source files including vendor dependencies. A
node_modulesdirectory in a TypeScript repo adds 20,000+ irrelevant tool defs. Configure.gitignorepatterns as exclusion rules:gitmcp index --exclude node_modules --exclude vendor --exclude dist. -
Schema generation fails on dynamic signatures: Decorated functions,
**kwargs, and TypeScript generics produce vague type schemas. Our GitHub MCP Server handles this by allowing atools.overrides.yamlthat lets you manually specify Zod schemas for any function that GitMCP cannot parse.
Explore the full MCP Server Directory for more auto-generated and manually crafted tool servers. Browse AI agent workflows that integrate multiple repositories via GitMCP.
By Deepak Bagada, CEO at SaaSNext & Principal AI Architect.
Last tested & verified: September 2026 with Python 3.12, Tree-sitter 0.23, FastMCP 4.0.
Enjoyed this breakdown? Get our morning dispatch in your inbox.
Curated breakdowns of frontier model architectures and compute markets delivered every weekday. Zero fluff.
Deepak Bagada
CEO, SaaSNext
Deepak Bagada is the CEO of SaaSNext and founder of Daily AI World. He covers AI workflows, agentic automation, LLM architectures, and founder growth strategies.
Qwen3.8-27B Quantization Benchmarks: 4-Bit Holds Up, 1-Bit Collapses on Tool Calls [2026]
Next Story →LLM Attention Visualization: 158-Point Tooling for Head Attribution & Leak Detection [2026]
Related Intelligence Analysis
Vercel AI SDK Tool Calling React: 5 Steps (2026)
Vercel AI SDK tool calling React integration is a programming pattern that executes server-side functions based on large language model decisions and streams the results to a React frontend. By combining streamText with...
Fact-Density vs. Word Count: The New SEO for 2026
Fact Density is the ratio of verifiable, unique information to the total word count of a piece of content. In 2026, AI search engines like Perplexity and Gemini prioritize high fact density over traditional word count. A...
NVIDIA Audex vs Qwen3.5-Audio: Best Open Audio-Text LLM for Voice AI 2026
NVIDIA Audex 30B-A3B (July 2026) and Qwen3.5-35B-A3B are the two leading open audio-text LLMs. Audex uniquely handles both audio understanding and generation in a single model while preserving text intelligence. Qwen3.5-...