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