MCP vs WebMCP: A Developer's Guide with TypeScript Setup Tutorial

AA
Atif AliFull Stack Engineer
https://res.cloudinary.com/dbonkjhet/image/upload/v1790762145/groooh/blog-images/rwbwb4xyzjcqfn2zp8af.jpg

Understand the Model Context Protocol, the real difference between local and remote MCP, what WebMCP actually is, and build a secure TypeScript MCP server step by step.

Quick Answer

The Model Context Protocol (MCP) is an open standard, introduced by Anthropic, for connecting AI models to external tools and data through a single shared interface instead of a custom integration per tool. MCP servers can run locally over stdio (fast, direct system access) or remotely over HTTP/SSE (zero-install, cloud-native, but with network latency). WebMCP is a separate, newer standard: a W3C-incubated browser API (navigator.modelContext), currently in early preview in Chrome, that lets a webpage itself expose callable tools directly to an in-browser AI agent, with no server involved at all.


If you've built anything with Large Language Models recently, you've likely hit the integration wall. You have a capable AI assistant, but getting it to securely talk to your database, your Notion workspace, or your local file system feels like reinventing the wheel every single time.

Every new data source requires a new custom API integration, a new authentication flow, and a new way to parse context. It's the classic integration nightmare.

Enter the Model Context Protocol (MCP).

In this post, we'll demystify MCP, clarify what it actually shares (and doesn't share) with the separate WebMCP browser standard, and walk through building, testing, and securing your first TypeScript MCP server today.


1. What is the Model Context Protocol?

At its core, the Model Context Protocol is an open standard designed to fix how AI models connect to external data.

Think of MCP as the USB-C port for AI applications. Before USB-C, you needed a specific cable for your phone, another for your camera, and another for your laptop. MCP does for AI what USB-C did for hardware: it provides a single, standardized way for an AI client to ask for data, and for the data source (the MCP server) to serve it up securely.

Whether an LLM needs to read a local text file, query a remote PostgreSQL database, or fetch the latest messages from Slack, MCP provides a standardized protocol for that exchange, regardless of which model or which tool is on the other end.


2. The "Why": Solving the Context Bottleneck

Why did this protocol need to exist? Look at the state of AI development before it.

The Fragmented Data Silo Problem

Your user's data doesn't live in one place. It's scattered across cloud drives, internal databases, and local machines. Historically, if you wanted your AI app to access these silos, you had to write custom API wrappers for each one. If you wanted to switch from one LLM provider to another, or from a cloud model to a local one, you often had to rewrite your entire context-fetching logic.

Feeding the Context Window

Modern LLMs have massive context windows, some can ingest entire books in a single prompt. But the bottleneck isn't the size of the window anymore, it's the plumbing required to get the right data into that window securely and efficiently.

MCP solves this by decoupling the AI model from the data source:

  • MCP clients (like an AI chat interface or IDE) don't need to know how to query a database.
  • MCP servers (lightweight applications wrapping your data) don't need to know which LLM is asking for the data.

They just speak the same protocol. You write an MCP server for your data once, and any MCP-compatible AI client can understand it.


3. Local MCP vs. Remote MCP: The Real Divide

MCP servers can run in two places, and this, not "MCP vs WebMCP", is the practical distinction most developers actually have to choose between.

Local MCP (stdio)

Local MCP servers run directly on the user's machine and communicate over standard input/output. This is what most desktop AI tools and IDE integrations use today.

Where it lives: Directly on the user's local machine. What it does: Gives AI tools direct access to local file systems, secure backend databases, and local developer environments. If you want an AI coding assistant to read your local Git repository, this is the setup you use.

Pros:

  • Strong security by default. Data never leaves the local machine unless the user explicitly allows it.
  • Low latency. Reading local files or querying a local database via stdio is fast, since there's no network round trip.
  • Deep system access. Capable of executing local terminal commands, reading log files, and integrating deeply with IDEs.

Cons:

  • Deployment friction. Users often need to install a local binary or runtime environment (like Node.js) to run the server.

Remote MCP (HTTP/SSE)

Remote MCP servers use the same protocol, but communicate over HTTP and Server-Sent Events instead of stdio, letting a web-based AI client reach a cloud-hosted server without the user installing anything locally.

Where it lives: Cloud-hosted endpoints, reachable over the internet. What it does: Lets web-native AI apps securely access cloud data, interact with web APIs, and pull context from a hosted service.

Pros:

  • Zero-install experience. Users don't need to touch a terminal.
  • Cloud-native. Integrates naturally with SaaS APIs and cloud databases.

Cons:

  • Network latency. Every context request requires a network round trip.
  • Security overhead. Requires robust authentication (OAuth, API keys) and strict CORS configuration to prevent unauthorized access.

