The Integration Problem

Every useful AI application eventually needs to connect to the outside world. A coding assistant needs to read files and run commands. A customer-support agent needs to query a database and update tickets. A research assistant needs to search the web and read PDFs. Each of these capabilities requires an integration: code that bridges the gap between what the model wants to do and the external system that can actually do it.

Without a standard protocol, every one of these integrations is custom. Connecting an LLM to Slack requires one bespoke adapter. Connecting it to GitHub requires a completely different one. A PostgreSQL integration shares no code or conventions with a filesystem integration. If you build a new AI application, you have to write all these adapters from scratch, even though someone building a different AI application will write nearly identical ones.

The scaling problem becomes clear when you count the combinations. If you have $N$ AI models (or model hosts) and $M$ external tools, the naive approach requires $N \times M$ custom integrations. Each model provider writes a separate connector for each tool, and each tool provider writes a separate connector for each model. With 5 models and 20 tools, that's 100 integrations to build and maintain. With 50 models and 200 tools, it's 10,000. This is the same combinatorial explosion that hardware faced before USB: every peripheral manufacturer had to build a different cable for every computer brand.

The Model Context Protocol (MCP) (Anthropic, 2024) solves this the same way USB solved the peripheral problem: by defining a standard interface . Tool providers implement the MCP server specification once. Model hosts implement the MCP client specification once. And then everything that speaks MCP can connect to everything else that speaks MCP. The $N \times M$ problem collapses to $N + M$: each model host writes one client, each tool writes one server, and they all interoperate.

๐Ÿ’ก The USB analogy is worth taking seriously. Before USB, you needed a PS/2 port for the keyboard, a serial port for the modem, a parallel port for the printer, and a proprietary port for the joystick. USB replaced all of those with a single standard. MCP aims to do the same for AI tool integration: one protocol to replace the zoo of custom connectors.

MCP is an open protocol (specification) originally developed by Anthropic and released as an open standard. It is not specific to Claude or any particular model. Any AI application can implement an MCP client, and any tool or data source can implement an MCP server. The rest of this article explains exactly how.

MCP Architecture: Clients, Servers, and Hosts

MCP defines three distinct roles, each with a clear responsibility. Understanding these roles is essential because the protocol's power comes from how they compose together.

The MCP Host is the application the user interacts with. This could be Claude Desktop, an IDE like Cursor, a custom chatbot, or any application that wants to use an AI model enhanced with external tools. The host is responsible for the user experience: it presents results, handles permissions and consent, and decides which MCP servers to connect to. Crucially, the host controls how much of the MCP server's capabilities are actually exposed to the model. A host might, for security reasons, choose not to expose certain tools even if the server offers them.

Inside the host lives one or more MCP Clients . Each client maintains a one-to-one stateful connection with a single MCP server. If the host wants to connect to three different services (say, GitHub, a PostgreSQL database, and a web search API), it creates three separate MCP client instances, one for each server. The client handles the protocol-level details: sending requests, receiving responses, managing the connection lifecycle.

The MCP Server is the counterpart that exposes capabilities. Each server provides a specific set of tools, resources, and prompts to the client that connects to it. A GitHub MCP server exposes GitHub operations. A filesystem MCP server exposes file reading and writing. The server does not need to know anything about the model or the host โ€” it simply responds to protocol requests.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  MCP Host (e.g. Claude Desktop, IDE, app)   โ”‚
โ”‚                                             โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”           โ”‚
โ”‚  โ”‚ MCP Client  โ”‚  โ”‚ MCP Client  โ”‚  . . .    โ”‚
โ”‚  โ”‚    (1:1)    โ”‚  โ”‚    (1:1)    โ”‚           โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
          โ”‚                โ”‚
     JSON-RPC 2.0     JSON-RPC 2.0
     (stdio/HTTP)     (stdio/HTTP)
          โ”‚                โ”‚
   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”
   โ”‚ MCP Server  โ”‚  โ”‚ MCP Server  โ”‚
   โ”‚  (GitHub)   โ”‚  โ”‚ (Postgres)  โ”‚
   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Communication between client and server uses JSON-RPC 2.0 , a lightweight remote procedure call protocol that encodes requests and responses as JSON objects. The transport layer (how those JSON messages actually travel) supports two modes: stdio (standard input/output) for local servers that run as child processes on the same machine, and Streamable HTTP for remote servers accessible over the network. The stdio transport is simpler (no networking, no authentication) and is the most common for local development. Streamable HTTP is needed when the server runs on a different machine or behind a cloud API.

