AI & ML

MCP (Model Context Protocol): The USB-C for AI Applications Explained

· 5 min read
SitePoint Premium
Stay Relevant and Grow Your Career in Tech
  • Premium Results
  • Publish articles on SitePoint
  • Daily curated jobs
  • Learning Paths
  • Discounts to dev tools
Start Free Trial

7 Day Free Trial. Cancel Anytime.

What Is MCP (Model Context Protocol)?

MCP (Model Context Protocol) is an open protocol created by Anthropic that standardizes how AI applications connect to external data sources, tools, and services. Often called "the USB-C for AI," MCP replaces custom, one-off integrations with a single universal interface using JSON-RPC 2.0, enabling any compatible AI host to communicate with any MCP server through three primitives: Resources, Tools, and Prompts.

Every AI application that needs to read a database, query an API, or pull files from a repository currently solves the same problem from scratch. The Model Context Protocol (MCP) exists to end that cycle.

Table of Contents

Why AI Integration Is Broken (and How MCP Fixes It)

When a team builds a chatbot that needs Postgres access and another team builds an IDE assistant that needs GitHub data, both write custom connectors from scratch. Scale this across M AI applications and N data sources and the result is M×N one-off integrations, each with its own authentication logic, error handling, and data formatting quirks.

Anthropic open-sourced MCP in late 2024 to address exactly this fragmentation. The protocol works like USB-C for AI context: a single, standardized interface that any AI host can use to connect to any compatible data source or tool. Just as USB-C replaced a drawer full of proprietary cables with one universal connector, MCP replaces a tangle of custom integrations with one protocol that both sides can implement independently.

Just as USB-C replaced a drawer full of proprietary cables with one universal connector, MCP replaces a tangle of custom integrations with one protocol that both sides can implement independently.

By the end of this article, readers will understand the client-server handshake, the three primitives (Resources, Tools, Prompts), and transport options. They will also build a working MCP server in Node.js, wire an MCP client into a React application, and walk away with a concrete implementation checklist for production use.

What Is the Model Context Protocol (MCP)?

The Core Idea: A Universal Connector for AI Context

MCP is an open protocol that standardizes how AI-powered applications communicate with external data sources, tools, and services. Rather than each AI host implementing its own proprietary method of fetching context, MCP defines a common language for requesting and providing that context. Any application acting as an MCP host (Claude Desktop, an IDE plugin, a custom Node.js app) can connect to any MCP server (a database wrapper, a file system accessor, a SaaS API bridge) without either side needing to know the other's internals.

AspectBefore MCPWith MCP
Integration effortCustom code per AI app per data sourceOne server per data source, works with all hosts
ProtocolVaries (REST, GraphQL, proprietary SDKs)JSON-RPC 2.0 over standardized transports
Tool discoveryHardcoded per integrationDynamic capability negotiation
ReusabilityNear zero across projectsAny MCP host can connect to any MCP server
Security modelAd hoc per integrationProtocol-recommended human-in-the-loop approval (host application enforced)

Key Terminology You Need to Know

Four terms form the vocabulary of every MCP conversation. The MCP Host is the AI-powered application that needs external context; Claude Desktop, a custom Node.js app, or an IDE like Cursor all qualify. Inside each host lives an MCP Client, the protocol handler that maintains a 1:1 connection to a single MCP server. A host may contain multiple clients connecting to multiple servers simultaneously. On the other end, an MCP Server is a lightweight service that exposes specific data or capabilities through MCP, wrapping a Postgres database, a GitHub repository, or a company's internal knowledge base. Servers expose three primitive types: Resources (structured data for reading), Tools (executable functions the model can invoke), and Prompts (reusable prompt templates and workflows).

MCP Architecture: How It Works Under the Hood

Client-Server Communication Model

MCP uses JSON-RPC 2.0 as its wire format, which keeps messages simple, language-agnostic, and easy to debug. The communication flow follows a clear path: Host → Client → Server → Data Source/Tool. The host application delegates protocol handling to its embedded client, which talks directly to the server over one of two supported transport mechanisms (as of the 2024-11-05 protocol version; check the current spec for additional transports).

