While everyone debated which LLM to use, a small protocol from Anthropic focused on solving the harder problem: how agents actually connect to the world.Everyone building AI agents eventually hits the same wall. Your LLM is smart. Your tools exist. But connecting them is a mess of one-off integrations, model-specific function calling schemas, and fragile JSON parsers that break every time the API changes.Before MCP, this is what agent tool integration looked like:Write a tool definition in OpenAI's function calling formatRewrite it in Anthropic's tool use format when you switch modelsWrite it again in LangChain's tool schema when you adopt a frameworkMaintain three versions of the same tool indefinitelyWatch all three break when any of the three APIs updateThis is not an engineering problem. It is an interoperability problem. And interoperability problems are solved by standards not by better code. That is what MCP is.MCP is to AI agents what USB was to hardware peripherals. Before USB, every device needed its own port, its own driver, its own cable. After USB, one standard connected everything. MCP is that standard for agent tool use.What MCP actually isModel Context Protocol (MCP) is an open standard introduced by Anthropic in late 2024. It defines a universal interface for connecting LLMs to external tools, data sources, and services. The architecture is deliberately simple:| Component | Role | Analogy ||----|----|----|| MCP Host | The application running the LLM (Claude, your app) | The browser || MCP Client | Manages connections to MCP servers | The browser's network layer || MCP Server | Exposes tools, resources, and prompts | A web server / API || Transport Layer | stdio or SSE how client and server communicate | HTTP |Your agent (the MCP client) connects to one or many MCP servers. Each server exposes a set of tools, functions the LLM can call. The LLM never needs to know how the tool is implemented. It just sees a name, a description, and a JSON schema for the inputs. This separation of concerns is the entire point. Your tool logic lives in the MCP server. Your LLM logic lives in the host. They are completely decoupled.Building your first MCP server in PythonLet's make this concrete. Here is a minimal MCP server that exposes two tools, a web search tool and a file reader. Any MCP-compatible agent can use these tools immediately without any additional integration work:# pip install mcpfrom mcp.server import Serverfrom mcp.server.stdio import stdio_serverfrom mcp.types import Tool, TextContentimport asyncioimport httpximport json# ── Create the MCP server ────────────────────────────────────────────────app = Server("research-tools")# ── Define tools ─────────────────────────────────────────────────────────@app.list_tools()async def list_tools() -> list[Tool]: return [ Tool( name="web_search", description="Search the web and return top results. Use for current information.", inputSchema={ "type": "object", "properties": { "query": {"type": "string", "description": "Search query"}, "max_results": {"type": "integer", "default": 5} }, "required": ["query"] } ), Tool( name="read_file", description="Read content from a local file path.", inputSchema={ "type": "object", "properties": { "path": {"type": "string", "description": "Absolute file path"} }, "required": ["path"] } ) ]# ── Handle tool calls ─────────────────────────────────────────────────────@app.call_tool()async def call_tool(name: str, arguments: dict) -> list[TextContent]: if name == "web_search": query = arguments["query"] max_results = arguments.get("max_results", 5) # In production: call your search API here results = await search_web(query, max_results) return [TextContent(type="text", text=json.dumps(results))] elif name == "read_file": path = arguments["path"] with open(path, "r") as f: content = f.read() return [TextContent(type="text", text=content)] raise ValueError(f"Unknown tool: {name}")# ── Run the server ────────────────────────────────────────────────────────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__": asyncio.run(main())That is your entire MCP server. Any agent that speaks MCP Claude Desktop, a LangChain agent, a custom Python agent can now use these tools with zero additional integration code.Connecting an agent to your MCP serverNow let's connect an AI agent to the server we just built. Here's a complete agent that discovers available tools from the MCP server and uses them autonomously:import asynciofrom mcp import ClientSession, StdioServerParametersfrom mcp.client.stdio import stdio_clientimport anthropicCLAUDE = anthropic.Anthropic()async def run_agent(user_query: str): """ Connect to MCP server, discover tools, run agent loop. The agent autonomously decides which tools to call. """ server_params = StdioServerParameters( command="python", args=["research_server.py"], # our MCP server from above ) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: # ── Discover tools from the server ────────────────────────── await session.initialize() tools_response = await session.list_tools() # Convert MCP tool definitions to Anthropic tool format tools = [ { "name": t.name, "description": t.description, "input_schema": t.inputSchema } for t in tools_response.tools ] # ── Agentic loop ───────────────────────────────────────────── messages = [{"role": "user", "content": user_query}] while True: response = CLAUDE.messages.create( model="claude-sonnet-4-6", max_tokens=4096, tools=tools, messages=messages ) # If no tool use — agent is done if response.stop_reason == "end_turn": final = next(b.text for b in response.content if hasattr(b, "text")) print(f"Agent: {final}") break # Execute tool calls via MCP tool_results = [] for block in response.content: if block.type == "tool_use": print(f" → Calling tool: {block.name}({block.input})") result = await session.call_tool(block.name, block.input) tool_results.append({ "type": "tool_result", "tool_use_id": block.id, "content": result.content[0].text }) # Feed results back into the conversation messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": tool_results})# Run the agentasyncio.run(run_agent("Research the latest developments in MCP adoption and summarise in 3 bullets"))Notice what is missing from this agent code: any tool implementation details. The agent discovers tools at runtime, calls them by name, and processes results. You can add 10 more tools to your MCP server and this agent code does not change at all.MCP resources and prompts: the features most people missTools are the headline feature of MCP, but the protocol also defines two other primitives that most tutorials skip entirely: Resources and Prompts.Resources structured data the LLM can readResources are read-only data sources the agent can access. Think of them as context the LLM can pull in without a tool call database schemas, documentation, configuration files:\@app.list_resources()async def list_resources(): return [ Resource( uri="file://./schema.sql", name="Database Schema", description="Current production database schema", mimeType="text/plain" ) ]@app.read_resource()async def read_resource(uri: str): if uri == "file://./schema.sql": with open("schema.sql") as f: return f.read()Prompts — reusable prompt templatesPrompts let you define parameterised templates that agents can invoke by name. This standardises how your team structures common agent tasks:@app.list_prompts()async def list_prompts(): return [ Prompt( name="summarise_research", description="Summarise research findings into executive bullets", arguments=[ PromptArgument(name="topic", description="Research topic", required=True), PromptArgument(name="depth", description="brief|detailed", required=False) ] ) ]@app.get_prompt()async def get_prompt(name: str, arguments: dict): if name == "summarise_research": topic = arguments["topic"] depth = arguments.get("depth", "brief") return GetPromptResult( messages=[{ "role": "user", "content": f"Summarise the latest research on {topic}. Depth: {depth}. Output as numbered bullets." }] )\MCP vs function callingThis is the question everyone asks. Function calling (OpenAI) and tool use (Anthropic) let LLMs call tools. MCP also lets LLMs call tools. So what is the difference?| Dimension | Function Calling / Tool Use | MCP ||----|----|----|| Scope | Single model, single request | Cross-model, persistent server || Tool definition | Inline in API request | Defined once in MCP server || Model portability | Rewrite for each model API | Same server works with all MCP hosts || Tool discovery | Manual — you define what's available | Automatic — agent lists tools at runtime || State | Stateless per request | Stateful server — tools can maintain context || Reusability | Copy-paste across projects | Deploy once, connect from anywhere || Ecosystem | Proprietary per vendor | Open standard — growing shared library |Function calling is a feature. MCP is an architecture. Function calling answers: 'how does this LLM call this tool?' MCP answers: 'how do all LLMs call all tools, consistently, forever?' These are different questions with different answers.Common mistake: treating MCP as a replacement for function calling. It is not. Under the hood, MCP still uses your LLM's native tool calling. MCP is the layer that standardises how tools are defined, discovered, and served, not how the LLM invokes them.Production MCPPattern 1: SSE transport for remote serversStdio transport is fine for local development. In production, use Server-Sent Events (SSE) so your MCP server can run remotely and serve multiple clients:# pip install mcp[sse]from mcp.server.sse import SseServerTransportfrom starlette.applications import Starlettefrom starlette.routing import Routeimport uvicorn# Your MCP server app (same as before)mcp_app = Server("production-tools")# Wrap in SSE transportsse = SseServerTransport("/messages")async def handle_sse(request): async with sse.connect_sse(request.scope, request.receive, request._send) as streams: await mcp_app.run(streams[0], streams[1], mcp_app.create_initialization_options())# Deploy as a standard web servicestarlette_app = Starlette(routes=[ Route("/sse", endpoint=handle_sse),])if __name__ == "__main__": uvicorn.run(starlette_app, host="0.0.0.0", port=8000) # Clients connect to: http://your-server:8000/ssePattern 2: authentication and security@app.call_tool()async def call_tool(name: str, arguments: dict, context=None) -> list[TextContent]: # Extract auth token from request context token = context.request_context.meta.get("auth_token") if context else None if not token or not await verify_token(token): raise PermissionError("Unauthorised: valid token required") # Scope tool access by user role user_role = await get_role(token) if name == "delete_records" and user_role != "admin": raise PermissionError("Admin role required for delete operations") # Proceed with tool execution return await execute_tool(name, arguments)Pattern 3: tool versioning# Version your tools explicitly — agents in production break when tools change@app.list_tools()async def list_tools(): return [ Tool( name="search_v2", # versioned name description="[v2] Web search with result ranking and deduplication. Replaces search_v1 which is deprecated.", inputSchema={ ... } ), # Keep v1 running until all agents have migrated Tool( name="search_v1", description="[DEPRECATED - use search_v2] Basic web search.", inputSchema={ ... } ) ]\The MCP ecosystem in 2026MCP adoption has accelerated dramatically since its launch. Here is where the ecosystem stands:| Category | Who supports MCP | Status ||----|----|----|| LLM Hosts | Claude Desktop, Claude.ai, Cursor, Zed, Windsurf | Native support || LLM APIs | Anthropic (Claude), OpenAI (GPT-4o), Google (Gemini) | Full support || Agent Frameworks | LangChain, LlamaIndex, AutoGen, CrewAI | MCP adapters available || Pre-built Servers | GitHub, Slack, Google Drive, PostgreSQL, Stripe | Official MCP servers || Cloud Providers | AWS, GCP, Azure | MCP-compatible tool hosting || Community Registry | MCP Hub (mcp.so) | 1,000+ community servers |The pre-built server ecosystem is the real accelerant. You no longer need to write an MCP server for GitHub, Anthropic already published one. Connect it to your agent in three lines of config and your agent can read issues, create PRs, and review code immediately.The network effect is real. Every MCP server built by anyone is usable by every MCP-compatible agent built by anyone. This is the same dynamic that made npm and PyPI so powerful, a shared, composable ecosystem that compounds over time.When NOT to use MCPMCP is not always the right choice. Here is when to skip it:Simple single-tool integrations: if your agent calls exactly one API, function calling is simpler and sufficientLatency-critical paths: MCP adds serialisation overhead. For sub-100ms tool calls, direct function calling is fasterServerless/ephemeral environments: MCP servers are persistent processes. They do not fit naturally into stateless function invocationsTeams with no existing agent infrastructure: MCP has a setup cost. Build a working agent first, extract to MCP when you need reusabilityThe decision is straightforward: if you have more than one agent, or more than one model, or you plan to reuse any tool across projects, MCP saves you more time than it costs. If you have one agent calling one tool, skip it.\ConclusionThe history of software is the history of standards winning. TCP/IP won networking. REST won web APIs. USB won hardware peripherals. In each case, the standard did not win because it was the most technically sophisticated option, it won because it solved the interoperability problem everyone had and nobody wanted to keep solving individually.MCP is solving that problem for AI agents. The tool integration mess that every agent builder has been living with since 2023, the rewritten schemas, the model-specific integrations, the unmaintainable glue code, MCP makes all of that unnecessary.It is not loud about it. There is no viral benchmark, no flashy demo. It is a protocol specification and a Python library. But the adoption curve is steep, the ecosystem is compounding, and every major LLM provider has signed on. Quietly and methodically, MCP is becoming the standard. The only question is how long it takes you to notice.Build your next agent with MCP. Not because it is trendy, because in six months, every agent tutorial will assume you already know it.\