The Model Context Protocol (MCP) has rapidly emerged as the "USB-C for AI," providing a standardized way to connect Large Language Models (LLMs) with external data sources and tools. While early implementations often relied on local Standard Input/Output (stdio) for simplicity, the transition to HTTP transports is a critical evolution for production environments, distributed systems, and multi-tenant architectures.
This guide explores the mechanics of MCP over HTTP, detailing the underlying JSON-RPC structure, security implications, and code examples for implementing both client and server sides.
Why Move Beyond Stdio?
The standard stdio transport is ideal for local development and single-user CLI tools. However, it faces significant limitations in scalable scenarios:
- Isolation: Stdio requires the model and the tool to run on the same machine and process tree.
- Scalability: It does not natively support load balancing or horizontal scaling.
- Security: Direct process spawning is less secure than networked services with proper authentication layers.
HTTP (specifically HTTP/1.1 or HTTP/2) offers stateless communication, robust middleware support, and global accessibility, making it the natural choice for exposing MCP servers to remote LLM instances.
The Architecture: JSON-RPC over HTTP
MCP is not just a transport layer; it is a semantic protocol. When deployed over HTTP, MCP utilizes JSON-RPC 2.0 as the message format. Every interaction consists of a JSON object containing a method, parameters, and an ID.
Key Methods
The core capabilities exposed by an MCP server include:
tools/list: Discover available tools.tools/call: Invoke a specific tool with arguments.resources/list: Access available data resources.
Implementation Examples
1. The Server-Side Handler (Node.js/Express Example)
Below is a minimal example of how an MCP server handles an incoming HTTP POST request. Note that MCP over HTTP typically uses a single endpoint (e.g., /mcp) that routes based on the method field in the JSON body.
const express = require('express');
const app = express();
app.use(express.json());
app.post('/mcp', (req, res) => {
const { method, params, id } = req.body;
// Basic routing based on MCP methods
switch (method) {
case 'tools/list':
res.json({
jsonrpc: '2.0',
id,
result: {
tools: [
{
name: 'get_weather',
description: 'Get current weather data',
inputSchema: {
type: 'object',
properties: {
city: { type: 'string' }
},
required: ['city']
}
}
]
}
});
break;
case 'tools/call':
// Implement your tool logic here
const cityName = params.arguments.city;
res.json({
jsonrpc: '2.0',
id,
result: {
content: [{ type: 'text', text: `Sunny in ${cityName}, 75°F` }]
}
});
break;
default:
res.json({
jsonrpc: '2.0',
id,
error: { code: -32601, message: 'Method not found' }
});
}
});
app.listen(3000, () => console.log('MCP Server running on port 3000'));
2. The Client-Side Request
The LLM application (or its middleware) acts as the MCP client. It sends a POST request with the JSON-RPC payload. It is crucial to handle timeouts and async responses correctly, as tool calls can be lengthy.
const fetch = require('node-fetch');
async function callMCPTool(toolName, args) {
const response = await fetch('http://localhost:3000/mcp', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
method: 'tools/call',
params: {
name: toolName,
arguments: args
},
id: '12345'
})
});
const data = await response.json();
if (data.error) {
throw new Error(data.error.message);
}
return data.result;
}
// Usage
callMCPTool('get_weather', { city: 'New York' }).then(console.log);
Security Considerations
Exposing MCP tools over HTTP introduces attack surfaces that do not exist in local stdio environments. Developers must implement strict security controls:
- Authentication: Always require API keys, OAuth 2.0 tokens, or mTLS. Never expose an MCP server on the public internet without authentication.
- Authorization: Ensure the LLM context does not allow privilege escalation. The server should verify that the requesting user has permission to call specific tools.
- Input Validation: Strictly validate all arguments passed to tools. Since the LLM generates the input, it may produce malformed or malicious JSON. Use schema validation (e.g., Zod, Joi) on the server side.
- Rate Limiting: Implement rate limits to prevent abuse or cost overruns associated with external API calls made by the tools.
Best Practices for Production
- Use HTTPS: Always encrypt traffic in transit.
- Statelessness: Design your MCP server to be stateless wherever possible to allow for easy horizontal scaling.
- Logging: Log all tool invocations for auditing purposes. Track which LLM instance called which tool with what parameters.
- Versioning: Include versioning in your URL or headers (e.g.,
/mcp/v1) to manage breaking changes to tool schemas.
Conclusion
Transitioning MCP from stdio to HTTP is a necessary step for building robust, scalable AI applications. By leveraging the standard JSON-RPC 2.0 format over secure HTTP endpoints, developers can create modular, reusable tool ecosystems that can be shared across different LLM providers and deployment environments. As the MCP ecosystem matures, expect to see richer features such as streaming responses and complex resource linking becoming standard over HTTP transports.