AI Agents and MCP in Production · The Model Context Protocol (MCP)

MCP: One Protocol Instead of N×M Integrations

Why every AI app re-implemented the same integrations, and how MCP's servers, clients and transports replace them.

Create a free account to save this page, take notes and track your progress. Sign in

Scenario

Example company: three internal AI apps (a support assistant, a coding assistant and an analytics chatbot) all need the same systems: the ticket tracker, the wiki and the customer database. Each team wrote its own integration code.

What you see in production

The ticket tracker changes its API. Three teams fix three integrations over two weeks, each with slightly different bugs. A fourth team wants to add the wiki to its new agent and starts writing integration number four. Auth, pagination and error handling are implemented differently everywhere.

The naive code

# support_assistant/tools.py, coding_assistant/tools.py, analytics_bot/tools.py: three copies
def search_tickets(query):
    r = requests.get(f"{TRACKER}/api/v3/search", params={"q": query}, headers={"Authorization": TOKEN})
    return [t["title"] for t in r.json()["items"]]      # each copy parses, pages and fails differently

Reproduce it

A minimal in-process MCP-style exchange: one server, any client, JSON-RPC messages in between:

Initialize, list tools, call a tool: the MCP message flowTry it yourself
import json

class TicketServer:
    tools = [{"name": "search_tickets", "description": "Search tickets by text. Returns id, title, status.",
              "inputSchema": {"type": "object", "properties": {"query": {"type": "string"}},
                              "required": ["query"]}}]
    data = [{"id": 101, "title": "Login fails on Safari", "status": "open"},
            {"id": 102, "title": "Export to CSV is slow", "status": "closed"}]

    def handle(self, msg):
        if msg["method"] == "initialize":
            result = {"protocolVersion": "2025-11-25", "capabilities": {"tools": {}},
                      "serverInfo": {"name": "tickets", "version": "1.0"}}
        elif msg["method"] == "tools/list":
            result = {"tools": self.tools}
        elif msg["method"] == "tools/call":
            q = msg["params"]["arguments"]["query"].lower()
            hits = [t for t in self.data if q in t["title"].lower()]
            result = {"content": [{"type": "text", "text": json.dumps(hits)}], "isError": False}
        return {"jsonrpc": "2.0", "id": msg["id"], "result": result}

server = TicketServer()
for i, (method, params) in enumerate([("initialize", {"clientInfo": {"name": "support-assistant"}}),
                                      ("tools/list", {}),
                                      ("tools/call", {"name": "search_tickets", "arguments": {"query": "login"}})], 1):
    reply = server.handle({"jsonrpc": "2.0", "id": i, "method": method, "params": params})
    print(method, "->", json.dumps(reply["result"])[:110])

Output

initialize -> {"protocolVersion": "2025-11-25", "capabilities": {"tools": {}}, "serverInfo": {"name": "tickets", "version": 
tools/list -> {"tools": [{"name": "search_tickets", "description": "Search tickets by text. Returns id, title, status.", "in
tools/call -> {"content": [{"type": "text", "text": "[{\"id\": 101, \"title\": \"Login fails on Safari\", \"status\": \"open

Root cause

Without a shared protocol, integrations are N apps × M systems: every app owns its own copy of every integration. The model side is already standard (tool definitions with a name, description and JSON Schema); what was missing is a standard way for an app to discover and call tools that live elsewhere.

The fix

Wrap each system once in an MCP server. Any MCP-capable app (the host, with one client per server connection) discovers the server's tools at runtime and passes them to its model:

from mcp.server import MCPServer

mcp = MCPServer("Tickets")

@mcp.tool()
def search_tickets(query: str, status: str = "open", limit: int = 20) -> list[dict]:
    '''Search tickets by text. Returns id, title and status, newest first.'''
    return tracker.search(query, status=status, limit=limit)

if __name__ == "__main__":
    mcp.run()                      # stdio by default; mcp.run(transport="streamable-http") for remote use

Key concepts:

  • Primitives: servers offer tools (actions the model can call), resources (data the app can read) and prompts (reusable templates).
  • Transports: stdio for local servers started by the host, Streamable HTTP for remote servers.
  • Lifecycle: initialize negotiates protocol version and capabilities, then tools/list and tools/call do the work.

Trade-offs

  • MCP adds a process or network hop; for one app talking to one system, a plain in-process tool is simpler.
  • Servers expose a generic interface; app-specific shortcuts may still belong in the app.
  • The protocol evolves; pin the protocol version your client and server support and test upgrades.

Detect it in production

  • Inventory of integrations per system: duplicates across apps are candidates for one MCP server.
  • Per-server call counts, latency and error rates, measured on the server, not in each app.

Key points

  • MCP turns N×M integrations into N clients and M servers.
  • Servers expose tools, resources and prompts; hosts discover them at runtime.
  • stdio for local servers, Streamable HTTP for remote ones.

Further reading: MCP introduction · MCP specification · MCP Python SDK

Practice questions

What problem does MCP solve compared with per-app tool integrations?

Answer

It replaces N apps × M systems custom integrations with one MCP server per system that any MCP-capable app can discover and call, so fixes and auth live in one place.

Name the three server primitives in MCP and what each is for.

Answer

Tools are actions the model can invoke, resources are data the application can read and include as context, and prompts are reusable templates the user or app can select.

When would you use stdio versus Streamable HTTP?

Answer

stdio when the host starts the server locally as a subprocess (developer tools, desktop apps); Streamable HTTP when the server runs remotely and serves many clients over the network.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?