Welcome back! In the previous chapter, Configuration Hierarchy & Loading, we learned how the application finds the "phonebook" of available servers.
Now that we know who we want to talk to, we face a new problem: The Door is Locked.
Most powerful tools (like GitHub, Google Drive, or Slack) require permission to access your data. This chapter covers how the MCP project handles Authentication & Security. Think of this as the "Passport Control" of the application.
If you are building a tool that connects to 10 different services, you don't want to:
We need a unified system that handles the "handshake" securely and remembers who you are so you don't have to keep proving it.
Imagine you want to use an MCP tool to read a file from Google Drive.
Error: 401 Unauthorized.We handle security using two main approaches: Standard OAuth and XAA.
This is what you are likely used to on the web.
This is an Enterprise feature designed to reduce "Login Fatigue."
Imagine you work at a big company with 50 internal tools. Logging into all of them individually is painful. XAA allows you to log in once to your company's Identity Provider (IdP), and that single login acts as a master key to silently unlock all authorized MCP servers.
When the application tries to connect to a protected server, it follows this decision tree:
Let's look at the code that powers this logic. The main entry point is in auth.ts.
The function performMCPOAuthFlow decides which path to take: XAA or Standard OAuth.
// auth.ts
export async function performMCPOAuthFlow(
serverName: string,
serverConfig: McpServerConfig,
// ... callbacks ...
) {
// 1. Check if this server is configured for Enterprise XAA
if (serverConfig.oauth?.xaa) {
// If yes, run the silent "Master Key" flow
await performMCPXaaAuth(serverName, serverConfig, /*...*/);
return;
}
// 2. Otherwise, run standard OAuth (Browser Pop-up)
const provider = new ClaudeAuthProvider(serverName, serverConfig, /*...*/);
// Start the standard flow...
// ...
}
Explanation: This function checks the configuration we loaded in Chapter 1. If xaa is true, it routes to the enterprise flow. Otherwise, it sets up a ClaudeAuthProvider to handle the standard browser flow.
For standard OAuth, after you click "Approve" in your browser, the website needs to send a code back to the running application. How does a website talk to a command-line tool?
The application briefly starts a tiny web server on your computer (usually on localhost).
// auth.ts
server = createServer((req, res) => {
const parsedUrl = parse(req.url || '', true)
// 1. Listen for the /callback URL
if (parsedUrl.pathname === '/callback') {
const code = parsedUrl.query.code;
// 2. Validate valid code and security state
if (code) {
res.end('<h1>Success! You can close this window.</h1>');
resolveOnce(code); // Pass the code back to the app
}
}
});
server.listen(port, '127.0.0.1');
Explanation: This code spins up a listener. It waits for the browser to redirect to http://localhost:port/callback?code=xyz. Once it catches the code, it shuts down the server.
We never save tokens in plain text files. We use the operating system's Keychain (Mac) or Credential Manager (Windows).
// auth.ts (inside ClaudeAuthProvider)
async saveTokens(tokens: OAuthTokens): Promise<void> {
const storage = getSecureStorage();
// Update the secure storage with the new token
storage.update({
mcpOAuth: {
[this.serverName]: {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
// Calculate exactly when it expires
expiresAt: Date.now() + (tokens.expires_in || 3600) * 1000,
},
},
});
}
Explanation: When we get a token, we calculate its expiration date and ask the SecureStorage utility to lock it away.
This is the advanced part. If XAA is enabled, we use an id_token (proof of who you are) to ask for an access_token (permission to use a specific tool) without bothering the user.
// xaa.ts
export async function performCrossAppAccess(config: XaaConfig) {
// 1. Exchange User Identity for a temporary "Grant"
const jag = await requestJwtAuthorizationGrant({
idToken: config.idpIdToken, // "I am Alice"
audience: config.asIssuer, // "I want to talk to GitHub"
// ...
});
// 2. Exchange "Grant" for the final Access Token
const tokens = await exchangeJwtAuthGrant({
assertion: jag.jwtAuthGrant,
// ...
});
return tokens; // Success! No browser needed.
}
Explanation: This code performs a "Token Exchange." It says to the server: "Here is proof from our Company ID system that I am Alice. Please give Alice the key to the database." The server verifies the proof and hands over the key silently.
Tokens usually expire after an hour. It would be annoying to log in every hour. The system handles Refresh Tokens automatically.
In ClaudeAuthProvider.tokens(), the system checks:
This happens in the background, so the user flow remains uninterrupted.
In this chapter, we learned:
Now that we are configured (Chapter 1) and authenticated (Chapter 2), we are ready to actually open a line of communication.
Next Chapter: Connection Lifecycle Management
Generated by Code IQ