Skip to main content
Workflows Library MCP Directory Realtime AI News Sponsor Tier Subscribe
Front Page / AI Tools / Deep Dive

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

Deepak Bagada

CEO, SaaSNext

Sep 09, 2026 Published
|
Sep 09, 2026 Updated
|
7 Minutes Reading Time
Core Takeaways for Founders & Builders
  • 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 pull triggers 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:

  1. 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 --domain filter: 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.

  2. Third-party dependency tools: GitMCP parses all source files including vendor dependencies. A node_modules directory in a TypeScript repo adds 20,000+ irrelevant tool defs. Configure .gitignore patterns as exclusion rules: gitmcp index --exclude node_modules --exclude vendor --exclude dist.

  3. Schema generation fails on dynamic signatures: Decorated functions, **kwargs, and TypeScript generics produce vague type schemas. Our GitHub MCP Server handles this by allowing a tools.overrides.yaml that 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.

Executive Briefing

Enjoyed this breakdown? Get our morning dispatch in your inbox.

Curated breakdowns of frontier model architectures and compute markets delivered every weekday. Zero fluff.

🎉 Thank You for Subscribing!

Frequently Asked Questions
No. All parsing and indexing happens locally. The codebase is read from disk and the MCP server runs as a local FastMCP process. No code is sent to any remote API.
Python, TypeScript, JavaScript, Rust, Go, Java, Ruby, C, C++, Zig, Kotlin, and Swift. Tree-sitter grammar files for these 12 languages are bundled with the package. Additional languages can be added by installing the corresponding grammar.
GitMCP works with any locally cloned repository. For private repos, clone them with your GitHub credentials, then run `gitmcp index` on the local path. The indexed tool definitions remain local.
Yes. Use the --domain flag to restrict to a subpackage, the --exclude flag for directory patterns, or create a tools.overrides.yaml file for per-function granular control including custom schemas and descriptions.
Deepak Bagada
Author Profile

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.

Related Intelligence Analysis

Briefing AI Tools

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

Deepak Bagada Deepak Bagada
12m read
Breaking AI Tools

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

Deepak Bagada Deepak Bagada
4m read
Audio Briefing
Accessibility Preferences
High Contrast Mode
Accessible Reading Font

Keyboard Shortcuts

Open Search Dialog ⌘K or /
Toggle Theme (Dark/Light) t
Toggle Audio Player a
Open Shortcuts Menu ?
Close Active Dialog Esc