The protocol defines three main primitives that servers can expose to clients:

  • Tools: functions the model can invoke to perform actions or compute results. Creating a GitHub issue, running a SQL query, sending a message โ€” these are all tools. Tools are model-controlled : the model decides when and how to call them, just like function calling, but the tools are discovered dynamically through the protocol rather than hardcoded in the API request.
  • Resources: data the model can read to build context. File contents, database records, API documentation, log files โ€” these are resources. Unlike tools (which perform actions), resources are read-only and are typically application-controlled : the host or user decides which resources to include in the model's context.
  • Prompts: reusable prompt templates that the server provides. These are user-controlled โ€” they give users standardised ways to interact with a tool's capabilities. For example, a code-review MCP server might expose a "review_pull_request" prompt template that structures the review request in a way the server knows how to handle well.
๐Ÿ’ก The distinction between who controls each primitive matters. Tools are model-controlled (the AI decides to call them). Resources are application-controlled (the host or user chooses what context to load). Prompts are user-controlled (the user selects a template). This separation gives each layer appropriate authority over what happens.

How Tool Discovery Works

In standard function calling (the kind we covered earlier in this track), the set of available tools is defined at API call time. You include a list of tool schemas in the request body, and the model can only call those specific tools during that conversation turn. If you want to change the available tools, you change the API request. The tool definitions are static โ€” baked into each request by the application developer.

MCP flips this model. Instead of the application developer defining tools upfront, the server declares its own tools at connection time . When an MCP client connects to a server, it sends a tools/list request. The server responds with a complete list of every tool it offers, including names, descriptions, and full JSON Schema definitions for each tool's input parameters. The client then passes these tool definitions into the model's context, and the model can call any of them.

// 1. Client asks: "what tools do you have?"
// Request โ†’
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list"
}

// 2. Server responds with tool schemas
// โ† Response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "create_issue",
        "description": "Create a new issue in a GitHub repository",
        "inputSchema": {
          "type": "object",
          "properties": {
            "repo": {
              "type": "string",
              "description": "Repository in owner/repo format"
            },
            "title": {
              "type": "string",
              "description": "Issue title"
            },
            "body": {
              "type": "string",
              "description": "Issue body (Markdown)"
            }
          },
          "required": ["repo", "title"]
        }
      },
      {
        "name": "list_pull_requests",
        "description": "List open pull requests for a repository",
        "inputSchema": {
          "type": "object",
          "properties": {
            "repo": {
              "type": "string",
              "description": "Repository in owner/repo format"
            },
            "state": {
              "type": "string",
              "enum": ["open", "closed", "all"],
              "description": "Filter by PR state"
            }
          },
          "required": ["repo"]
        }
      }
    ]
  }
}

This dynamic discovery has a profound consequence: the model's capabilities can change without changing any code in the host application . Connect a new MCP server, and the model instantly gains access to whatever tools that server provides. Disconnect a server, and those tools disappear. The host application doesn't need to be redeployed or even restarted โ€” it simply reflects whatever servers are currently connected.

When the model decides to call a tool, the flow is straightforward. The model generates a tool call (just like in standard function calling), the client routes it to the appropriate server via a tools/call request, the server executes the operation and returns a result, and the client feeds that result back to the model for the next generation step.

// 3. Model decides to call a tool โ†’ client sends:
// Request โ†’
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "create_issue",
    "arguments": {
      "repo": "acme/webapp",
      "title": "Fix login timeout bug",
      "body": "Users report being logged out after 5 minutes..."
    }
  }
}

// 4. Server executes the action and returns the result
// โ† Response
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Created issue #347 in acme/webapp"
      }
    ]
  }
}

The tool schemas use the same JSON Schema format as standard function calling, so the model's existing ability to generate structured tool calls transfers directly. The difference is purely in how the schemas arrive: pushed by the application developer in function calling, pulled from the server in MCP. Additionally, MCP tool definitions can carry annotations โ€” metadata hints like whether the tool is read-only or destructive, which the host can use to make permission decisions (e.g., auto-approve reads but require user confirmation for writes).

