Building a Production-Ready Python MCP Server with FastAPI and GitHub API
Back to Blog
Development8 min read

Building a Production-Ready Python MCP Server with FastAPI and GitHub API

HHazrat Ummar ShaikhJune 20, 20261 views

Remember when AI models felt like black boxes? You'd feed them a prompt, and they'd spit out text, often without real-world context or the ability to do anything beyond generating. That era is rapidly fading, thanks to concepts like the Model Context Protocol (MCP).

MCP, which started as a more niche idea, has quickly become an industry standard. It's essentially a way for your AI models to interact with external tools and gain real-time, dynamic context. Think of it like giving your super-smart assistant not just a brain, but also a utility belt full of gadgets. Today, we're going to build one of those gadgets: a Python MCP server, specifically designed to leverage the GitHub API. This is powerful stuff that can transform how your AI applications operate, letting them fetch data, update statuses, or even kick off workflows directly.

Why a custom Python MCP server? Because while there are frameworks, understanding and controlling the pipeline yourself gives you immense flexibility. Plus, let's be honest, getting hands-on with the GitHub API and FastAPI is just plain fun.

This isn't about generic AI talk; we're diving deep into practical implementation. By the end, you'll have a working Python MCP server that can act as a sophisticated middleware between your AI model and the rich data of your GitHub repositories.

Let's get started.

An abstract 3D illustration of disparate data streams, represented as glowing, colored tendrils of light, converging int

Unpacking the Model Context Protocol (MCP)

So, what exactly is MCP? At its core, it's a protocol that defines how an AI model requests and receives information or actions from external systems. Instead of the model hallucinating or asking for information it can't get, it can tell its host application, "Hey, I need some context on X, or I need to perform action Y. Do you have a tool for that?"