Stdio transport is designed for local processes. The host spawns the MCP server as a child process and communicates through standard input/output streams. This is the default for local development and tools like Claude Desktop.

HTTP with Server-Sent Events (SSE) handles remote servers. The client sends requests over HTTP POST and receives streamed responses via SSE. This transport suits production deployments where the MCP server runs on a different machine or in the cloud.

The Three MCP Primitives

A file's contents, a set of database rows, the response from an external API: these are all examples of Resources, structured data that the model can read but not modify. Each resource is identified by a URI.

Tools are executable functions that the model can call. A tool might search a knowledge base, create a GitHub issue, or run a database query. The host must enforce user approval before executing any tool; the protocol only recommends it.

Prompts are reusable prompt templates and workflows that servers can expose. These let server authors package domain-specific prompting strategies alongside the data and tools they provide.

Capability Negotiation and Security

When a client first connects to a server, they exchange capabilities through an initialization handshake. The client declares what it supports; the server declares what it offers. This negotiation ensures both sides agree on which primitives and features are available before any data flows.

The protocol enforces security through three mechanisms. First, tool execution requires explicit human approval, and host applications must enforce this gate. Second, servers declare their capabilities up front so hosts can enforce consent boundaries. Third, data access follows the principle of least privilege: a server exposes only what it's configured to expose.

Here is what the initialization handshake looks like at the wire level (replace the protocolVersion value with the current version from the MCP specification; this string was accurate as of late 2024):

// Client → Server: initialize request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "roots": { "listChanged": true }
    },
    "clientInfo": {
      "name": "my-app",
      "version": "1.0.0"
    }
  }
}

// Server → Client: initialize response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "resources": { "listChanged": true },
      "tools": {}
    },
    "serverInfo": {
      "name": "knowledge-base-server",
      "version": "1.0.0"
    }
  }
}

// Client → Server: initialized notification
{
  "jsonrpc": "2.0",
  "method": "notifications/initialized"
}

The capabilities objects are where negotiation happens. The server above declares support for resources (with change notifications) and tools. The client knows exactly what to expect.

Building Your First MCP Server with Node.js

Prerequisites

  • Node.js ≥18.0.0 (required for native ESM support and built-in fetch)
  • npm ≥9.0.0
  • TypeScript ≥5.3.0

Project Setup and Dependencies

Anthropic provides an official TypeScript/JavaScript SDK under the @modelcontextprotocol/sdk package. Here is the scaffolding:

mkdir mcp-knowledge-server
cd mcp-knowledge-server
npm init -y
npm install @modelcontextprotocol/[email protected] [email protected]
npm install -D typescript @types/node
npx tsc --init

After running npx tsc --init, replace the contents of tsconfig.json with the following to ensure ESM-compatible output:

{
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "target": "ES2022",
    "strict": true
  }
}

Update package.json to include "type": "module" (required for ESM import statements to work in Node.js):

{
  "name": "mcp-knowledge-server",
  "version": "1.0.0",
  "type": "module",
  "main": "dist/index.js",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "1.0.0",
    "zod": "3.22.0"
  },
  "devDependencies": {
    "typescript": "^5.3.0",
    "@types/node": "^20.0.0"
  }
}

Both @modelcontextprotocol/sdk and zod are pinned to exact versions (1.0.0 and 3.22.0 respectively) rather than using caret ranges. The SDK's ESM subpath exports (e.g., @modelcontextprotocol/sdk/server/mcp.js) can change between minor versions, and pinning prevents silent breakage. Use npm ci in CI environments with a committed package-lock.json for fully reproducible builds.

The zod dependency handles runtime schema validation for tool inputs, which matters for security when LLM-generated arguments arrive at tool handlers.

Creating a Simple "Company Knowledge Base" MCP Server

This server exposes two primitives: a Resource that returns FAQ entries from an in-memory array (replace with a file read or database call in production), and a Tool that searches those entries by keyword.

