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 — and understanding it properly changes how you think about AI-integrated systems.
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 is not just 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.
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:
(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
}
}Clean, inspectable, transport-agnostic.
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.
Claude Desktop: Native MCP support from the start — this was Anthropic’s initial target. Configure servers in claude_desktop_config.json.
Claude Code: Full MCP support; can act as both client and server. Tools are available across all chat modes.
Cursor: MCP support across all chat modes, one-click setup for common servers.
VS Code with GitHub Copilot: GA in VS Code 1.102 (July 2025). Configure via mcp.json. Tools are available in Copilot’s Agent mode only — not in standard chat. The implementation includes OAuth authentication and Settings Sync, making it the most enterprise-ready option.
JetBrains IDEs: MCP server support built into the AI Assistant plugin.
Windsurf: Native MCP client support.
The practical implication: if your team uses VS Code with Copilot, MCP tools work in agent mode. If you build on Claude Desktop or Cursor, MCP tools are 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 walkthrough — including a real-world server with auth, resources, and Streamable HTTP transport — is covered in Build Your Own MCP Server in C#. If you want to go further and build an MCP server that connects to an existing REST API in Python, see Build Your Own MCP Server in Python.
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:
(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| User
Each MCP server is independently deployed, independently versioned, and reusable across any host that speaks the protocol.
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 are appearing 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 want to 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.

