# 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

> Source: <https://dev.to/sravya_dangeti/how-i-connected-an-ai-agent-to-github-with-nango-and-mcp-without-touching-a-single-oauth-token-14j1>
> Published: 2026-09-28 16:16:28+00:00

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](https://github.com/sravya520/nango-github-mcp)

``` php
MCP client  ->  my MCP server (Python)  ->  Nango proxy (adds GitHub auth)  ->  GitHub API
```

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 | 

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

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.

``` python
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,
    )
    ...
```

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

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.

``` python
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]
```

Three small choices make agents behave better:

`create_issue` is not, so clients can treat writes more carefully.
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:

``` python
from mcp.server.mcpserver.exceptions import ToolError

class NangoError(ToolError):
    """A readable error the agent can show to the user."""
```

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.

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

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:

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

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

`.env` for stray lines: `python-dotenv` warned me about a parse error on line 4.`command` is the program that runs the server (Python). The server file goes in `args`. I had typed the server's name into `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.
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](https://github.com/sravya520/nango-github-mcp)
