Model Context Protocol (MCP)

"USB-C for AI" — expose your REST API as typed tools for Claude, GPT-4, Gemini, and any MCP-compatible AI agent

Last Updated:

What is Model Context Protocol?

Model Context Protocol (MCP) is an open standard released by Anthropic in November 2024 that defines how AI models connect to external tools, databases, and APIs through a standardized, vendor-neutral interface.

In 2026, MCP has crossed critical adoption thresholds: 97M SDK downloads, 13,000+ MCP servers on GitHub, and native support from Anthropic, OpenAI, Google, and Microsoft. Gartner predicts 75% of API gateway vendors will include MCP support by end of 2026. Your REST API for AI agents can be standardized through MCP to work across all LLM providers with a single integration.

MCP vs REST

PropertyREST APIMCP Server
Designed forHumans, browsers, traditional clientsAI agents, LLMs
Interface definitionOpenAPI / docsTool schemas with NL descriptions
DiscoveryDocumentationtools/list, resources/list RPC calls
ProtocolHTTP/1.1 + HTTP/2JSON-RPC 2.0 over stdio or HTTP/SSE
Replaces REST?No — MCP wraps REST. Both coexist.

Core Primitives: Tools, Resources, Prompts

MCP has three building blocks:

  • Tools — executable functions the LLM can call (read, write, compute). Maps to your REST POST/PUT/PATCH/DELETE endpoints.
  • Resources — read-only data the LLM can access for context (files, database records, API state). Maps to your REST GET endpoints.
  • Prompts — reusable prompt templates that guide the LLM on how to use your tools effectively.

Architecture & Transports

MCP uses a client-server architecture over JSON-RPC 2.0. Two transport options:

  • stdio — local subprocess; the host app (Claude Desktop, Cursor) spawns your server as a child process. Zero network overhead. Best for development and desktop tools.
  • HTTP/SSE (Streamable HTTP) — remote server over HTTP; the host connects via Server-Sent Events for streaming. Required for cloud deployment and multi-client use.

Build Your First MCP Server in Node.js

npm install @modelcontextprotocol/sdk zod
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';

const server = new McpServer({
  name: 'users-api',
  version: '1.0.0',
  description: 'MCP server for the Users REST API'
});

// ── Tool: Create a user ───────────────────────────────────────────────
server.tool(
  'create_user',
  'Create a new user account. Use this when the user wants to register or add a new member.',
  {
    email: z.string().email().describe('User email address'),
    name:  z.string().min(1).max(100).describe('Full display name'),
    role:  z.enum(['admin', 'user', 'viewer']).default('user')
           .describe('Permission level for this user')
  },
  async ({ email, name, role }) => {
    const res = await fetch('https://api.example.com/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.API_TOKEN}`
      },
      body: JSON.stringify({ email, name, role })
    });

    if (!res.ok) {
      const err = await res.json();
      return {
        content: [{ type: 'text', text: `Error creating user: ${err.message}` }],
        isError: true
      };
    }

    const user = await res.json();
    return {
      content: [{
        type: 'text',
        text: `User created successfully. ID: ${user.id}, Email: ${user.email}`
      }]
    };
  }
);

// ── Resource: List users ──────────────────────────────────────────────
server.resource(
  'users_list',
  'users://list',
  { mimeType: 'application/json', description: 'Current list of all users' },
  async (uri) => {
    const res = await fetch('https://api.example.com/users', {
      headers: { 'Authorization': `Bearer ${process.env.API_TOKEN}` }
    });
    const users = await res.json();
    return {
      contents: [{
        uri: uri.href,
        mimeType: 'application/json',
        text: JSON.stringify(users, null, 2)
      }]
    };
  }
);

// ── Start server (stdio transport for local use) ──────────────────────
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('MCP server running on stdio');

Wrapping an Existing REST API

// Pattern: auto-generate MCP tools from OpenAPI spec
// npm install openapi-to-mcp (community package)

// Or manually: one MCP tool per meaningful REST operation
// Group related endpoints into a single tool when appropriate

// ✅ Good: one tool per user intent
server.tool('search_products', 'Search the product catalog by keyword or category', ...);
server.tool('get_product', 'Get full details for a specific product by ID', ...);
server.tool('add_to_cart', 'Add a product to the current shopping cart', ...);

// ❌ Avoid: exposing raw CRUD — too low-level for an LLM
// server.tool('POST_products', ...)  ← meaningless to an LLM

// HTTP/SSE transport for remote deployment
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import express from 'express';

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

app.post('/mcp', async (req, res) => {
  const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => crypto.randomUUID() });
  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

app.listen(3000, () => console.log('MCP server on http://localhost:3000/mcp'));

Authentication: OAuth 2.1

MCP 2026 spec formally recommends OAuth 2.1 for remote server authorization. The MCP client obtains a scoped token and forwards it with each tool call:

// Server: validate bearer token on each tool invocation
server.tool('create_order', 'Create a new order', { ... }, async (args, context) => {
  const authHeader = context.meta?.authorization;
  if (!authHeader?.startsWith('Bearer ')) {
    return { content: [{ type: 'text', text: 'Unauthorized' }], isError: true };
  }
  const token = authHeader.slice(7);
  const user = await verifyToken(token);  // same JWT verification as your REST API
  // proceed with user context...
});

Local vs Remote Deployment

ModeTransportUse CaseAuth
LocalstdioClaude Desktop, Cursor, dev toolsAPI key in env var
RemoteHTTP/SSEProduction, multi-user, cloudOAuth 2.1 Bearer
Dockerstdio or HTTPContainerized deploymentSecrets manager

Security Best Practices

  • Validate all inputs — Zod schemas enforce types, but also sanitize string inputs before passing to SQL/shell
  • Require explicit approval for destructive operations (delete, payment, email send)
  • Rate limit tool calls — a runaway agent loop can call thousands of times/second
  • Scope tokens minimally — each MCP server should have the least-privilege token
  • Log every tool invocation — include agent ID, tool name, arguments, and result
  • Never pass user input directly to shell commands or raw SQL queries

Testing with MCP Inspector

# Launch the visual debugger — test tools without running a full LLM
npx @modelcontextprotocol/inspector node server.js

# The Inspector opens at http://localhost:5173
# You can list tools, call them with custom arguments, and inspect responses

FAQ

Does MCP replace REST?

No. MCP is complementary to REST. Your REST API continues to serve browsers, mobile apps, and traditional services. MCP wraps your REST API to make it accessible to AI agents. Think of MCP as an AI-native adapter layer over your existing REST infrastructure.

What is the difference between MCP tools and REST endpoints?

REST endpoints are designed for structured HTTP clients. MCP tools are designed for LLMs: they have natural-language descriptions the model uses to decide when to call them, strongly-typed parameters, and return text or structured content. An MCP tool typically wraps one or more REST calls internally.