Servers can also notify the client that their tool list has changed via a notifications/tools/list_changed message, prompting the client to re-fetch the tool list. This means a running server can add or remove tools on the fly โ€” useful for servers that adapt their capabilities based on context (e.g., a database server that exposes different tools depending on which database you're connected to).

Resources and Context

Tools let the model do things . Resources let the model know things . While tools are about actions (create an issue, run a query, send a message), resources are about data โ€” structured, read-only information that the server makes available for inclusion in the model's context. Think of resources as the model's reference library: documents it can consult to ground its responses in facts.

Every resource is identified by a URI (Uniform Resource Identifier) that follows a scheme specific to the server. A filesystem server might expose resources with URIs like file:///home/user/docs/spec.md . A GitHub server might use github://acme/webapp/issues/347 . A database server might use postgres://mydb/users/schema . The URI scheme is up to the server, but the protocol provides a standard way to list, read, and subscribe to resources regardless of the scheme.

Servers expose resources in two ways. Direct resources are concrete items the server knows about and can enumerate via a resources/list request โ€” the server returns a list of available resources with their URIs, names, descriptions, and MIME types. Resource templates are URI patterns with placeholders (like github://acme/webapp/issues/{issue_number} ) that the client can fill in dynamically. Templates are useful when the set of possible resources is too large to enumerate (e.g., every possible database query).

// Client lists available resources
// Request โ†’
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "resources/list"
}

// โ† Response
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "resources": [
      {
        "uri": "file:///project/README.md",
        "name": "Project README",
        "mimeType": "text/markdown"
      },
      {
        "uri": "file:///project/src/config.json",
        "name": "App configuration",
        "mimeType": "application/json"
      }
    ]
  }
}

// Client reads a specific resource
// Request โ†’
{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "resources/read",
  "params": {
    "uri": "file:///project/README.md"
  }
}

// โ† Response
{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "contents": [
      {
        "uri": "file:///project/README.md",
        "mimeType": "text/markdown",
        "text": "# My Project\nThis project implements..."
      }
    ]
  }
}

Resources can also be subscribed to . If the server supports it, the client can call resources/subscribe on a particular URI, and the server will send notifications/resources/updated whenever the underlying data changes. The client can then re-read the resource to get the latest version. This is useful for resources that change frequently, like log files, monitoring dashboards, or live database views.

This is where MCP intersects with the retrieval-augmented generation (RAG) patterns covered in the RAG Pipelines track. A resource server can function like a retrieval pipeline: the host asks the model what information it needs, the model specifies resources by URI, and the host reads them and includes their contents in the context. Alternatively, a more sophisticated MCP server could expose a search tool that takes a query and returns relevant document URIs, combining tool use and resource reading in a single flow. The protocol is flexible enough to support both patterns.

๐Ÿ’ก The resource primitive is what makes MCP more than just "function calling with a protocol". Function calling gives the model the ability to act. Resources give the model the ability to learn about its environment. Together, they let a model both gather information and take action โ€” the full loop an agent needs.

MCP in the Wild

MCP's value is proportional to its adoption. A protocol is only useful if both sides implement it. So who's actually using MCP?

On the host side (applications that connect to MCP servers), adoption has been rapid. Claude Desktop was the first host with native MCP support, allowing users to connect local MCP servers and give Claude access to their filesystem, databases, and other tools. Claude Code (Anthropic's CLI agent) uses MCP extensively for IDE integration, file system access, and extensibility โ€” users can add custom MCP servers to extend Claude Code's capabilities. Cursor and Windsurf (AI-powered IDEs) support MCP for connecting to external tools and services. The pattern is clear: any application that wants to give an AI model access to external capabilities can use MCP as the standard way to do it.

On the server side (tools and services that expose capabilities via MCP), the ecosystem is growing quickly. The official MCP servers repository hosts reference implementations for common services: filesystem access, GitHub, GitLab, Slack, Google Drive, PostgreSQL, SQLite, Puppeteer (browser automation), and many more. Community-maintained servers cover an even wider range: AWS services, Jira, Notion, Stripe, Twilio, and hundreds of others. Because MCP is an open spec, anyone can write a server for any service.

Connecting an MCP server to a host is typically done through a configuration file. Here's an example of what you might add to Claude Desktop's configuration to connect a filesystem server and a GitHub server:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/Users/alice/projects",
        "/Users/alice/documents"
      ]
    },
    "github": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-github"
      ],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "<your-token>"
      }
    }
  }
}