The import paths below (e.g., @modelcontextprotocol/sdk/server/mcp.js) follow the SDK's ESM subpath exports. Verify these paths against SDK version 1.0.0 before use since subpath exports can change between versions. Run node -e "import('@modelcontextprotocol/sdk/server/mcp.js').then(m=>console.log(Object.keys(m)))" to confirm McpServer and ResourceTemplate are exported from this path.

Create the file src/index.ts:

import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// Runtime guard: verify SDK exports exist (catches version mismatch early)
if (typeof McpServer !== "function") {
  throw new Error(
    "McpServer not found in @modelcontextprotocol/sdk/server/mcp.js — " +
    "verify SDK version and subpath exports"
  );
}

// Sample knowledge base data
const faqEntries = [
  { id: "faq-1", question: "What is our return policy?", answer: "Items can be returned within 30 days of purchase with a valid receipt." },
  { id: "faq-2", question: "How do I reset my password?", answer: "Visit /account/reset and enter your registered email address." },
  { id: "faq-3", question: "What are your support hours?", answer: "Support is available Monday through Friday, 9am to 6pm EST." },
  { id: "faq-4", question: "Do you offer bulk pricing?", answer: "Yes, orders over 100 units qualify for a 15% discount. Contact [email protected]." }
];

const server = new McpServer({
  name: "knowledge-base-server",
  version: "1.0.0"
});

// Resource: list all FAQ entries
server.resource(
  "faq-list",
  "knowledge://faqs",
  "Complete list of FAQ entries",
  async (uri) => ({
    contents: [{
      uri: uri.href,
      mimeType: "application/json",
      text: JSON.stringify(faqEntries, null, 2)
    }]
  })
);

// Resource template: individual FAQ by ID
// The `list` option controls whether this template appears in resource listing;
// `undefined` means the server will not proactively list individual FAQ URIs.
server.resource(
  "faq-entry",
  new ResourceTemplate("knowledge://faqs/{id}", { list: undefined }),
  "A single FAQ entry",
  async (uri, { id }) => {
    const entry = faqEntries.find(e => e.id === id);
    if (!entry) {
      // Throw an error so the MCP SDK returns a JSON-RPC error response.
      // This lets callers distinguish "not found" from a valid text resource.
      throw new Error(`Resource not found: ${id}`);
    }
    return {
      contents: [{
        uri: uri.href,
        mimeType: "application/json",
        text: JSON.stringify(entry, null, 2)
      }]
    };
  }
);

// Tool: search knowledge base by keyword
server.tool(
  "searchKnowledgeBase",
  "Search the company knowledge base by keyword",
  { keyword: z.string().min(1).describe("Search term to match against questions and answers") },
  async ({ keyword }) => {
    const lower = keyword.toLowerCase();
    const results = faqEntries.filter(
      e => e.question.toLowerCase().includes(lower) || e.answer.toLowerCase().includes(lower)
    );
    return {
      content: [{
        type: "text",
        text: results.length > 0
          ? JSON.stringify(results, null, 2)
          : `No results found for "${keyword}"`
      }]
    };
  }
);

// Start server with stdio transport
const transport = new StdioServerTransport();
await server.connect(transport);

The z.string().min(1) validation on the tool input is not optional decoration. LLMs can and do send empty strings, malformed arguments, or unexpected types. Zod catches these before they reach business logic.

The z.string().min(1) validation on the tool input is not optional decoration. LLMs can and do send empty strings, malformed arguments, or unexpected types. Zod catches these before they reach business logic.

Testing Your Server Locally

First, build the project:

npm run build

The MCP Inspector is a developer tool for testing MCP servers interactively. Run it with:

npx @modelcontextprotocol/inspector node dist/index.js

Verify the exact package name on npm before running. The Inspector opens a local web UI, typically at http://localhost:5173.

It connects to the server, lists its capabilities, and lets developers invoke resources and tools manually.

For integration with Claude Desktop, add the server to claude_desktop_config.json:

{
  "mcpServers": {
    "knowledge-base": {
      "command": "node",
      "args": ["/absolute/path/to/mcp-knowledge-server/dist/index.js"]
    }
  }
}

