MCP: What It Is and Why It Changes How You Build AI Tools
MCP solves the N×M integration problem between AI models and tools. What it is, how it works, and why it matters before your next AI feature.
Before MCP existed, adding tools to an AI application meant writing the same glue code over and over. You had OpenAI’s function calling syntax. Anthropic had tool use with a slightly different schema. LangChain abstracted over both, but now you depended on LangChain’s versioning decisions. Every new model provider meant rewriting your tool definitions. Every new tool meant re-registering it across every AI integration you maintained.
This is the problem MCP solves. Define a tool once as an MCP server, and every MCP-capable client can call it: no per-client re-registration, and no rewrite when you switch model providers.
The N×M Integration Problem
Imagine you have 5 models (GPT-4o, Claude, Gemini, Mistral, Llama) and 10 tools (database queries, file access, GitHub, Slack, email, calendar, browser, code execution, vector search, monitoring). Without a standard, you need up to 50 separate integrations, each with its own schema, auth handling, error model, and discovery mechanism.
M models × N tools = M×N custom integrationsThis isn’t a theoretical concern. Anyone who has maintained a multi-model AI system with more than a handful of tools has felt this. The diagram below shows the explosion without a standard versus the hub-and-spoke model MCP enables:
Each model connects to one MCP client. Each tool exposes one MCP server. The protocol handles the rest.
What MCP Actually Is
MCP is an open protocol, originally designed by Anthropic and announced in November 2024. In December 2025 it was donated to the Agentic AI Foundation under the Linux Foundation, so it is now a vendor-neutral standard, not an Anthropic product. OpenAI officially adopted it in March 2025.
At its core, MCP is a JSON-RPC 2.0 based protocol for communication between AI applications (clients) and capability providers (servers). The spec defines:
- What messages look like (JSON-RPC envelopes)
- How capability discovery works (the client asks the server what it can do)
- The three primitive types a server can expose
- How transport is handled
The roles are distinct:
- Host: the application the user interacts with (Claude Desktop, Cursor, VS Code with Copilot, your own app)
- Client: Lives inside the host; manages one connection to one MCP server
- Server: A process (local or remote) that exposes tools, resources, or prompts over the protocol
A single host can run multiple clients simultaneously, each connected to a different server.
The Three Primitives
MCP servers expose capabilities via three primitive types. Understanding the distinction matters because they have different control models.
Tools
Tools are functions the model can call. This is the closest analog to OpenAI function calling. You define a name, a JSON Schema input definition, and the server handles execution and returns a result.
Example: a GitHub MCP server might expose create_pull_request, list_issues, get_file_contents. The model decides when to call them based on the conversation.
Tools are model-controlled: the AI decides when to invoke them.
Resources
Resources are data the model can read. Think of them as addressable content: files, database rows, API responses, log streams. A resource has a URI and a MIME type. The host application controls when and which resources are surfaced to the model.
Resources are application-controlled: the host decides what to expose, not the model.
Prompts
Prompts are reusable prompt templates exposed by the server. A user can invoke a prompt by name (e.g., “summarise this PR”) and the server returns a structured message array ready to send to the model. Think of them as server-managed prompt libraries that clients can discover and surface in their UI.
Prompts are user-controlled: surfaced through UI affordances for the user to invoke explicitly.
How It Works Technically
Transport Layer
MCP supports two primary transports in the current spec (2025-03-26):
stdio: The client spawns the server as a local subprocess. Messages go over stdin/stdout as newline-delimited JSON-RPC. This is the standard for local tools: filesystem, local database connections, CLI wrappers. Simple, no networking required.
Streamable HTTP: The server runs as an independent process. The client sends JSON-RPC over HTTP POST requests. The server can optionally respond using Server-Sent Events (SSE) for streaming multiple messages back. This replaces the older SSE-only transport from the original 2024-11-05 spec, which is now deprecated.
For local developer tooling, stdio is the default. For remote servers, shared infrastructure, or multi-tenant deployments, Streamable HTTP is the right choice.
Capability Discovery
When a client connects to a server, it calls initialize with the client’s protocol version and capabilities. The server responds with its own version and capabilities. The client then calls tools/list, resources/list, and prompts/list to discover what the server exposes. This discovery is dynamic: a server can change its tool list at runtime and notify connected clients.
A Tool Call, End to End
Here is what happens when an AI model calls a tool over MCP:
sequenceDiagram participant User participant Host as Host App<br/>(Claude Desktop) participant Client as MCP Client participant Server as MCP Server<br/>(GitHub) participant Tool as GitHub API
User->>Host: "List open PRs in repo X" Host->>Client: Forward to model with tool list Client->>Host: Model requests tools/list Client->>Server: tools/list Server-->>Client: [{name: "list_pull_requests", ...}] Host->>Host: Model generates tool call Client->>Server: tools/call {name: "list_pull_requests", arguments: {repo: "X"}} Server->>Tool: GET /repos/X/pulls Tool-->>Server: PR list JSON Server-->>Client: {content: [{type: "text", text: "..."}]} Client->>Host: Tool result Host->>Host: Model generates final response Host-->>User: "There are 3 open PRs..."The JSON-RPC messages are straightforward. A tool call looks like this:
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_pull_requests", "arguments": { "repo": "nitin27may/my-app", "state": "open" } }}And the response:
{ "jsonrpc": "2.0", "id": 1, "result": { "content": [ { "type": "text", "text": "Found 3 open pull requests: ..." } ], "isError": false }}The messages are plain JSON-RPC, and the same envelope works over any transport.
The Ecosystem in Practice
As of early 2026, the MCP ecosystem has grown substantially. Anthropic maintains official reference servers, and there are 5,000+ community servers available.
Official servers (Anthropic-maintained):
@modelcontextprotocol/server-filesystem: local file operations with configurable path restrictions@modelcontextprotocol/server-github: issues, PRs, repo management@modelcontextprotocol/server-postgres: natural language SQL against Postgres databases@modelcontextprotocol/server-slack: post messages, read channels, manage workspaces@modelcontextprotocol/server-google-drive: read/search Google Drive documents
Well-adopted community servers:
- Browser automation (Playwright-based)
- Jira, Linear, Notion
- AWS, Azure, GCP management
- Redis, MongoDB, SQLite
- Docker and Kubernetes operations
- Memory/knowledge graph servers
The reference implementations are at github.com/modelcontextprotocol/servers.
Client Support
MCP is only useful if the clients your team uses actually support it.
| Client | Where MCP tools work | Config |
|---|---|---|
| Claude Desktop | Everywhere in the chat UI (Anthropic’s initial target) | claude_desktop_config.json |
| Claude Code | All chat modes; acts as both client and server | In-app / CLI |
| Cursor | All chat modes; one-click setup for common servers | In-app setup |
| VS Code + GitHub Copilot | Agent mode only, not standard chat (GA in 1.102, July 2025) | mcp.json |
| JetBrains IDEs | Via the AI Assistant plugin | Plugin settings |
| Windsurf | Native MCP client | In-app setup |
VS Code with Copilot is the most enterprise-ready option: it ships OAuth authentication and Settings Sync. The practical implication: on VS Code with Copilot, MCP tools work in agent mode; on Claude Desktop or Cursor, they’re available everywhere in the chat UI.
MCP vs Plain Function Calling
This is where people overthink it. Function calling and MCP are not competing approaches. They operate at different layers.
Function calling is a model-level capability. The model receives a JSON Schema list of functions, decides which to call, generates a structured call object, and your application handles execution. It is tightly coupled to your application code. The tool list is defined and managed inside your app.
MCP adds a network-addressable layer on top. The tool definitions live in a separate server process. Any compatible client can discover and call them without changes to the server. When you switch models, your tools keep working.
When function calling alone is the right choice:
- Small prototype with 2-3 tools that only one application uses
- Tight latency requirements where spawning an MCP subprocess or making an extra HTTP round-trip matters
- You’re using a model API directly and have no MCP client layer
- Simple, stateless operations where reusability across clients isn’t relevant
When MCP pays for itself:
- Multiple AI applications or tools that need the same capabilities (your database tools shouldn’t be rewritten per client)
- Tools that need to be tested, versioned, and deployed independently
- You’re building tools for others to consume (internal platform teams, open source)
- You need to support multiple model providers without changing tool definitions
- The tool server requires its own auth, dependencies, or environment
The break-even point is lower than you might expect. If you’re writing tool code that more than one application will use, MCP gives you the right abstraction.
Building Your First MCP Server
MCP has official SDKs for TypeScript and Python, with community SDKs for .NET, Rust, Go, and others. A minimal Python server with one tool looks like this:
from mcp.server import Serverfrom mcp.server.stdio import stdio_serverfrom mcp import types
app = Server("my-server")
@app.list_tools()async def list_tools() -> list[types.Tool]: return [ types.Tool( name="get_weather", description="Get current weather for a city", inputSchema={ "type": "object", "properties": { "city": {"type": "string", "description": "City name"} }, "required": ["city"] } ) ]
@app.call_tool()async def call_tool(name: str, arguments: dict) -> list[types.TextContent]: if name == "get_weather": city = arguments["city"] # your actual implementation here return [types.TextContent(type="text", text=f"Weather for {city}: 22°C, partly cloudy")] raise ValueError(f"Unknown tool: {name}")
async def main(): async with stdio_server() as (read_stream, write_stream): await app.run(read_stream, write_stream, app.create_initialization_options())
if __name__ == "__main__": import asyncio asyncio.run(main())The Full Interaction: Putting It Together
Here is how all the components fit together in a production setup, from the user’s query through to the result:
graph TD User([User]) -->|query| Host["Host Application<br/>(Claude Desktop / VS Code)"] Host -->|system prompt + tool list| Model["LLM<br/>(Claude / GPT-4o)"] Model -->|tool call request| Host Host -->|tools/call JSON-RPC| Client["MCP Client"]
Client -->|stdio / HTTP| S1["MCP Server A<br/>GitHub"] Client -->|stdio / HTTP| S2["MCP Server B<br/>Postgres"] Client -->|stdio / HTTP| S3["MCP Server C<br/>Filesystem"]
S1 -->|GitHub API| GH[(GitHub)] S2 -->|SQL| DB[(PostgreSQL)] S3 -->|read/write| FS[(Local Files)]
S1 -->|tool result| Client S2 -->|tool result| Client S3 -->|tool result| Client
Client -->|tool result| Host Host -->|result + context| Model Model -->|final response| Host Host -->|answer| UserEach MCP server is deployed and versioned on its own, and any host that speaks the protocol can reuse it.
Where This Is Going
MCP was donated to the Linux Foundation in December 2025, which signals Anthropic’s intent: this should be infrastructure, not a product. OpenAI adopted it in March 2025. JetBrains, Microsoft (via VS Code), and the major AI cloud providers are all shipping native support.
The ecosystem is moving fast. MCP servers for everything from databases to browser automation appear weekly. The time to understand the protocol is before your next AI feature, not after. If you’re building anything that involves an LLM calling external services, the question isn’t whether to learn MCP. It’s whether you build the right abstraction from the start or refactor to it later.
The spec lives at modelcontextprotocol.io. The reference servers are a good place to read real implementation patterns before writing your own.
Comments
Comments are GitHub discussions. Sign in with GitHub to post; reactions need no account.