Welcome to the final chapter! In Chapter 4: XAA Identity Management, we learned how to use a "Passport" (Identity Provider) to log in to multiple servers.
However, to get that passport, or to talk to certain servers, we often need Secrets (Passwords, API Keys, or Client Secrets).
This raises a critical question: Where do we save these secrets?
If we save them in a plain text file, anyone who sees your computer can steal them. If you commit that file to GitHub, the whole world can steal them.
In this chapter, we will build Secure Credential Handling.
Imagine a large office building.
In the MCP CLI:
config.json) are the Directory. They hold URLs and Names. It is safe to copy/paste these to a friend.Secure Credential Handling is the logic that automatically splits your data: names go to the file, passwords go to the Keychain.
Let's look at this command:
claude mcp add my-server https://api.company.com --client-id 123 --client-secret
Note the --client-secret flag. The CLI will prompt you to type the password.
Our Goal:
https://api.company.com and client-id: 123 into a JSON file.
We never write secrets to config.json. The application code must explicitly separate "Public Config" from "Secret Config" before saving.
To find the password later, we need a label. Usually, we use the Server Name or the Issuer URL as the "Key" to look up the password in the Keychain.
If you delete the server from the config file, you must remember to delete the password from the Keychain. If you don't, you leave "Ghost Credentials" cluttering the secure storage.
Let's see how this separation works inside addCommand.ts.
We don't want the user to type the password in the command line history (where it stays visible). We prompt for it or read it from the environment.
// From: addCommand.ts
// 1. Check if the user wants to use a secret
const clientSecret =
options.clientSecret && options.clientId
? await readClientSecret() // <--- Prompts user securely
: undefined
Explanation: readClientSecret() hides what you type (showing ***), just like sudo on Linux.
Next, we create the configuration object without the secret.
// 2. Create the Public Config Object
const serverConfig = {
type: 'sse',
url: actualCommand,
oauth: {
clientId: options.clientId
// NOTICE: No clientSecret here!
}
}
// 3. Save to JSON file
await addMcpConfig(name, serverConfig, scope)
Explanation: This writes to the file system. If you opened the file later, you would see the URL and ID, but the secret would be missing.
Finally, we put the secret in the "Digital Safe."
// 4. Save the secret to the Keychain
if (clientSecret) {
// We use the 'name' and 'serverConfig' to generate a unique key
saveMcpClientSecret(name, serverConfig, clientSecret)
}
Explanation: saveMcpClientSecret calls the operating system's native security API. It encrypts the password and stores it.
Here is what happens when you press Enter.
Writing secrets is easy. Managing them when things change is harder. Let's look at xaaIdpCommand.ts to see how we handle updates and deletion.
When the application needs to log in, it pulls from both sources.
// From: xaaIdpCommand.ts
// 1. Get public settings
const idp = getXaaIdpSettings()
// 2. Get private secret
const secret = getIdpClientSecret(idp.issuer)
// 3. Combine them to log in
await acquireIdpIdToken({
idpClientId: idp.clientId,
idpClientSecret: secret,
// ...
})
Explanation: The application reconstructs the full credential set in memory, uses it for a millisecond to authenticate, and then discards it. The secret never touches the disk.
What if you change the URL of a server? The old password in the keychain is now linked to a URL that doesn't exist. We must clean it up.
// From: xaaIdpCommand.ts (inside setup command)
// If the Issuer URL changed...
if (oldIssuer && oldIssuer !== newIssuer) {
// ...delete the OLD secret
clearIdpClientSecret(oldIssuer)
}
// Then save the NEW secret
if (newSecret) {
saveIdpClientSecret(newIssuer, newSecret)
}
Explanation: We compare the old settings (before saving) with the new ones. If they differ, we "garbage collect" the old secrets to keep the user's keychain clean.
When a user runs clear or remove, we must be thorough.
// From: xaaIdpCommand.ts (inside clear command)
// 1. Remove from Public Config
updateSettingsForSource('userSettings', { xaaIdp: undefined })
// 2. Remove from Keychain
if (idp) {
clearIdpClientSecret(idp.issuer)
// Also remove any cached tokens
clearIdpIdToken(idp.issuer)
}
Explanation: We perform a synchronized delete. If we only did step 1, the secret would stay in the keychain forever (a "ghost credential").
Secure Credential Handling is about trust. By respecting the difference between "Configuration" (Public) and "Credentials" (Private), we build a tool that professionals can use safely.
In this tutorial series, you have built a complete CLI application:
You now possess the foundational knowledge of the MCP CLI architecture. Happy coding!
Generated by Code IQ