Chapter 3: Authenticate your agent
At the end of Chapter 2 you locked yourself out on purpose. Your agent still has the server in its config, but every call now comes back 401: Unauthorized because the MCP server demands an access token and your agent doesn’t have one. This chapter grants you access the right way: your agent signs in through Keycard, and from now on every call has a real identity that Keycard can see.
The fun part is, you don’t write any code in this chapter, and you don’t touch the console either. The server is already protected and already advertising how to authenticate. Your agent can follow that trail on its own. Your just have to let it, by triggering authentication and approving it in the browser.
How the agent knows how to log in
Section titled “How the agent knows how to log in”Remember the 401 from Chapter 2. That response included a WWW-Authenticate header pointing at your resource metadata, which points at Keycard. A well-behaved agent treats that as a set of instructions:
- Your agent calls your MCP server endpoint, gets the
401, and reads theWWW-Authenticateheader. - It follows the header to your protected-resource metadata, which names Keycard as the authorization server.
- It reads Keycard’s OAuth metadata, which advertises a registration endpoint. The agent registers itself as a client on the spot. This doesn’t require a client ID or client secret because it’s Dynamic Client Registration. This is why you didn’t have to configure anything for your agent in Keycard in Chapter 1.
- You’re already logged into Keycard, so your agent opens your browser and prompts you to approve access (this is the consent you set to required in Chapter 1).
- Keycard returns a token scoped to your MCP server, the agent caches it, and voilà, you (and your agent) are in.
The agent is reacting to metadata the server already supplies. That’s why agent authentication doesn’t require any more code.
Connect your agent
Section titled “Connect your agent”Make sure your server is still running (npm run dev in support-escalation/). Then trigger authentication for your agent. Pick your tab below; the steps differ because each agent exposes the OAuth flow a little differently.
Your server is already registered from Chapter 0, so you don’t re-add it. You just authenticate it now that it’s protected.
-
In Claude Code, run:
/mcp -
You’ll see
support-escalationlisted under Local MCPs and marked needs authentication. Select it and choose Authenticate. -
Your browser opens and depending on if you already signed in last chapter, prompts you for GitHub authentication/consent, Keycard consent, and Linear consent. Log in and approve any access requests. When the flow completes, Claude Code announces the
support-escalationserver is connected.
Claude Desktop reaches your HTTP endpoint through mcp-remote (the bridge you set up in Chapter 0). mcp-remote runs the OAuth flow.
-
Fully quit and reopen Claude Desktop. On startup,
mcp-remotetries to connect to thesupport-escalationMCP server, sees the401, runs discovery, and opens your browser for authentication and authorization. -
Sign in with GitHub / Keycard / Linear as necessary and approve access.
mcp-remotecatches the callback, exchanges it for a token, and caches it under~/.mcp-auth. -
The
support-escalationtools are available again in the app.
Codex connects to your endpoint over HTTP using the entry you added in Chapter 0, and authenticates on demand.
-
Trigger the login:
Command linecodex mcp login support-escalation -
Your browser opens and depending on if you already signed in last chapter, prompts you for GitHub authentication/consent, Keycard consent, and Linear consent. Log in and approve any access requests. When the flow completes, Codex announces you’re logged in to the
support-escalationserver. -
The
support-escalationtools are available again.
Cursor reaches your HTTP endpoint using the entry you added in Chapter 0, and runs the OAuth flow when the server requires it. You may need to log out and back in again to trigger the connection flow.
-
Open Settings (
Cmd+Shift+J/Ctrl+Shift+J) → Features → Model Context Protocol. When Cursor hits the now-protected server and gets a401, it runs discovery and surfacessupport-escalationas needing authentication. Start the login from that panel (Cursor may also open your browser automatically when it detects the401). -
Your browser opens and, depending on whether you already signed in last chapter, prompts you for GitHub authentication/consent, Keycard consent, and Linear consent. Log in and approve any access requests.
-
Cursor catches the callback, exchanges it for a token, and the
support-escalationtools are available again.
Make an authenticated call
Section titled “Make an authenticated call”With the agent reconnected, prompt it the same way you did in Chapter 0:
Using the support-escalation tools, list the open support tickets.
This time the call goes through with a token attached, and you get the tickets back. The request is no longer anonymous. Behind the tool response is a token Keycard minted for you, scoped to your MCP server resource.
See authentication in the audit log
Section titled “See authentication in the audit log”-
Go to console.keycard.ai and click Audit Log in the sidebar.
-
Find the most recent
credentials:issueevent. Its Entity is your Workshop MCP Server resource, and its Actor is the client your agent registered for itself (you’ll see a name likeMCP CLI Proxyfor themcp-remotebridge used by Claude Desktop, or your agent’s own registered client, e.g.,Claude CodeorCodex). -
Click into the event. Under Actor Details you’ll see the type is an identity chain with two identities: the agent’s client and you, your GitHub email, listed as the User. The raw Request Information shows
grant_type: authorization_codeandscopes: ["read", "issues:create"].
Before this, there was no way to distinguish one caller from another. Your MCP server no longer sees an anonymous caller, and it no longer sees a faceless shared key. It recognizes your agent acting on your behalf, and explicitly logs that. Compare that to Chapter 0 where tool calls weren’t logged at all, and even if they were, they would have been indistinguishable from each other.