By 2026 the Model Context Protocol (MCP) has quietly become the default way to plug Claude into the rest of your stack. If you still wire custom tool definitions into every agent, hand-roll JSON schemas and paste documentation into system prompts, you do roughly twice the work for half the result. Below: what MCP actually is, how to build a real server, how to deploy it, and how it sits next to a gateway like codexkey.
What MCP is, briefly
MCP is an open protocol from Anthropic. It defines how an AI application (the client: Claude Desktop, Claude Code, OpenCode, your own agent) talks to a provider of capabilities (the server: a filesystem, a Postgres database, a GitHub account, your company's internal API). The server exposes three kinds of primitives: tools (functions you can call), resources (data you can read) and prompts (parameterised templates). The client finds them at runtime and hands them to the model. There is no custom glue per integration and no bespoke schema per project.
Why it beats hand-written tool definitions
You can call Claude's tools parameter directly, with JSON schemas you wrote by hand. People did exactly that for two years, and it works. Across an organisation it stops scaling:
- Every app re-implements the same five tools. Filesystem, search, database, ticketing, calendar: each team ships its own version, with slightly different parameters and its own bugs.
- Tool schemas live inside application code. Changing one parameter means redeploying every consumer.
- There is no discovery. A new agent has no idea which tools the organisation already has. With MCP it simply asks the server.
- Auth and rate limits are scattered across apps. With MCP they sit on the server.
MCP turns a tool integration into a service. Build it once, run it once, let any Claude-powered client connect.
Architecture
An MCP server is a process, local or remote, that speaks the MCP wire protocol over a transport. Two transports matter in 2026:
- stdio. The server is a child process of the client. It suits local-only servers: filesystem, local git, a desktop app's own data. No network, no auth, the fastest to ship.
- Streamable HTTP. The server is a long-running HTTP service. Use it for anything multi-user or hosted: your SaaS, a shared internal API, third-party integrations. It supports auth headers and SSE streaming, and it scales horizontally like any other web service.
The client keeps a session, lists capabilities (tools/list, resources/list, prompts/list) and invokes them on demand. The model itself never speaks MCP; the harness does. From the model's side it is still calling ordinary tools.
A minimal MCP server in TypeScript
Install the official SDK:
npm install @modelcontextprotocol/sdk zod
Here is a server with one tool (add) and one resource (config://app):
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "demo-server",
version: "1.0.0",
});
server.tool(
"add",
"Add two numbers and return the sum.",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
}),
);
server.resource(
"config",
"config://app",
async (uri) => ({
contents: [{
uri: uri.href,
text: JSON.stringify({ env: "prod", region: "eu" }),
}],
}),
);
const transport = new StdioServerTransport();
await server.connect(transport);
That is a complete server that follows the MCP spec. Save it as server.ts, compile it, and any MCP-capable client can register it.
Testing the server in three clients
Three clients, three configs.
Claude Desktop. Edit claude_desktop_config.json:
{
"mcpServers": {
"demo": {
"command": "node",
"args": ["/abs/path/to/server.js"]
}
}
}
Restart Claude Desktop, and the tool shows up in the hammer menu.
Claude Code. Run claude mcp add demo node /abs/path/to/server.js. Inside a session, /mcp lists the connected servers.
OpenCode. Add this to opencode.json:
{
"mcp": {
"demo": {
"type": "local",
"command": ["node", "/abs/path/to/server.js"]
}
}
}
In all three, ask the model "what is 2 + 3?" and it will call your add tool. Welcome to MCP.
Deploying as a remote HTTP MCP server
For anything multi-user, move to Streamable HTTP. Swap the stdio transport for this:
import express from "express";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
const app = express();
app.use(express.json());
app.post("/mcp", async (req, res) => {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => crypto.randomUUID(),
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
app.listen(3000);
Put the server behind your normal load balancer, terminate TLS, add an auth middleware that validates a bearer token per tenant, and you have a production MCP server. Clients connect like this:
{
"mcpServers": {
"demo": {
"url": "https://mcp.example.com/mcp",
"headers": { "Authorization": "Bearer ..." }
}
}
}
Security: what people forget most often
MCP servers run with your credentials, so treat them like any other API:
- Auth per client. Issue scoped tokens. One master key shared across every consumer is a bad idea. Rotate keys.
- Tool access by caller. A tool that lists all tickets is dangerous. A tool that lists the tickets of the authenticated user is not. Carry the identity through every call.
- Rate limits at the transport. A misbehaving agent can hammer a tool in a tight loop. Cap requests per minute per token.
- Validation of every argument. Zod schemas are not optional. Tool arguments are untrusted input, because a jailbroken model can emit arbitrary JSON.
- A log entry for every call. MCP is the audit boundary between the model and your data. Record the tool name, arguments, caller and outcome.
- No secrets in resources. The model reads resources, and anything exposed that way may be echoed in a response.
Real servers worth learning from
The official Anthropic and community servers are the fastest way to pick up the idioms:
- filesystem. Sandboxed read and write access to a directory. The reference for path validation.
- postgres. Read-only SQL, with schema introspection served as resources.
- github. Issues, PRs, code search. A good example of pagination inside tool responses.
- stripe. Payments and customers. An excellent example of scoped auth and idempotent operations.
- sentry. Issues and events. Shows how to expose time-windowed data as tools rather than resources.
Read their source. The patterns carry over.
How MCP works through codexkey
A common question: does the gateway need to do anything special to support MCP servers? No. MCP is a client-side concern. The flow goes like this:
- Your client (Claude Desktop, Claude Code, OpenCode, your own agent) connects to one or more MCP servers.
- The client lists their tools and resources.
- When the user sends a message, the client makes a normal
/v1/messagesrequest to codexkey, withtools: [...]filled in from the MCP servers. - codexkey routes that request to Claude at our standard pay-per-token pricing.
- Claude returns a
tool_useblock. The client, not the gateway, runs the call against the MCP server and sends back the result.
Put differently, codexkey sits on the model side of the protocol and MCP sits on the harness side, so they do not get in each other's way. Point an MCP-aware client at codexkey's Anthropic-compatible base URL, fill in the tools from any MCP server, and the per-token price stays predictable.
Bottom line
In 2026 MCP is still the right abstraction for tool integration. Build servers once, mount them anywhere and let the harness do the wiring. Start with a stdio server for a local workflow, move to HTTP when you need several users, and keep auth, scopes and rate limits on the server, where they belong. After that you can stop reinventing tool schemas and ship.