The path in args must be absolute, pointing to the compiled dist/index.js file. Relative paths are the most common source of "server not found" errors.

On macOS, this file lives at ~/Library/Application Support/Claude/claude_desktop_config.json. On Windows, it's at %APPDATA%\Claude\claude_desktop_config.json. After saving the config and restarting Claude Desktop, the knowledge base server's resources and tools become available in the conversation.

Building an MCP Client in a React + Node.js Application

Why Build a Custom Client?

Claude Desktop is a convenient host for testing, but production applications often need their own MCP client. A React dashboard that aggregates context from multiple MCP servers, a customer support tool that routes queries through domain-specific knowledge bases, or any application that wants to consume MCP data programmatically all require a custom client.

Setting Up the Node.js Backend Client

The same @modelcontextprotocol/sdk package provides client classes. Here is an Express backend that connects to the knowledge base server and exposes its capabilities as REST endpoints.

First, install the additional dependency:

npm install express

Then create the backend server:

import express from "express";
import path from "path";
import { fileURLToPath } from "url";
import { dirname } from "path";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const app = express();
app.use(express.json());

// Resolve relative to this source file, not process.cwd()
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const serverPath = path.resolve(__dirname, "../../mcp-knowledge-server/dist/index.js");

const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001;
const CONNECT_TIMEOUT_MS = 10_000;
const MAX_CONCURRENT_MCP_REQUESTS = 10;

let mcpClient: Client | undefined;
let clientReady = false;
let inflightRequests = 0;

// Middleware: reject requests if MCP client is not yet initialized
function requireMcpClient(
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
) {
  if (!clientReady || !mcpClient) {
    return res.status(503).json({ error: "MCP client not ready" });
  }
  next();
}

async function initMcpClient(retries = 3): Promise<void> {
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      const transport = new StdioClientTransport({
        command: "node",
        args: [serverPath]
      });

      mcpClient = new Client({ name: "web-app-client", version: "1.0.0" });

      // Connect with a timeout to prevent hanging indefinitely
      // if the child process is unresponsive
      await Promise.race([
        mcpClient.connect(transport),
        new Promise<never>((_, reject) =>
          setTimeout(() => reject(new Error("MCP connect timeout")), CONNECT_TIMEOUT_MS)
        )
      ]);

      clientReady = true;
      console.log(JSON.stringify({
        event: "mcp_connected",
        serverPath,
        ts: new Date().toISOString()
      }));
      return;
    } catch (err) {
      clientReady = false;
      mcpClient = undefined;
      console.error(JSON.stringify({
        event: "mcp_connect_failed",
        attempt,
        error: err instanceof Error ? err.message : "unknown error",
        ts: new Date().toISOString()
      }));
      if (attempt < retries) {
        await new Promise(r => setTimeout(r, 2000 * attempt));
      }
    }
  }
  throw new Error("MCP client failed to connect after retries");
}

app.get("/api/resources", requireMcpClient, async (req, res) => {
  try {
    const resources = await mcpClient!.listResources();
    res.json(resources);
  } catch (error) {
    console.error(JSON.stringify({
      event: "mcp_error",
      route: "/api/resources",
      detail: error instanceof Error ? error.message : "unknown error",
      ts: new Date().toISOString()
    }));
    res.status(503).json({ error: "MCP server unavailable" });
  }
});

app.post("/api/tools/search", requireMcpClient, async (req, res) => {
  const { keyword } = req.body;

  if (typeof keyword !== "string" || keyword.trim().length === 0) {
    return res.status(400).json({ error: "keyword must be a non-empty string" });
  }
  if (keyword.length > 200) {
    return res.status(400).json({
      error: "keyword exceeds maximum length of 200 characters"
    });
  }

  // Guard against too many concurrent MCP requests sharing the single client
  if (inflightRequests >= MAX_CONCURRENT_MCP_REQUESTS) {
    return res.status(429).json({ error: "Too many concurrent MCP requests" });
  }

  inflightRequests++;
  try {
    const result = await mcpClient!.callTool({
      name: "searchKnowledgeBase",
      arguments: { keyword }
    });
    res.json(result);
  } catch (error) {
    console.error(JSON.stringify({
      event: "mcp_error",
      route: "/api/tools/search",
      detail: error instanceof Error ? error.message : "unknown error",
      ts: new Date().toISOString()
    }));
    res.status(503).json({ error: "MCP server unavailable" });
  } finally {
    inflightRequests--;
  }
});

