In the previous Feature Gating chapter, we learned how to turn specific features on and off.
Up until now, we have built hard-coded tools: FileEditTool for files, BashTool for the terminal, and WebSearchTool for the internet.
But what if you want to connect claudeCode to your Postgres database? Or your Slack workspace? Or a custom Internal API?
Building a new hard-coded tool for every single service in the world is impossible. We need a "Universal Adapter."
Enter the Model Context Protocol (MCP).
Think of claudeCode as a computer.
You can plug any compatible device (Server) into the USB port, and the computer instantly knows how to use it.
MCP allows claudeCode to connect to external data sources and tools without us having to write new code inside the application. We simply "plug in" an MCP Server, and the AI gains new superpowers immediately.
Imagine you have a local SQLite database named customers.db. You want to ask the AI:
"How many users signed up last week?"
Without MCP, the AI cannot see inside that binary database file. With MCP, you connect a "SQLite MCP Server."
claudeCode acts as the MCP Client. It is the host. It is responsible for finding available servers and sending messages to them.
This is a small program running separately (on your computer or a remote server). It defines:
How do the Client and Server talk?
In claudeCode, we don't "write" MCP tools manually. We define a Generic Wrapper.
When claudeCode starts, it looks at your configuration. If you have an MCP server configured, claudeCode asks it: "What tools do you have?"
If the server has a tool named query_db, claudeCode dynamically creates a tool definition for it.
Users typically provide a config file to tell claudeCode where the servers are.
{
"mcpServers": {
"sqlite": {
"command": "uvx",
"args": ["mcp-server-sqlite", "--db-path", "./customers.db"]
}
}
}
Once connected, the AI sees a new tool in its list.
sqlite_query{ sql: string }
The AI uses this tool exactly like it uses BashTool, but the execution happens outside of claudeCode.
The implementation relies on a Generic MCP Tool. This is a chameleon. It pretends to be whatever tool the external server says it is.
When the AI calls an MCP tool, claudeCode acts as a messenger.
sqlite_query with SELECT * FROM users."Here is the flow:
The core logic is in tools/MCPTool/MCPTool.ts.
Unlike specific tools (like FileEditTool) that have strict schemas, the MCPTool must be flexible because we don't know what the external server requires until runtime.
// tools/MCPTool/MCPTool.ts
import { buildTool } from '../../Tool.js'
import { z } from 'zod/v4'
export const MCPTool = buildTool({
isMcp: true, // Flag to treat this differently
name: 'mcp', // Placeholder name
// We accept ANY input object because the schema defines itself dynamically
inputSchema: lazySchema(() => z.object({}).passthrough()),
// This function is overridden at runtime by the specific MCP Client
async call() {
return { data: '' }
}
});
Explanation: z.object({}).passthrough() means "accept any arguments." This is necessary because one MCP tool might need a sql string, while another might need a channel_id.
We need a way to send messages. Here is a simplified look at the WebSocketTransport in utils/mcpWebSocketTransport.ts.
// utils/mcpWebSocketTransport.ts
export class WebSocketTransport {
// 1. Connect to the socket
constructor(private ws: WebSocketLike) {
// Listen for incoming messages
this.ws.on('message', (data) => {
const message = JSON.parse(data);
this.onmessage?.(message); // Notify the system
});
}
// 2. Send outgoing messages
async send(message: JSONRPCMessage): Promise<void> {
const json = JSON.stringify(message);
this.ws.send(json);
}
}
Explanation: This is pure plumbing. It takes a JavaScript object, turns it into a text string (JSON.stringify), and shoots it over the wire. It listens for replies and passes them back up.
We need to show the user what tools are available. We use the Ink UI Framework to render the list.
// components/mcp/MCPToolListView.tsx
export function MCPToolListView({ server }) {
// Get all tools from the Global State
const mcpTools = useAppState(s => s.mcp.tools);
// Filter to show only tools for this server
const serverTools = filterToolsByServer(mcpTools, server.name);
return (
<Dialog title={`Tools for ${server.name}`}>
{/* List them in a Select menu */}
<Select
options={serverTools.map(t => ({ label: t.name }))}
onSelect={/* Handle selection */}
/>
</Dialog>
);
}
Explanation: This component reads the State Management store to find loaded tools and displays them so the user can inspect what capabilities the AI currently has.
Because MCP tools connect to the outside world, they are subject to the Permission & Security System.
When an external server registers a tool, it can flag itself as:
claudeCode uses these flags to decide whether to prompt the user for confirmation.
MCP is the future of extensibility for claudeCode.
You have learned that the Model Context Protocol (MCP) acts as a universal connector. Instead of writing code to add new features, we can plug in external servers that provide tools and data. claudeCode uses a generic wrapper and a transport layer (WebSocket or Stdio) to communicate with these servers seamlessly.
Now that we have a way to plug in external capabilities, what if we want to plug in another AI entirely?
Generated by Code IQ