DEV Community

Sravya Dangeti
Sravya Dangeti

Posted on

How I connected an AI agent to GitHub with Nango and MCP (without touching a single OAuth token) published: false tags: ai, mcp, python, tutorial

Every time you connect an AI agent to an external API, you inherit the boring, risky part: OAuth flows, token storage, token refresh, and making sure nothing leaks.

In this tutorial I built a small MCP server in Python that exposes GitHub to any MCP client, where my code never sees a GitHub token. Nango handles the auth. My server only knows a Nango secret key and a connection ID.

Code: https://github.com/sravya520/nango-github-mcp

How it works

MCP client  ->  my MCP server (Python)  ->  Nango proxy (adds GitHub auth)  ->  GitHub API
Enter fullscreen mode Exit fullscreen mode

The server exposes three tools:

Tool What it does
list_my_repos Lists my repos, most recently updated first
list_open_issues Lists open issues in a repo (skips pull requests)
create_issue Creates an issue in a repo

Step 1: Connect GitHub in Nango

Nango's getting-started flow creates a GitHub integration (github-getting-started) and has you authorize your GitHub account. That gives you a connection ID. From then on, Nango stores and refreshes the GitHub token for that connection.

Put your values in a .env file and add .env to .gitignore:

NANGO_SECRET_KEY=your-secret-key
NANGO_CONNECTION_ID=your-connection-id
NANGO_PROVIDER_CONFIG_KEY=github-getting-started
Enter fullscreen mode Exit fullscreen mode

Step 2: Call GitHub through Nango's proxy

Instead of calling api.github.com with a token, you call Nango's proxy with three headers. Nango finds the connection, adds the GitHub token and forwards the request.

def nango_request(method, endpoint, params=None, json=None):
    headers = {
        "Authorization": f"Bearer {os.getenv('NANGO_SECRET_KEY')}",
        "Connection-Id": os.getenv("NANGO_CONNECTION_ID"),
        "Provider-Config-Key": os.getenv("NANGO_PROVIDER_CONFIG_KEY"),
    }
    response = httpx.request(
        method, f"https://api.nango.dev/proxy{endpoint}",
        headers=headers, params=params, json=json, timeout=20,
    )
    ...
Enter fullscreen mode Exit fullscreen mode

A one-line smoke test (GET /user) confirms the whole chain works:

Smoke test: connected to GitHub as sravya520

Step 3: Turn the calls into MCP tools

I used the official MCP Python SDK. One thing that caught me out: in version 2.x, FastMCP was renamed to MCPServer, so older tutorials fail on import.

from mcp.server.mcpserver import MCPServer

mcp = MCPServer("github-via-nango")

@mcp.tool(annotations=READ_ONLY)
def list_my_repos(limit: int = 10) -> list[dict]:
    """List the user's GitHub repositories, most recently updated first.
    Use this first to find a repo's full name (owner/name)."""
    repos = nango_request("GET", "/user/repos",
                          params={"sort": "updated", "per_page": _clamp(limit)})
    return [{"full_name": r["full_name"], "url": r["html_url"]} for r in repos]
Enter fullscreen mode Exit fullscreen mode

Three small choices make agents behave better:

  • The docstring is the instruction. The agent reads it to decide when to call the tool. "Use this first to find a repo's full name" helps it chain tools in the right order.
  • Small results. Lists are capped at 20 items so the agent's context stays small.
  • Tool annotations. Read tools are marked read-only and create_issue is not, so clients can treat writes more carefully.

Step 4: Make errors readable for the agent

This was my biggest lesson. In the MCP SDK, if a tool raises a normal Python exception, the agent only sees "Error executing tool". It has no idea what went wrong.

Only errors raised as ToolError pass their message through. So my error class extends it:

from mcp.server.mcpserver.exceptions import ToolError

class NangoError(ToolError):
    """A readable error the agent can show to the user."""
Enter fullscreen mode Exit fullscreen mode

Now a bad repo name produces an answer the agent can act on (see the screenshot in the next step). I wrote a test that locks this in, plus seven others covering the Nango headers and URL, filtering out pull requests, input validation and error messages.

Step 5: Test it over real MCP

VS Code is an MCP host. When I added the server, it started it and showed it as Running, with all three tools:

VS Code showing github-via-nango Running

To test the protocol directly, I wrote a small Python client (client_demo.py in the repo). It launches the server over stdio, lists the tools, and calls them the way an agent would:

client_demo.py: tools listed and repos returned

And the error case, where the message comes through clearly:

client_demo.py: readable error for a bad repo name

What I tested, and what I didn't: list_my_repos ran live over MCP, through Nango to GitHub, and the bad-repo error came back over MCP too. list_open_issues and create_issue are covered by the unit tests, but I did not run them against my live account.

Problems I hit (so you don't have to)

  • 401 Unauthorized from Nango. Nango has separate environments, each with its own secret key, so copy the key from the environment where your connection lives. Also check your .env for stray lines: python-dotenv warned me about a parse error on line 4.
  • "Command not found" in the VS Code MCP setup. command is the program that runs the server (Python). The server file goes in args. I had typed the server's name into command.
  • Wiring an AI client is the fiddly part. Copilot's agent mode was out of quota, and Claude Code's panel did not pick up my server even though claude mcp list showed it connected. Rather than keep debugging, I tested with the script above. Because the server speaks standard MCP, it should work with any MCP client.

Why I like this approach

The agent side stays simple: three small tools and clear errors. All the hard auth work (OAuth, storage, refresh) lives in Nango. Adding another API like Notion or Slack would mostly mean a new integration in Nango and a few more tools, not a new auth system.

Code, tests and setup: https://github.com/sravya520/nango-github-mcp

Top comments (0)