Home / Extending: skills, MCP, subagents, hooks, plugins
Building your own MCP server
A homemade MCP server launches as a child process over stdio transport, declares a JSON schema in inputSchema that the server itself must validate, and a precise description decides when Claude calls it.
A homemade MCP server is nothing more than an ordinary program that reads from and writes to its standard input and output following the MCP protocol. Claude Code launches it itself as a child process, via the stdio transport, without this program ever listening on a network port. The same mechanism serves an internal tool, connected to a private database, that no public server could expose without risk.
Declaring it to Claude Code
The registration command clearly separates what belongs to Claude Code from what belongs to the server: claude mcp add --env KEY=value --transport stdio server-name -- command arguments. Everything after the double dash is passed through to the server as is, without interpretation; everything before it, --transport or --env, configures the connection on Claude Code's side.
claude mcp add --env DEPOT=./donnees --transport stdio compteur -- node ./serveurs/compteur.js
The compteur.js process then starts at each session that needs it, receives the DEPOT variable in its environment, and communicates with Claude Code through messages written to its standard output.
The schema decides what gets in, the description decides whether it gets called
Each tool declares a JSON schema that bounds its valid arguments. A tool that counts the words in a text can require a mandatory string and reject any other type:
{
"name": "compter_mots",
"description": "Compte le nombre de mots dans un texte fourni en argument.",
"inputSchema": {
"type": "object",
"properties": {
"texte": { "type": "string" }
},
"required": ["texte"]
}
}
This schema declares what the tool expects, a missing or wrongly typed argument does not conform to it, but validation remains the server's responsibility: the MCP specification requires the server to validate every tool input, the schema is not a guarantee enforced on the client side before the call. Nor does the schema decide whether Claude chooses this tool over another: that role belongs to the description field, exactly as with a skill. A description that promises more than what the tool actually does, for example counting the words of any document without specifying that it only reads a text argument already supplied, backfires as soon as Claude relies on it for a case it does not cover.
The previous lesson shows how to check that a server declared this way indeed shows up connected before you trust it.
From the request to the MCP tool call
An engineer registers a homemade MCP server with claude mcp add --transport stdio compteur -- node ./serveurs/compteur.js, whose only declared tool requires a text argument of type string. He then asks Claude to count the words of an integer he types directly into the conversation.
Write in one sentence what this situation establishes, and in one sentence what it does not establish.
What this establishes: This situation establishes that the compteur tool requires an argument of type string, declared in its schema, and that the request supplies an integer rather than a string.
What this does not establish: It does not establish whether Claude actually attempted to call this tool with that number, nor how the schema reacted to this particular attempt.
The three most common miscalibrations
- Too broad This situation proves that the schema rejected the call and that Claude reported the type error to the engineer.
- Too narrow This situation says nothing at all, since only one tool among those possible was declared on this server.
- Beside the point This situation confirms that the stdio transport chosen for this server is the right choice over a network transport.
- A homemade MCP server is an ordinary program launched as a child process by Claude Code via the stdio transport, never opening a network port.
- The double dash in the claude mcp add command separates Claude Code's own options, --transport and --env, from the command and arguments passed through to the server as is.
- A JSON schema, in the inputSchema field, declares a tool's valid arguments, but it is the server's code, never the client's, that the specification requires to validate every call it receives.
- A tool's description, not its schema, decides whether Claude picks it for a given task, exactly as with a skill.
Write, in a text file on your machine, the JSON schema of an imaginary tool with a single mandatory argument, following the example in this lesson, without registering it with Claude Code.
Every datable claim in this lesson links here to the public text behind it. A source that does not open proves nothing.
- Claude Code, Model Context Protocol documentation, local servers and stdio transport consultée le 2026-09-02