Before MCP, connecting an AI model to an external tool meant writing custom function-calling glue for every model-tool pair. Claude needed one integration, OpenAI's function calling needed another, and LangChain wrapped everything in a third layer. Model Context Protocol — an open standard Anthropic released in late 2024 and that the rest of the industry largely adopted through 2025 — replaced that mess with a single protocol: one server runs anywhere, and any MCP-compatible client can use it.
What MCP actually is
MCP is a client-server protocol that defines how an AI model host (the client) communicates with tool servers. The client sends capability requests; the server exposes what it can do; the model decides when to invoke it.
The three MCP primitives:
- Tools: Functions the model can call to take actions — fetch a URL, write a file, query a database, run a shell command.
- Resources: Data sources the model can read — files, database rows, API responses structured as readable content.
- Prompts: Reusable prompt templates the server defines and the client can inject into conversations.
In practice, most MCP servers expose only Tools — that's where the power is.
How clients use MCP in 2026
Claude Desktop reads a claude_desktop_config.json file to discover which MCP servers to start. When you open Claude Desktop, it starts the listed servers as child processes via stdio, and they appear as available tools in your conversation.
Claude Code (the CLI) supports MCP servers in its configuration — you can expose your project's build system, test runner, or custom DB queries as tools Claude Code can invoke during agentic tasks.
Cursor and other AI code editors have adopted MCP as the extension mechanism for custom tool integrations.
Building a simple MCP server in TypeScript
Install the SDK:
npm install @modelcontextprotocol/sdk
Expose one tool — fetch open GitHub issues for a repo:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
const server = new Server({ name: "github-issues", version: "1.0.0" }, {
capabilities: { tools: {} }
});
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: "get_open_issues",
description: "Fetch open issues for a GitHub repo",
inputSchema: {
type: "object",
properties: {
owner: { type: "string" },
repo: { type: "string" }
},
required: ["owner", "repo"]
}
}]
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { owner, repo } = request.params.arguments as { owner: string; repo: string };
const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/issues?state=open`);
const issues = await res.json();
return { content: [{ type: "text", text: JSON.stringify(issues.slice(0, 10), null, 2) }] };
});
const transport = new StdioServerTransport();
await server.connect(transport);
Add it to Claude Desktop's config:
{
"mcpServers": {
"github-issues": {
"command": "node",
"args": ["/path/to/your/server.js"]
}
}
}
Restart Claude Desktop — the tool is now available in every conversation.
Building in Python
pip install mcp
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
import httpx
server = Server("github-issues")
@server.list_tools()
async def list_tools():
return [Tool(name="get_open_issues", description="Fetch open GitHub issues",
inputSchema={"type": "object", "properties": {"owner": {"type": "string"}, "repo": {"type": "string"}}, "required": ["owner", "repo"]})]
@server.call_tool()
async def call_tool(name: str, arguments: dict):
owner, repo = arguments["owner"], arguments["repo"]
async with httpx.AsyncClient() as client:
r = await client.get(f"https://api.github.com/repos/{owner}/{repo}/issues?state=open")
return [TextContent(type="text", text=r.text)]
async def main():
async with stdio_server() as streams:
await server.run(*streams, server.create_initialization_options())
import asyncio; asyncio.run(main())
Comparison: MCP vs alternatives in 2026
| Approach |
Portability |
Complexity |
Language support |
IDE integration |
Client support |
Best for |
| MCP |
Excellent |
Low |
Any |
Native (Claude Code, Cursor) |
Growing fast |
Multi-client tool deployment |
| Raw function calling |
None (per-model) |
Medium |
Any |
None |
Per-model |
Single-model integrations |
| LangChain tools |
Medium |
High |
Python-heavy |
Limited |
LangChain ecosystem |
Python ML pipelines |
| OpenAI Assistants API |
None |
Medium |
Any |
Limited |
OpenAI only |
OpenAI-only deployments |
Common mistakes to avoid
Blocking the stdio transport. MCP servers communicate over stdio by default — any console.log in Node.js or print() in Python corrupts the protocol stream. Use stderr for debugging output only.
Exposing too many tools at once. Models perform better with fewer, well-described tools than with dozens of poorly described ones. Start with what you actually need.
Not validating tool arguments. The model generates the arguments — treat them as untrusted input. Validate before passing to downstream APIs or file system operations.
FAQ
Do I need to know Rust to build MCP servers?
No — the official SDKs are TypeScript and Python. Rust MCP servers exist for performance-critical use cases but aren't required.
Does MCP work with OpenAI models?
OpenAI adopted MCP in 2025 — ChatGPT desktop and the Responses API both support MCP servers. Your server works across Claude, ChatGPT, and other adopters without changes.
Can MCP servers access the internet?
Yes — an MCP server is just a process; it can make HTTP requests, query databases, and access local files within its own permissions. The model doesn't constrain what the server does; you do.
Where to go next
For more developer AI tooling see how to use Claude for coding in 2026, best AI agent security tools in 2026, and best AI coding assistants in 2026.