initMcpClient().then(() => {
  app.listen(PORT, () =>
    console.log(JSON.stringify({
      event: "api_server_started",
      port: PORT,
      ts: new Date().toISOString()
    }))
  );
}).catch(err => {
  console.error("MCP client init failed:", err instanceof Error ? err.message : err);
  process.exit(1);
});

Key design decisions in this backend:

  • Path resolution uses import.meta.url to resolve the MCP server path relative to the source file, not process.cwd(). This prevents silent path-resolution bugs when the working directory varies across development, CI, and production environments.
  • Connect timeout ensures that if the child MCP server process hangs during startup, the backend does not block indefinitely.
  • Retry logic attempts reconnection up to three times with exponential backoff before giving up.
  • Readiness guard returns HTTP 503 for any request arriving before the MCP client has successfully connected, instead of crashing with a TypeError on undefined.
  • Concurrency limit prevents unbounded concurrent requests from overwhelming the single shared MCP client instance.
  • Safe error responses log full error details internally but return only a generic message to HTTP callers, preventing internal path and dependency disclosure.

The StdioClientTransport spawns the MCP server as a child process. For remote servers, swap to SSEClientTransport with the server's URL:

import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
const transport = new SSEClientTransport(new URL("https://your-server/sse"));

Verify the SSE import path against your installed SDK version.

Consuming MCP Data in a React Frontend

import { useState, useEffect } from "react";