4. What About WebMCP?

WebMCP is a different thing entirely, and it's worth being precise about the distinction, since the two get conflated often.

WebMCP (formally the Web Model Context Protocol) is a proposed W3C web standard, incubated by the W3C Web Machine Learning Community Group, and it's currently in early preview in Chrome. Rather than describing how an AI client talks to a separate server, WebMCP defines a browser API, navigator.modelContext, that lets a webpage itself register tools an in-browser AI agent can call directly: searching, filling out a form, adding an item to a cart, and so on. Because those tools run entirely inside the open tab, there's no backend MCP server involved at all.

The two standards are meant to complement each other. If your use case is "let an agent read my database," you want local or remote MCP. If your use case is "let an agent click around and fill out forms on my live webpage," that's WebMCP. { const totalElement = document.querySelector('#cart-total'); return totalElement ? totalElement.innerText : "$0.00"; } }); }

Notice the difference? There are no ports, no stdio, and no network transports. It’s just standard DOM manipulation exposed to whatever AI agent happens to be active in the user's browser tab.'" type="suggestion">

---

## 5. Implementation Tutorial: Building a Task Manager MCP Server in TypeScript

Let's get hands-on. Rather than a single hardcoded lookup, we'll build a local TypeScript MCP server that manages a task list, exposing three tools: listing tasks, creating a task, and marking one complete.

### Step 1: Initialize the Project

Set up your project directory and install the necessary dependencies, including TypeScript.

```bash
mkdir task-manager-mcp
cd task-manager-mcp
npm init -y

# Install dependencies
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
npx tsc --init

Step 2: Define the MCP Server

Create your main server file (index.ts). We'll initialize the server, register three tools, and implement the logic that keeps them in sync with an in-memory task list.

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
    CallToolRequestSchema,
    ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';

// Define our internal types
interface Task {
    id: number;
    title: string;
    done: boolean;
}

// 1. Initialize the Server
const server = new Server({
    name: "Task Manager Context Server",
    version: "1.0.0"
}, {
    capabilities: { tools: {} }
});

// In-memory task store. In production, this would be a real database.
let tasks: Task[] = [
    { id: 1, title: "Write MCP tutorial", done: false },
    { id: 2, title: "Review pull request", done: false }
];
let nextId = 3;

// 2. Define the available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
    return {
        tools: [
            {
                name: "list_tasks",
                description: "List all tasks, optionally filtered by completion status.",
                inputSchema: {
                    type: "object",
                    properties: {
                        done: { type: "boolean", description: "Filter by status." }
                    }
                }
            },
            {
                name: "create_task",
                description: "Create a new task with the given title.",
                inputSchema: {
                    type: "object",
                    properties: {
                        title: { type: "string", description: "The task title" }
                    },
                    required: ["title"]
                }
            },
            {
                name: "complete_task",
                description: "Mark an existing task as done by its id.",
                inputSchema: {
                    type: "object",
                    properties: {
                        id: { type: "number", description: "The task id" }
                    },
                    required: ["id"]
                }
            }
        ]
    };
});

// 3. Define the execution logic for each tool
server.setRequestHandler(CallToolRequestSchema, async (request) => {
    const { name, arguments: args } = request.params;

    if (name === "list_tasks") {
        const filtered = (args && typeof args.done === "boolean")
            ? tasks.filter((t) => t.done === args.done)
            : tasks;

        return {
            content: [{ type: "text", text: JSON.stringify(filtered, null, 2) }]
        };
    }

    if (name === "create_task") {
        if (!args || typeof args.title !== "string") {
            throw new Error("A non-empty 'title' string is required.");
        }

        const task: Task = { id: nextId++, title: args.title, done: false };
        tasks.push(task);

        return {
            content: [{ type: "text", text: `Created task #${task.id}: "${task.title}"` }]
        };
    }

    if (name === "complete_task") {
        if (!args || typeof args.id !== "number") {
            throw new Error("A valid task 'id' number is required.");
        }

        const task = tasks.find((t) => t.id === args.id);
        if (!task) throw new Error(`No task found with id ${args.id}.`);

        task.done = true;

        return {
            content: [{ type: "text", text: `Marked task #${task.id} as complete.` }]
        };
    }

    throw new Error("Tool not found");
});

// 4. Start the server via stdio
async function run() {
    const transport = new StdioServerTransport();
    await server.connect(transport);
    console.error("Task Manager MCP Server is running..."); // Log to stderr so it doesn't break stdout JSON!
}

run().catch(console.error);

Step 3: Testing and Debugging with MCP Inspector

Before connecting a live AI to your code, you need to verify it works. Because stdio servers communicate via raw JSON over the command line, manual testing is difficult.

