Deep dive into the Model Context Protocol (MCP), its architecture, and how to create clients and servers for AI applications with practical examples, including integration with Claude and other LLMs.
The Model Context Protocol (MCP) represents a significant advancement in AI application development, providing a standardized way for AI models to interact with external tools and data sources. Originally developed by Anthropic for Claude, MCP has evolved into a robust protocol that enables seamless integration between AI models and external tools. This comprehensive guide explores MCP's architecture, components, and includes practical examples of implementing MCP clients and servers, with a special focus on integration with Claude and other language models.
MCP follows a client-host-server architecture that enables seamless integration between AI models and external tools. This architecture, as documented in Anthropic's official documentation, is designed to be flexible, secure, and scalable, making it ideal for modern AI applications.
Before implementing MCP clients and servers, you need to set up your development environment with the necessary tools and dependencies. This section follows Anthropic's recommended setup process.
# Create a new TypeScript project
npm init -y
npm install typescript @types/node --save-dev
# Install MCP dependencies
npm install @anthropic-ai/sdk @mcp/client @mcp/server @mcp/core
# Initialize TypeScript configuration
npx tsc --init
# Create project structure
mkdir src
mkdir src/client
mkdir src/server
mkdir src/tools
Let's create a comprehensive MCP client that integrates with Claude and handles various interactions with the server, including tool execution, resource access, and error handling.
import { MCPClient, ClientOptions, ToolResponse } from '@mcp/client';
import { Claude } from '@anthropic-ai/sdk';
interface AIClientOptions {
version: string;
capabilities: string[];
timeout?: number;
claudeApiKey?: string;
}
class ClaudeAIClient {
private client: MCPClient;
private claude: Claude;
private connected: boolean = false;
constructor(options: AIClientOptions) {
const clientOptions: ClientOptions = {
version: options.version,
capabilities: options.capabilities,
timeout: options.timeout || 30000,
onError: this.handleError.bind(this),
onDisconnect: this.handleDisconnect.bind(this)
};
this.client = new MCPClient(clientOptions);
this.claude = new Claude({
apiKey: options.claudeApiKey
});
}
async connect(serverUrl: string): Promise<void> {
try {
await this.client.connect(serverUrl);
await this.client.initialize();
this.connected = true;
console.log('Successfully connected to MCP server');
} catch (error) {
console.error('Failed to connect:', error);
throw error;
}
}
async executeTool<T>(name: string, params: any): Promise<ToolResponse<T>> {
if (!this.connected) {
throw new Error('Client not connected');
}
try {
// First, validate with Claude
const validation = await this.claude.validateToolCall({
tool: name,
parameters: params
});
if (!validation.isValid) {
throw new Error(`Invalid tool call: ${validation.error}`);
}
// Execute the tool
return await this.client.invoke('tool', {
name,
parameters: params
});
} catch (error) {
console.error(`Tool execution failed: ${name}`, error);
throw error;
}
}
async processWithClaude(prompt: string, tools: string[]): Promise<any> {
try {
const message = await this.claude.messages.create({
model: 'claude-3-opus-20240229',
max_tokens: 1000,
messages: [{
role: 'user',
content: prompt
}],
tools: tools.map(tool => this.client.getToolDefinition(tool))
});
return message.content;
} catch (error) {
console.error('Claude processing failed:', error);
throw error;
}
}
private handleError(error: Error): void {
console.error('MCP client error:', error);
// Implement retry logic or error reporting
}
private handleDisconnect(): void {
this.connected = false;
console.log('Disconnected from MCP server');
// Implement reconnection logic
}
}
// Usage example
async function main() {
const client = new ClaudeAIClient({
version: '2.1',
capabilities: ['sampling', 'tools', 'resources'],
timeout: 60000,
claudeApiKey: process.env.CLAUDE_API_KEY
});
try {
await client.connect('ws://localhost:3000');
// Execute a tool directly
const searchResult = await client.executeTool('search', {
query: 'AI protocols',
filters: { type: 'technical' }
});
// Process with Claude
const claudeResult = await client.processWithClaude(
'Please analyze this search result and provide insights.',
['search', 'analyze']
);
console.log('Results:', { searchResult, claudeResult });
} catch (error) {
console.error('Operation failed:', error);
}
}
Now let's create a full-featured MCP server that provides various tools and resources while implementing proper error handling, security measures, and Claude integration.
import { MCPServer, ServerOptions, ToolContext } from '@mcp/server';
import { Claude } from '@anthropic-ai/sdk';
import { validateInput, sanitizeOutput } from './utils';
interface SearchParams {
query: string;
filters?: Record<string, any>;
}
class SearchTool {
private claude: Claude;
constructor(apiKey: string) {
this.claude = new Claude({ apiKey });
}
async execute(params: SearchParams, context: ToolContext) {
// Validate input
validateInput(params);
// Enhance search with Claude
const enhancedParams = await this.enhanceSearchParams(params);
// Implement search logic
const results = await this.performSearch(enhancedParams);
// Analyze results with Claude
const analyzedResults = await this.analyzeResults(results);
// Sanitize output
return sanitizeOutput(analyzedResults);
}
private async enhanceSearchParams(params: SearchParams) {
const enhancement = await this.claude.messages.create({
model: 'claude-3-opus-20240229',
max_tokens: 500,
messages: [{
role: 'user',
content: `Enhance this search query: ${params.query}`
}]
});
return {
...params,
enhancedQuery: enhancement.content
};
}
private async analyzeResults(results: any[]) {
const analysis = await this.claude.messages.create({
model: 'claude-3-opus-20240229',
max_tokens: 1000,
messages: [{
role: 'user',
content: `Analyze these search results: ${JSON.stringify(results)}`
}]
});
return {
results,
analysis: analysis.content
};
}
private async performSearch(params: SearchParams) {
// Implement actual search functionality
return [
{ title: `Result for: ${params.query}`, relevance: 0.95 }
];
}
}
class AdvancedAIServer {
private server: MCPServer;
private claude: Claude;
constructor(claudeApiKey: string) {
const serverOptions: ServerOptions = {
version: '2.1',
tools: {
search: new SearchTool(claudeApiKey)
},
security: {
allowedOrigins: ['https://trusted-domain.com'],
maxRequestSize: 1024 * 1024 // 1MB
},
logging: {
level: 'info',
format: 'json'
}
};
this.server = new MCPServer(serverOptions);
this.claude = new Claude({ apiKey: claudeApiKey });
this.setupEventHandlers();
}
private setupEventHandlers() {
this.server.on('client.connect', this.handleClientConnect.bind(this));
this.server.on('client.disconnect', this.handleClientDisconnect.bind(this));
this.server.on('error', this.handleError.bind(this));
}
private handleClientConnect(clientId: string) {
console.log(`Client connected: ${clientId}`);
}
private handleClientDisconnect(clientId: string) {
console.log(`Client disconnected: ${clientId}`);
}
private handleError(error: Error) {
console.error('Server error:', error);
}
async start(port: number) {
try {
await this.server.listen(port);
console.log(`MCP Server listening on port ${port}`);
} catch (error) {
console.error('Failed to start server:', error);
throw error;
}
}
async stop() {
await this.server.close();
console.log('Server stopped');
}
}
// Usage example
async function main() {
const server = new AdvancedAIServer(process.env.CLAUDE_API_KEY!);
try {
await server.start(3000);
} catch (error) {
console.error('Server startup failed:', error);
process.exit(1);
}
}
process.on('SIGTERM', () => {
server.stop();
});
When working with Claude through MCP, there are several important considerations and best practices to follow.
MCP, especially when integrated with Claude, provides a robust foundation for building AI-powered applications that can seamlessly interact with external tools and data sources. By following the architecture patterns, security practices, and optimization techniques outlined in this guide, developers can create powerful and maintainable AI applications that leverage the full potential of large language models. As the AI landscape continues to evolve, MCP's standardized approach to AI integration will become increasingly valuable for building the next generation of intelligent applications.
Let's discuss how we can help you apply these solutions to your business challenges.