Welcome to the MCP (Model Context Protocol) project tutorial! In this series, we will build a robust command-line tool used to manage servers that talk to AI models.
We begin with the foundation: CLI Command Architecture.
Imagine you are walking into a large hotel. You approach the front desk and say, "I need extra towels." The receptionist doesn't run to get them; they look up the housekeeping department's number and route your request there. If you said, "I want to check out," they would route you to the billing department.
A CLI (Command Line Interface) works exactly the same way.
claude mcp add my-server.Without this architecture, the application is just a confusing blob of code that doesn't know what the user wants.
Throughout this chapter, we will focus on this specific command:
claude mcp add my-weather-server npx weather-cli
What needs to happen?
mcp is the main tool.add is the specific Subcommand.my-weather-server as the Name Argument.npx weather-cli as the Command Argument.To build this, we use a pattern involving four main components.
This represents a specific action (like add, list, or xaa). It holds the definition of what the user is allowed to type.
These are required pieces of information. In our use case, you must provide a name for the server.
<name>.These are modifiers. They aren't strictly required but change how the command behaves.
--transport http or -e KEY=value.This is a standard JavaScript function that runs only when the command matches.
Let's look at how we build this structure using the code from addCommand.ts. We use a library called Commander.js to do the heavy lifting.
We start by creating a function that registers our command onto the main program.
// From: addCommand.ts
import { type Command } from '@commander-js/extra-typings'
// We export a function that takes the main 'mcp' program
export function registerMcpAddCommand(mcp: Command): void {
// We define the trigger word "add" and the required arguments
mcp.command('add <name> <commandOrUrl> [args...]')
// We add a description for the help menu
.description('Add an MCP server to Claude Code.')
Explanation:
.command('add ...'): This tells the switchboard: "If the user types 'add', send them here."<name>: This is a required argument.[args...]: The square brackets [] mean this is optional, and the ... means it captures everything else typed after it.Next, we define what special flags the user can use.
// Continuing the chain...
.option(
'-t, --transport <transport>',
'Transport type (stdio, sse, http). Defaults to stdio.',
)
.option(
'-e, --env <env...>',
'Set environment variables (e.g. -e KEY=value)',
)
Explanation:
claude mcp add ... --transport http, the code receives options.transport = "http".
Finally, we define the .action(). This is where the actual logic lives.
// The logic that runs when the command matches
.action(async (name, commandOrUrl, args, options) => {
// 'name' comes from <name>
// 'commandOrUrl' comes from <commandOrUrl>
// 'options' contains flags like --transport
if (!name) {
// Handle error if name is missing
cliError('Error: Server name is required.')
}
// ... Logic to save the configuration ...
})
}
Explanation:
.action function receives the arguments we defined in <...> as variables.What actually happens under the hood when you press Enter?
index.ts) initializes the application.mcp. Is it add? Is it xaa?register... function for that command.
Sometimes commands act as "folders" for other commands. Look at xaaIdpCommand.ts.
// From: xaaIdpCommand.ts
export function registerMcpXaaIdpCommand(mcp: Command): void {
// Create a parent command 'xaa'
const xaaIdp = mcp
.command('xaa')
.description('Manage the XAA (SEP-990) IdP connection')
// Register a child command 'setup' UNDER 'xaa'
xaaIdp.command('setup')
.description('Configure the IdP connection')
.action(options => { /* ... */ })
}
Explanation:
This creates a nested structure: claude mcp xaa setup.
mcp is the root.xaa is a category.setup is the executable action.This is critical for keeping large applications organized. You will learn more about what "XAA" actually does in XAA Identity Management.
The CLI Architecture isn't just about routing; it's the first line of defense against bad data.
In addCommand.ts, before we do any heavy lifting (like provisioning servers), we validate inputs:
// From: addCommand.ts (inside .action)
// 1. Check if the user is trying to use XAA without enabling it
if (options.xaa && !isXaaEnabled()) {
cliError('Error: --xaa requires CLAUDE_CODE_ENABLE_XAA=1')
}
// 2. Validate dependent options
if (Boolean(options.xaa)) {
const missing: string[] = []
if (!options.clientId) missing.push('--client-id')
// ... check other required flags ...
}
Why do this here? It provides immediate feedback. If the CLI architecture detects a missing flag, it stops execution before the application tries to connect to a database or write a file.
Once validation passes, the command delegates the actual work to a service.
addMcpConfig (covered in MCP Server Provisioning).saveMcpClientSecret (covered in Secure Credential Handling).The CLI Command Architecture is the "User Interface" of a terminal application. It translates human intent (text) into machine action (function calls). By organizing commands into a structured hierarchy of names, arguments, and options, we create a tool that is intuitive to use and easy to maintain.
Now that we know how to parse the command to add a server, let's learn how to actually create and save that server configuration.
Next Chapter: MCP Server Provisioning
Generated by Code IQ