Skip to content

Latest commit

 

History

History
90 lines (68 loc) · 4 KB

File metadata and controls

90 lines (68 loc) · 4 KB

Part 4: Remote, Authenticated MCP

The last step: you don't host the server at all. GitHub hosts its MCP server remotely at api.githubcopilot.com/mcp/, behind OAuth. ToolHive runs an MCPRemoteProxy with an embedded OAuth auth server in the cluster: it does the GitHub OAuth upstream and lets the agent authenticate to the proxy. The agent loop is unchanged from Part 3 — main.py just adds an OAuth handshake.

This is the advanced setup. Don't sweat the cluster internals — the auth is brokered on the ToolHive side; you run two steps, approve once in the browser, and the agent calls GitHub's real tools.

What changes from Part 3

  • The MCP server is remote and GitHub's — no container of ours.
  • It's behind OAuth. ToolHive's embedded auth server brokers it: the agent does an authorization-code + PKCE flow against the proxy, which chains to GitHub; the human approves once.
  • main.py gains an OAuthClientProvider (folded inline — still the only Python file). list_tools / call_tool and the agent loop are otherwise identical.

Files

  • main.py — given. Discovers GitHub's remote tools, runs the shared async loop, with the OAuth handshake inline (no separate client file).
  • toolhive/github-remote.yaml — the three resources deployed to the cluster: MCPOIDCConfig + MCPExternalAuthConfig (embedded auth server) + MCPRemoteProxy.
  • cluster-prereqs.sh — Step 1: cluster + operator + the auth server's secrets.

One-time GitHub OAuth app

Create a GitHub OAuth app (github.com/settings/developers):

  • Authorization callback URL: http://localhost:8080/oauth/callback
  • Put its client id in toolhive/github-remote.yaml (clientId) — the workshop app id is already filled in.
  • Put its client secret in .env as GITHUB_OAUTH_CLIENT_SECRET=… (Part 4 reads .env only — it never looks at your shell environment).

(The agent's own callback — localhost:8765 — is registered dynamically with ToolHive's auth server, not with GitHub, so it needs no app config.)

Stand it up (kind + operator)

Two steps. The first is a script; the second you type by hand — deploying the proxy + running the agent is the Part-4 moment.

# Step 1/2 — cluster + operator + the embedded auth server's secrets.
#            Reads GITHUB_OAUTH_CLIENT_SECRET from .env (not your shell env);
#            deletes + recreates all three secrets each run. (Prints Step 2.)
bash parts/04_remote_oauth/cluster-prereqs.sh

# Step 2/2 — deploy the proxy + auth server, expose it, run the agent (type these):
kubectl apply -f parts/04_remote_oauth/toolhive/github-remote.yaml
kubectl wait --for=condition=Ready mcpremoteproxy/github-remote \
  -n toolhive-system --timeout=120s
kubectl port-forward -n toolhive-system svc/mcp-github-remote-remote-proxy 8080:8080 &
uv run python parts/04_remote_oauth/main.py   # opens a browser to approve, then runs

Connect Claude Code to it

The proxy speaks MCP over HTTP behind OAuth, and Claude Code is an OAuth-capable MCP client — so it can use the remote server too, doing the same browser handshake main.py does. With the port-forward from Step 2 still running:

claude mcp add --transport http github-remote http://localhost:8080/mcp

Then trigger the OAuth flow from inside Claude Code: run /mcp, pick github-remote, and authenticate. A browser opens to approve (the embedded auth server chains through to GitHub); after that Claude Code calls GitHub's real remote tools through the proxy. Remove it with claude mcp remove github-remote.

Use localhost, not 127.0.0.1: the auth server's issuer and audience are pinned to http://localhost:8080/ (see github-remote.yaml), and the OAuth resource indicator must match that host exactly or the handshake fails.

Why it's last

Auth is the most failure-prone live step (browser redirects, issuer config, token refresh). Keeping it last means the rest of the tutorial runs even if this is skipped.

Tear down

bash cluster/uninstall.sh   # deletes the kind cluster