Each entry specifies a command to launch the server process and (optionally) environment variables for authentication. The host launches these processes, establishes MCP connections over stdio, discovers each server's tools and resources, and makes them available to the model. Adding a new capability is as simple as adding a new entry to this configuration.

This is where the ecosystem effect kicks in. Once a tool has an MCP server, it works with every MCP-compatible host โ€” not just Claude, but Cursor, Windsurf, and any future application that supports the protocol. And once a host supports MCP, it gains access to every MCP server in the ecosystem, without the host developer writing any tool-specific code. Write once, use everywhere. This is the same dynamic that made USB, HTTP, and LSP (the Language Server Protocol) so successful: the value of implementing the standard increases with every other participant who also implements it.

๐Ÿ’ก The Language Server Protocol (LSP) is a particularly close analogy. Before LSP, every IDE had to write a custom integration for every programming language (N editors ร— M languages = Nร—M integrations). LSP defined a standard protocol, and now a single language server works in any editor that supports LSP. MCP is LSP for AI tool integration.

MCP vs Direct API Integration

MCP is not the only way to connect a model to external tools. The simplest alternative is direct API integration : you write code that calls the GitHub API, parses the response, and formats it for the model. No protocol, no server, no discovery โ€” just a function in your application that does the thing. So when should you use MCP, and when is direct integration the better choice?

The tradeoffs break down along several axes:

  • Composability: MCP servers are composable by design. A host can connect to any combination of servers, and new servers can be added without changing the host. Direct integrations are tightly coupled to the host โ€” adding a new tool means changing the application code.
  • Reusability: an MCP server works with any MCP-compatible host. A direct integration works only in the application it was built for. If you're building a tool that should work across multiple AI applications, MCP is the clear choice.
  • Discovery: MCP tools are discovered dynamically โ€” the model learns what's available at runtime. Direct integrations are defined statically in code. Dynamic discovery is more flexible but adds a layer of indirection that can make debugging harder.
  • Simplicity: for a single integration in a single application, MCP adds ceremony. You need a server process, a client, protocol negotiation, and JSON-RPC message handling. A direct API call might be 20 lines of code. The protocol overhead only pays off when you need the composability and reusability.
  • Performance: MCP adds a communication layer (JSON-RPC over stdio or HTTP). For most tool calls this overhead is negligible (milliseconds), but for extremely high-throughput or latency-sensitive operations, a direct in-process function call will be faster.

When to use MCP:

  • You're building a platform that needs to connect to many different tools (IDE, assistant app, orchestration framework).
  • You're building a tool or service that should be accessible from many different AI applications.
  • You want users to be able to extend your application's capabilities without modifying its source code.
  • You want a separation of concerns between the AI application and the tool implementation (different teams, different repos, different deployment cycles).

When direct integration is fine:

  • You have a single application with a small, fixed set of tools that won't change.
  • The integration is performance-critical and you need to minimise overhead.
  • The tool uses a proprietary protocol that doesn't map cleanly to MCP's request/response model.
  • You're prototyping and want the fastest path to a working demo.

The trajectory of the ecosystem suggests MCP is becoming the default. As more hosts support MCP and more servers become available, the cost of not supporting it increases. If you build a tool without an MCP server, users of Claude Desktop, Cursor, Claude Code, and every other MCP host can't use it without custom work. If you build an AI application without an MCP client, your users can't connect the hundreds of existing MCP servers. The protocol isn't mandatory, but network effects are making it the path of least resistance โ€” much as HTTP became the default for web services, not by mandate, but by ecosystemic gravity.

Quiz

Test your understanding of the Model Context Protocol.

What core problem does MCP solve for AI tool integration?

What is the relationship between an MCP client and an MCP server?

How do MCP tools differ from standard function calling tools?

What distinguishes MCP Resources from MCP Tools?