The MCP server then acts as the intermediary. It receives these "tool calls" from the model, executes the requested action using external APIs (like GitHub's), and returns the result back to the model. This loop gives the AI real-time, accurate data and the ability to trigger real-world operations.

Imagine an AI assistant helping a developer. Instead of just saying "You need to check the CI/CD status," an MCP-enabled assistant could say, "I've checked the latest commit on your 'feature/cool-new-thing' branch, and the CI/CD pipeline failed with an 'auth token expired' error. Do you want me to generate a new token and retry?" See the difference? That's the power of context and tooling.

Why Python and FastAPI for Your MCP Server?

  • Python's Ecosystem: For AI and scripting, Python is king. Its libraries for HTTP requests (httpx, requests), data manipulation, and web frameworks are mature and extensive.
  • FastAPI's Performance and Developer Experience: FastAPI is a modern, high-performance web framework for building APIs with Python 3.7+ based on standard Python type hints. It's built on Starlette (for the web parts) and Pydantic (for data validation and serialization). This means:
    • Blazing Fast: Seriously, it's quick.
    • Asynchronous: Built for async/await from the ground up, perfect for I/O-bound tasks like making API calls.
    • Automatic Docs: Swagger UI and ReDoc out of the box. A lifesaver for debugging and sharing.
    • Type Safety: Pydantic ensures your data ingress and egress are validated, catching errors early.

For a high-performance backend that needs to quickly mediate between an AI model and various external APIs, FastAPI is an absolute no-brainer. I've used it for high-traffic microservices and custom Discord bots, and it's always delivered.

Setting Up Your Environment

First things first, let's get our project set up. Create a new directory and initialize a virtual environment.

mkdir python-mcp-github
cd python-mcp-github
python3 -m venv venv
source venv/bin/activate

Now, install our core dependencies:

pip install fastapi uvicorn httpx python-jose
  • fastapi: The web framework.
  • uvicorn: An ASGI server to run our FastAPI application.
  • httpx: A modern, async-first HTTP client. You'll need this for making calls to the GitHub API.
  • python-jose: Essential for verifying GitHub webhook signatures.

Create a main.py file in your project root. This will house our FastAPI application.

A dynamic abstract visual metaphor for an API server: multiple glowing geometric shapes representing requests orbiting a

Building Your FastAPI MCP Server

Our MCP server will expose a single main endpoint, perhaps /mcp, where the AI model sends its tool calls. The server will then interpret these calls, execute them, and return a result.

Let's define a basic structure for our main.py:

# main.py
from fastapi import FastAPI, Request, HTTPException, Header
from pydantic import BaseModel
from typing import List, Dict, Any
import httpx
import os
import hmac
import hashlib

# Configuration for GitHub App or Personal Access Token (PAT)
# For production, use environment variables and GitHub Apps with JWT
from dotenv import load_dotenv
load_dotenv()

app = FastAPI(title="Python MCP GitHub Server")

GITHUB_WEBHOOK_SECRET = os.getenv("GITHUB_WEBHOOK_SECRET")
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN") # For PAT or testing.

# Simple model for incoming tool calls
class ToolCall(BaseModel):
    id: str
    tool_name: str
    parameters: Dict[str, Any]

# Simple model for MCP request
class MCPRequest(BaseModel):
    tool_calls: List[ToolCall]
    conversation_id: str = None
    model_context: Dict[str, Any] = None

# Global client for GitHub API. It's good practice to reuse HTTP clients.
github_client = httpx.AsyncClient(
    base_url="https://api.github.com",
    headers={
        "Authorization": f"token {GITHUB_TOKEN}" if GITHUB_TOKEN else "",
        "Accept": "application/vnd.github.v3+json"
    }
)

@app.post("/mcp")
async def handle_mcp_request(request_data: MCPRequest):
    results = []
    for tool_call in request_data.tool_calls:
        result = await execute_tool(tool_call)
        results.append({"tool_call_id": tool_call.id, "result": result})
    return {"status": "success", "results": results}

async def execute_tool(tool_call: ToolCall) -> Dict[str, Any]:
    if tool_call.tool_name == "get_github_repo_issues":
        owner = tool_call.parameters.get("owner")
        repo = tool_call.parameters.get("repo")
        if not owner or not repo:
            return {"error": "owner and repo parameters are required"}
        return await get_github_repo_issues(owner, repo)
    return {"error": f"Unknown tool: {tool_call.tool_name}"}

async def get_github_repo_issues(owner: str, repo: str) -> Dict[str, Any]:
    try:
        response = await github_client.get(f"repos/{owner}/{repo}/issues")
        response.raise_for_status()
        issues = response.json()
        return {"issues": [{"title": issue["title"], "number": issue["number"], "state": issue["state"]} for issue in issues]}
    except httpx.HTTPStatusError as e:
        return {"error": f"GitHub API error: {e.response.status_code} - {e.response.text}"}
    except Exception as e:
        return {"error": f"An unexpected error occurred: {str(e)}"}

@app.get("/")
async def read_root():
    return {"message": "MCP GitHub Server is running"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host=0.0.0.0, port=8000)

In this basic setup, we define MCPRequest and ToolCall models using Pydantic. Our /mcp endpoint then iterates through incoming tool calls and dispatches them to an execute_tool function. For now, we have one simple GitHub tool: get_github_repo_issues.

You'll need a .env file:

# .envGITHUB_TOKEN="YOUR_GITHUB_PERSONAL_ACCESS_TOKEN" # Or a token for GitHub AppGITHUB_WEBHOOK_SECRET="YOUR_WEBHOOK_SECRET"

Integrating with the GitHub API: Beyond Basic Calls

Direct API calls are one thing, but a truly dynamic GitHub integration needs to react to events. That means webhooks.

GitHub Webhook Verification

When GitHub sends a webhook, it includes a signature in the X-Hub-Signature-256 header. You MUST verify this to ensure the request genuinely came from GitHub and hasn't been tampered with. It's a critical security step I've seen missed too many times, leading to potential vulnerabilities.

Let's add a webhook endpoint and a verification utility:

# Add this function to main.py
def verify_github_signature(payload: bytes, signature: str, secret: str) -> bool:
    if not signature:
        raise HTTPException(status_code=401, detail="X-Hub-Signature-256 header missing")
    try:
        sha_name, signature_digest = signature.split('=', 1)
    except ValueError:
        raise HTTPException(status_code=400, detail="Invalid X-Hub-Signature-256 header format")
    if sha_name != 'sha256':
        raise HTTPException(status_code=400, detail="Unsupported signature algorithm")
    mac = hmac.new(secret.encode('utf-8'), payload, hashlib.sha256)
    return hmac.compare_digest(mac.hexdigest(), signature_digest)

@app.post("/github-webhook")
async def github_webhook(request: Request, x_hub_signature_256: str = Header(None)):
    if not GITHUB_WEBHOOK_SECRET:
        raise HTTPException(status_code=500, detail="GitHub webhook secret not configured")
    payload_bytes = await request.body()
    if not verify_github_signature(payload_bytes, x_hub_signature_256, GITHUB_WEBHOOK_SECRET):
        raise HTTPException(status_code=403, detail="Invalid GitHub signature")
    return {"status": "success", "message": "Webhook received and verified"}

Remember to set GITHUB_WEBHOOK_SECRET in your .env file and configure it in your GitHub repository's webhook settings. GitHub App installation is the more robust, scalable, and secure way to handle authentication for production, involving JWTs and transient installation tokens. For this post, we're sticking to a PAT for simplicity, but always consider the security implications for a real-world system.

Handling Context and State in MCP

One of the beauties of MCP is passing `model_context` along with `tool_calls`. This dictionary can contain any relevant information the model already knows or has learned, helping your MCP server make more informed decisions or tailor its responses.

For instance, if the `model_context` contains `{"user_role": "admin"}`, your `execute_tool` function could check this before allowing sensitive operations. Or, if it contains `{"last_issue_id": 123}`, a tool could fetch comments on that specific issue. This makes your tools much smarter and your AI more capable of carrying on nuanced interactions.

Important Note: Never pass sensitive credentials directly in `model_context`. Use it for context data, not secrets.

An abstract visual metaphor for an integrated intelligent system: intricate, glowing networks of interconnected nodes an

Wrapping Up: Your Smart Assistant's New Brain

You've just laid the groundwork for a powerful Python MCP server. We've gone from a simple FastAPI setup to handling dynamic tool calls and securing GitHub webhooks. This is not just a theoretical exercise; you've built the core logic that can let an AI model:

  • Fetch real-time data: Get the latest issues, PRs, or commit statuses from any GitHub repository.
  • Trigger actions: Imagine tools to create issues, add labels, assign reviewers, or even approve pull requests (with proper authorization, of course!).
  • React to events: Using webhooks, your MCP server can update your AI's internal state or proactively notify it about new commits, comments, or CI failures.

The next steps could involve expanding your `execute_tool` function with more GitHub API integrations, building a robust GitHub App for production-grade authentication, or even integrating with other external services. Think about adding tools for Jira, Slack, or your internal CI/CD pipelines. The possibilities are truly endless once you have this foundation.

I've personally seen how integrating custom tools like this can drastically improve the utility of AI assistants, turning them from conversational chatbots into actionable workflow automation engines. Go forth, build, and let your AI models do more than just talk!

Need a Professional Mobile & Backend Developer?

I build premium native mobile apps (Android, iOS) and high-performance backend systems (FastAPI, Ktor). Let's collaborate on your next project!

H

Written by

Hazrat Ummar Shaikh

Android Developer with 4+ years of experience. Built production Android apps, Ktor backends, Discord bots, and SaaS products using Kotlin, Python, and MongoDB. Passionate about building robust systems and writing clean code.

Related Posts

Demystifying Android OS: A Deep Dive for Web & Software Engineers
Development

Ever wonder what makes an Android app tick, or why permissions work the way they do? This deep dive pulls back the curtain on the Android OS, revealing its core architecture and how it impacts your daily development.

#Android OS#Mobile Development#Software Architecture
Jun 19, 2026
Read More
Taming the Beast: Practical Strategies for Modernizing Legacy Code Before It Consumes You
Development

Legacy code is an unavoidable reality for many developers, often turning into a beast if left unchecked. This post shares actionable strategies to refactor, modernize, and manage aging systems effectively.

#javascript#webdev#programming
Jun 19, 2026
Read More
Mastering Modern Android Architecture: A Practical Guide for Robust Apps
Development

Dive into modern Android development with practical insights on clean architecture, state management, and dependency injection. Build more reliable and maintainable Android applications.

#Android#Mobile Development#Kotlin
Jun 19, 2026
Read More