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.
What’s covered#
- The N×M problem MCP exists to kill, and the hub-and-spoke model that replaces it.
- The protocol itself: hosts, clients, servers, and the three primitives (tools, resources, prompts).
- How a tool call actually flows over JSON-RPC, transport by transport.
- When MCP pays for itself versus when plain function calling is the right call.
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:
graph LR
subgraph "Without MCP — N×M integrations"
direction LR
M1[GPT-4o] --- T1[GitHub]
M1 --- T2[Postgres]
M1 --- T3[Filesystem]
M2[Claude] --- T1
M2 --- T2
M2 --- T3
M3[Gemini] --- T1
M3 --- T2
M3 --- T3
endgraph LR
subgraph "With MCP — hub and spoke"
direction LR
M1[GPT-4o] --> C[MCP Client]
M2[Claude] --> C
M3[Gemini] --> C
C --> S1[GitHub MCP Server]
C --> S2[Postgres MCP Server]
C --> S3[Filesystem MCP Server]
endEach 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.
This distinction matters for security. You don’t want an AI model deciding on its own which files to read. The application determines what’s in scope; the model reads what it’s given.
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
(Claude Desktop)
participant Client as MCP Client
participant Server as MCP Server
(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 Server
from mcp.server.stdio import stdio_server
from 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
(Claude Desktop / VS Code)"]
Host -->|system prompt + tool list| Model["LLM
(Claude / GPT-4o)"]
Model -->|tool call request| Host
Host -->|tools/call JSON-RPC| Client["MCP Client"]
Client -->|stdio / HTTP| S1["MCP Server A
GitHub"]
Client -->|stdio / HTTP| S2["MCP Server B
Postgres"]
Client -->|stdio / HTTP| S3["MCP Server C
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.
The bottom line#
- MCP kills the N×M rewrite. Define a tool once as a server; any compatible client calls it, and switching model providers doesn’t touch your tool code.
- The three primitives have different control models. Tools are model-controlled, resources application-controlled, prompts user-controlled. That split is a security feature, not a detail.
- stdio for local, streamable HTTP for remote. The old SSE-only transport is deprecated; don’t build new work on it.
- The break-even is low. If more than one application will use a tool, MCP is the right abstraction from day one.
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.