Anthropic provides an official testing tool called the MCP Inspector. Run it directly against your TypeScript file:

npx @modelcontextprotocol/inspector npx tsx index.ts

This commands spins up a local web interface (usually at http://localhost:5173). From there, you can view your registered tools, simulate API calls, pass in JSON arguments, and ensure your create_task and list_tasks functions are behaving exactly as expected before the AI ever sees them.

Step 4: Connecting the Client (Claude Desktop Example)

Once tested, you need to tell your AI client where your server lives. If you are using the Claude Desktop app, you configure this by editing the claude_desktop_config.json file.

Add your new server to the configuration:

{
  "mcpServers": {
    "task-manager": {
      "command": "npx",
      "args": [
        "tsx",
        "/absolute/path/to/your/task-manager-mcp/index.ts"
      ]
    }
  }
}

Restart Claude Desktop. When you ask, "Add a task to review the Q3 report, then show me what's still open," the AI will automatically chain your create_task and list_tasks tools together to fulfill the request.


6. Going Remote: SSE and Security Execution

What if you want to host this server in the cloud (Remote MCP) so web-based agents can use it? You must swap the StdioServerTransport for HTTP Server-Sent Events (SSE) and add strict security.

Here is how you wrap your MCP server in an Express app with Bearer Token authentication and CORS protection:

import express from 'express';
import cors from 'cors';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';

const app = express();

// 1. Strict CORS Configuration
app.use(cors({
    origin: 'https://your-trusted-ai-client.com', // DO NOT use '*' in production
    methods: ['GET', 'POST']
}));

// 2. Authentication Middleware
const requireAuth = (req: express.Request, res: express.Response, next: express.NextFunction) => {
    const authHeader = req.headers.authorization;
    if (authHeader !== `Bearer ${process.env.MCP_API_KEY}`) {
        return res.status(401).json({ error: "Unauthorized" });
    }
    next();
};

let transport: SSEServerTransport;

// 3. SSE Connection Endpoint (GET)
app.get('/mcp', requireAuth, async (req, res) => {
    transport = new SSEServerTransport('/mcp/messages', res);
    await server.connect(transport);
});

// 4. Message Handling Endpoint (POST)
app.post('/mcp/messages', requireAuth, express.json(), async (req, res) => {
    if (transport) {
        await transport.handlePostMessage(req, res);
    } else {
        res.status(503).send("SSE connection not established");
    }
});

app.listen(3000, () => {
    console.log("Remote MCP Server running securely on port 3000");
});

By enforcing standard web security practices, you ensure that only authorized AI clients can access your tools over the network.


7. Conclusion: The Future of AI Interoperability

Development is moving away from monolithic AI applications that try to do everything, toward a modular ecosystem. MCP is leading that shift on the server side, and WebMCP is doing the same thing natively inside the browser—two distinct but complementary standards.

By adopting MCP with robust languages like TypeScript, implementing proper testing workflows, and understanding the security requirements of remote deployments, you're future-proofing your applications. You write your context servers once, and as newer, smarter LLMs are released, they plug directly into your existing infrastructure.

The era of writing bespoke API wrappers for every new LLM feature is ending. The era of universal AI context is just beginning.


Frequently Asked Questions

What is the Model Context Protocol? MCP is an open standard that standardizes how AI models connect to external tools and data sources. You write one MCP server, and any MCP-compatible client can use it.

What is the actual difference between local and remote MCP? Local MCP servers run on the user's machine and communicate over stdio. Remote MCP servers run in the cloud, communicate over HTTP/SSE, and require explicit security measures like API keys and CORS restrictions.

Is WebMCP the same thing as remote MCP? No. Remote MCP is standard MCP using HTTP/SSE. WebMCP is a distinct, W3C-incubated browser standard where a webpage exposes tools directly to an in-page agent via the navigator.modelContext API.

How do I test my MCP server before connecting an AI? Use the @modelcontextprotocol/inspector CLI tool. It launches a local web UI where you can manually trigger your tools and inspect the JSON responses.

How do I connect my local server to Claude? You provide the execution command (e.g., node or npx) and the path to your server script inside the mcpServers object in your claude_desktop_config.json file.


AA
Written byCore Contributor

Atif Ali

Full Stack Engineer

Full-Stack Engineer at Groooh with extensive expertise in cross-platform mobile development and cloud systems. Specializes in building unified digital ecosystems using React Native, Next.js, and PostgreSQL. Known for writing maintainable, test-driven code and optimizing app performance from database query tuning down to 60fps mobile UI interactions.

Have a Project in Mind?

Ready to build your next breakthrough product?

Let’s collaborate on your architecture roadmap, MVP sprint, or full product build with our senior team.

Start a Project

Relevant Blogs