Deep dive into the technical architecture of the Model Context Protocol. Learn how clients, servers, and transports work together.
MCP follows a client-server architecture where clients (like MCPCodex) connect to specialized servers that provide specific capabilities. All communication happens over JSON-RPC 2.0 protocol.
Applications that need AI capabilities. They connect to MCP servers to access tools and resources.
Specialized services that expose specific capabilities to AI models through standardized interfaces.
Communication channels between clients and servers, supporting various connection methods.
MCP is built on JSON-RPC 2.0, providing a standardized way for clients and servers to communicate. Here's the initialization handshake:
{
"jsonrpc": "2.0",
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
},
"prompts": {
"listChanged": true
},
"logging": {}
},
"clientInfo": {
"name": "MCPCodex",
"version": "2.0.0"
}
},
"id": 1
}
Functions that AI models can call to perform actions or retrieve information.
tools/list
- List available toolstools/call
- Execute a toolData sources that AI models can read from, like files or database entries.
resources/list
- List resourcesresources/read
- Read resource contentReusable prompt templates with parameters for common AI interactions.
prompts/list
- Available promptsprompts/get
- Get prompt templateStructured logging and monitoring for debugging and observability.
logging/setLevel
- Set log levelMCP servers are the backbone of the protocol. They expose domain-specific functionality through a standardized interface. Here's how to implement a custom MCP server:
// MCP Server Implementation
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
class CodeAnalysisServer {
constructor() {
this.server = new Server(
{
name: "code-analysis-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
resources: {},
logging: {},
},
}
);
this.setupHandlers();
}
setupHandlers() {
// Handle tool calls
this.server.setRequestHandler('tools/call', async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case 'analyze_code':
return await this.analyzeCode(args.file_path, args.analysis_type);
case 'suggest_improvements':
return await this.suggestImprovements(args.code, args.language);
default:
throw new Error(`Unknown tool: ${name}`);
}
});
// Handle resource requests
this.server.setRequestHandler('resources/read', async (request) => {
const { uri } = request.params;
return await this.readResource(uri);
});
// List available tools
this.server.setRequestHandler('tools/list', async () => {
return {
tools: [
{
name: 'analyze_code',
description: 'Analyze code for issues and improvements',
inputSchema: {
type: 'object',
properties: {
file_path: { type: 'string' },
analysis_type: {
type: 'string',
enum: ['security', 'performance', 'style', 'bugs']
}
},
required: ['file_path', 'analysis_type']
}
}
]
};
});
}
async analyzeCode(filePath, analysisType) {
// Implementation details...
return {
content: [
{
type: 'text',
text: `Analysis results for ${filePath}`
}
]
};
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
}
}
const server = new CodeAnalysisServer();
server.run().catch(console.error);
MCP clients orchestrate multiple servers to provide comprehensive AI capabilities. Here's how MCPCodex implements its client architecture:
// MCP Client Implementation
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
class MCPCodexClient {
constructor() {
this.servers = new Map();
}
async connectToServer(serverName, command, args = []) {
try {
// Create transport for server communication
const transport = new StdioClientTransport({
command,
args
});
// Create client instance
const client = new Client(
{
name: "mcpcodex-client",
version: "2.0.0"
},
{
capabilities: {
roots: {
listChanged: true
},
sampling: {}
}
}
);
// Connect to server
await client.connect(transport);
// Initialize the connection
await client.initialize();
// Store the client
this.servers.set(serverName, client);
console.log(`Connected to ${serverName}`);
return client;
} catch (error) {
console.error(`Failed to connect to ${serverName}:`, error);
throw error;
}
}
async callTool(serverName, toolName, args) {
const client = this.servers.get(serverName);
if (!client) {
throw new Error(`Server ${serverName} not connected`);
}
try {
const result = await client.request(
{
method: 'tools/call',
params: {
name: toolName,
arguments: args
}
}
);
return result;
} catch (error) {
console.error(`Tool call failed:`, error);
throw error;
}
}
async listTools(serverName) {
const client = this.servers.get(serverName);
if (!client) {
throw new Error(`Server ${serverName} not connected`);
}
const response = await client.request({
method: 'tools/list'
});
return response.tools;
}
}
// Usage example
const mcpClient = new MCPCodexClient();
// Connect to multiple servers
await mcpClient.connectToServer('filesystem', 'mcp-server-filesystem');
await mcpClient.connectToServer('git', 'mcp-server-git');
await mcpClient.connectToServer('code-analysis', 'node', ['./code-analysis-server.js']);
// Use tools from different servers
const fileContent = await mcpClient.callTool('filesystem', 'read_file', {
path: 'src/components/UserProfile.tsx'
});
const analysis = await mcpClient.callTool('code-analysis', 'analyze_code', {
file_path: 'src/components/UserProfile.tsx',
analysis_type: 'security'
});
Understanding the message flow between clients and servers is crucial for debugging and optimizing MCP applications:
// MCP Message Flow Example
// 1. Client connects to server
{
"jsonrpc": "2.0",
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": { "tools": {} },
"clientInfo": { "name": "MCPCodex", "version": "2.0.0" }
},
"id": 1
}
// 2. Server responds with capabilities
{
"jsonrpc": "2.0",
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": { "listChanged": true },
"resources": { "subscribe": true },
"logging": {}
},
"serverInfo": { "name": "code-server", "version": "1.0.0" }
},
"id": 1
}
// 3. Client requests available tools
{
"jsonrpc": "2.0",
"method": "tools/list",
"id": 2
}
// 4. Server lists available tools
{
"jsonrpc": "2.0",
"result": {
"tools": [
{
"name": "generate_component",
"description": "Generate React component",
"inputSchema": {
"type": "object",
"properties": {
"name": { "type": "string" },
"props": { "type": "array" }
}
}
}
]
},
"id": 2
}
// 5. Client calls a tool
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "generate_component",
"arguments": {
"name": "UserCard",
"props": ["user", "onEdit", "onDelete"]
}
},
"id": 3
}
// 6. Server returns tool result
{
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": "Generated React component UserCard with props: user, onEdit, onDelete"
},
{
"type": "resource",
"resource": {
"uri": "file://src/components/UserCard.tsx",
"mimeType": "text/typescript"
},
"text": "import React from 'react';\n\ninterface UserCardProps {..."
}
]
},
"id": 3
}
The default transport for local MCP servers. Simple and efficient for single-machine deployments.
// Server runs as subprocess
spawn('node', ['server.js'])
Network-based transport for distributed deployments and web applications.
// Remote server connection
connect('wss://api.example.com/mcp')
Implement custom transport layers for specialized communication needs.
Built-in security features for all transport types.
Get help with implementing your own MCP servers and clients.