Skip to content

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.

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:

  1. Your agent calls your MCP server endpoint, gets the 401, and reads the WWW-Authenticate header.
  2. It follows the header to your protected-resource metadata, which names Keycard as the authorization server.
  3. 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.
  4. 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).
  5. 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.

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.

  1. In Claude Code, run:

    /mcp
  2. You’ll see support-escalation listed under Local MCPs and marked needs authentication. Select it and choose Authenticate.

  3. 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-escalation server is connected.

With the agent reconnected, prompt it the same way you did in Chapter 0:

Prompt your coding agent

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.

Keycard console
  1. Go to console.keycard.ai and click Audit Log in the sidebar.

  2. Find the most recent credentials:issue event. Its Entity is your Workshop MCP Server resource, and its Actor is the client your agent registered for itself (you’ll see a name like MCP CLI Proxy for the mcp-remote bridge used by Claude Desktop, or your agent’s own registered client, e.g., Claude Code orCodex).

  3. 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_code and scopes: ["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.