{"slug": "how-i-connected-an-ai-agent-to-github-with-nango-and-mcp-without-touching-a-tags", "title": "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", "summary": "A developer built a Python MCP server that exposes GitHub tools to any MCP client while delegating all OAuth handling to Nango, so the server code never sees a GitHub token and only holds a Nango secret key and connection ID. The server exposes list_my_repos, list_open_issues, and create_issue tools, and the author notes that in MCP Python SDK 2.x FastMCP was renamed to MCPServer, breaking older tutorials, and that only errors raised as ToolError pass readable messages through to the agent.", "body_md": "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.\n\nIn 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.\n\nCode: [https://github.com/sravya520/nango-github-mcp](https://github.com/sravya520/nango-github-mcp)\n\n``` php\nMCP client  ->  my MCP server (Python)  ->  Nango proxy (adds GitHub auth)  ->  GitHub API\n```\n\nThe server exposes three tools:\n\n| Tool | What it does | \n|---|---|\n| `list_my_repos` | Lists my repos, most recently updated first | \n| `list_open_issues` | Lists open issues in a repo (skips pull requests) | \n| `create_issue` | Creates an issue in a repo | \n\nNango'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.\n\nPut your values in a `.env` file and add `.env` to `.gitignore`:\n\n```\nNANGO_SECRET_KEY=your-secret-key\nNANGO_CONNECTION_ID=your-connection-id\nNANGO_PROVIDER_CONFIG_KEY=github-getting-started\n```\n\nInstead 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.\n\n``` python\ndef nango_request(method, endpoint, params=None, json=None):\n    headers = {\n        \"Authorization\": f\"Bearer {os.getenv('NANGO_SECRET_KEY')}\",\n        \"Connection-Id\": os.getenv(\"NANGO_CONNECTION_ID\"),\n        \"Provider-Config-Key\": os.getenv(\"NANGO_PROVIDER_CONFIG_KEY\"),\n    }\n    response = httpx.request(\n        method, f\"https://api.nango.dev/proxy{endpoint}\",\n        headers=headers, params=params, json=json, timeout=20,\n    )\n    ...\n```\n\nA one-line smoke test (`GET /user`) confirms the whole chain works:\n\nI 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.\n\n``` python\nfrom mcp.server.mcpserver import MCPServer\n\nmcp = MCPServer(\"github-via-nango\")\n\n@mcp.tool(annotations=READ_ONLY)\ndef list_my_repos(limit: int = 10) -> list[dict]:\n    \"\"\"List the user's GitHub repositories, most recently updated first.\n    Use this first to find a repo's full name (owner/name).\"\"\"\n    repos = nango_request(\"GET\", \"/user/repos\",\n                          params={\"sort\": \"updated\", \"per_page\": _clamp(limit)})\n    return [{\"full_name\": r[\"full_name\"], \"url\": r[\"html_url\"]} for r in repos]\n```\n\nThree small choices make agents behave better:\n\n`create_issue` is not, so clients can treat writes more carefully.\nThis 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.\n\nOnly errors raised as `ToolError` pass their message through. So my error class extends it:\n\n``` python\nfrom mcp.server.mcpserver.exceptions import ToolError\n\nclass NangoError(ToolError):\n    \"\"\"A readable error the agent can show to the user.\"\"\"\n```\n\nNow 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.\n\nVS Code is an MCP host. When I added the server, it started it and showed it as Running, with all three tools:\n\nTo 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:\n\nAnd the error case, where the message comes through clearly:\n\n**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.\n\n`.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.\nThe 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.\n\nCode, tests and setup: [https://github.com/sravya520/nango-github-mcp](https://github.com/sravya520/nango-github-mcp)", "url": "https://wpnews.pro/news/how-i-connected-an-ai-agent-to-github-with-nango-and-mcp-without-touching-a-tags", "canonical_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_at": "2026-09-28 16:16:28+00:00", "updated_at": "2026-09-28 16:21:29.916279+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "developer-tools", "ai-tools"], "entities": ["Nango", "GitHub", "MCP", "Python", "VS Code", "sravya520"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/how-i-connected-an-ai-agent-to-github-with-nango-and-mcp-without-touching-a-tags", "markdown": "https://wpnews.pro/news/how-i-connected-an-ai-agent-to-github-with-nango-and-mcp-without-touching-a-tags.md", "text": "https://wpnews.pro/news/how-i-connected-an-ai-agent-to-github-with-nango-and-mcp-without-touching-a-tags.txt", "jsonld": "https://wpnews.pro/news/how-i-connected-an-ai-agent-to-github-with-nango-and-mcp-without-touching-a-tags.jsonld"}}