function KnowledgeBase() {
  const [resources, setResources] = useState([]);
  const [searchTerm, setSearchTerm] = useState("");
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();

    fetch("/api/resources", { signal: controller.signal })
      .then(res => res.ok ? res.json() : Promise.reject("Server unavailable"))
      .then(data => setResources(data.resources || []))
      .catch(err => {
        if (err.name === "AbortError") return;
        setError(typeof err === "string" ? err : err.message);
      });

    return () => controller.abort();
  }, []);

  async function handleSearch() {
    setLoading(true);
    setError(null);
    try {
      const res = await fetch("/api/tools/search", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ keyword: searchTerm })
      });
      if (!res.ok) throw new Error("Search failed");
      const data = await res.json();
      const rawText = data.content?.[0]?.text;
      if (!rawText) {
        setError("Empty response from server");
        setResults([]);
        return;
      }
      try {
        const parsed = JSON.parse(rawText);
        setResults(Array.isArray(parsed) ? parsed : []);
      } catch {
        setError("Invalid response format from server");
        setResults([]);
      }
    } catch (err) {
      setError(err instanceof Error ? err.message : "An unknown error occurred");
      setResults([]);
    } finally {
      setLoading(false);
    }
  }

  if (error) return <div className="error">MCP Error: {error}</div>;

  return (
    <div>
      <h2>Knowledge Base ({resources.length} resources available)</h2>
      <input
        value={searchTerm}
        onChange={e => setSearchTerm(e.target.value)}
        placeholder="Search FAQs..."
      />
      <button onClick={handleSearch} disabled={loading}>
        {loading ? "Searching..." : "Search"}
      </button>
      <ul>
        {results.map(r => (
          <li key={r.id}>
            <strong>{r.question}</strong>
            <p>{r.answer}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default KnowledgeBase;

Handling Errors and Loading States

The pattern above demonstrates graceful degradation: when the MCP server is unavailable, the Express layer returns a 503 with a generic error message (without exposing internal details), and the React component renders an error state instead of crashing. JSON parse failures from malformed server responses surface to the user as an explicit error message rather than silently producing an empty result list. The useEffect cleanup with AbortController prevents state updates on unmounted components. This matters because MCP servers are external processes that can fail independently of the host application. Treating MCP connectivity as inherently unreliable and building around that assumption prevents cascading failures in production.

Real-World Use Cases and Ecosystem Adoption

Where MCP Is Already Being Used

Anthropic's own products, Claude Desktop and Claude Code, use MCP natively. Major IDEs have followed: Cursor, Windsurf, and VS Code (through GitHub Copilot, as of early 2025, subject to feature availability and version) all support MCP servers as context providers. On the enterprise side, Zed, Sourcegraph, and Block (parent company of Square) have integrated MCP into their developer toolchains.

Pre-Built MCP Servers You Can Use Today

The official MCP servers repository on GitHub provides production-ready servers for common data sources: file system access, GitHub, Google Drive, Postgres, Slack, and Puppeteer for browser automation. These can be composed in a single host by registering multiple entries in the MCP configuration. A Claude Desktop setup might connect to a Postgres server for database queries, a GitHub server for repository context, and a custom knowledge base server simultaneously, each running as an independent process with its own capability set.

MCP Implementation Checklist

  1. ☐ Identify every context source your AI application needs (databases, APIs, file systems, SaaS tools)
  2. ☐ Decide on transport per server: stdio for local processes, HTTP+SSE for remote deployments
  3. ☐ Scaffold each MCP server using @modelcontextprotocol/sdk
  4. ☐ Define Resources (read-only data) and Tools (executable actions) with explicit JSON schemas via Zod
  5. ☐ Implement capability negotiation; validate all tool inputs at runtime
  6. ☐ Add human-in-the-loop confirmation for any tool that modifies data or accesses sensitive systems
  7. ☐ Test each server with MCP Inspector before connecting to a host
  8. ☐ Register servers in your MCP host configuration (Claude Desktop, custom app, or IDE)
  9. ☐ Build or configure an MCP client in your application with error handling, connect timeouts, and retry logic
  10. ☐ Monitor server health, log tool invocations, and version your MCP server endpoints for backward compatibility

Common Pitfalls and Best Practices

Security Considerations

Never trust LLM-generated tool inputs without validation. The model might send file paths with directory traversal sequences, SQL fragments intended for injection, or arguments outside expected ranges (e.g., a limit parameter of 10 million). Every tool handler should validate inputs through Zod schemas. Apply the principle of least privilege: if a server only needs to read from one table, scope its credentials to that single table. Sanitize file paths by resolving them against a whitelisted root directory and rejecting anything that escapes it, and enforce maximum input lengths on all string parameters to prevent CPU exhaustion from processing excessively long inputs.

Never trust LLM-generated tool inputs without validation. The model might send file paths with directory traversal sequences, SQL fragments intended for injection, or arguments outside expected ranges.

Never expose raw internal error messages to HTTP clients either. Log full error detail server-side for debugging, but return only generic error descriptions in API responses to avoid disclosing internal file paths, dependency names, or stack traces.

Performance Tips

Keep MCP servers single-purpose and minimize their dependencies and memory footprint. A server that wraps a Postgres database should do that and nothing else. This makes servers easier to test, deploy, and scale independently. Cache responses from resources whose data changes infrequently; FAQ entries that update once a week do not need a fresh database query on every read. For database-backed servers, use connection pooling to avoid the overhead of establishing a new connection per request. When a single MCP client instance is shared across concurrent HTTP requests, enforce a concurrency limit to prevent interleaved requests from overwhelming the client's JSON-RPC handling.

What's Next for MCP and Why It Matters

The protocol continues to evolve. The MCP roadmap includes a formal authentication framework (portions of which already appear in newer spec drafts), remote server discovery mechanisms, and enhanced streaming support for large payloads (tracked in the spec's open issues; no size threshold has been finalized yet). With Anthropic, Microsoft (via Copilot), and major IDE vendors already shipping MCP support, the network effects that drive protocol adoption are building. For the current state of the specification and open roadmap items, see the MCP specification repository.