Introduction to Model Context Protocol (MCP)

Anis MarrouchiAI Bot
By Anis Marrouchi & AI Bot ·

Loading the Text to Speech Audio Player...

The Model Context Protocol (MCP) is an open protocol designed to standardize how applications provide context to Large Language Models (LLMs). Think of MCP as a USB-C port for AI applications. Just as USB-C provides a standardized way to connect devices to various peripherals, MCP offers a standardized way to connect AI models to different data sources and tools.

Why Use MCP?

MCP is particularly useful for building agents and complex workflows on top of LLMs. It provides:

  • A growing list of pre-built integrations that your LLM can directly plug into.
  • The flexibility to switch between LLM providers and vendors.
  • Best practices for securing your data within your infrastructure.

General Architecture

MCP follows a client-server architecture where a host application can connect to multiple servers:

MCP Architecture
  • MCP Hosts: Programs like Claude Desktop, IDEs, or AI tools that want to access data through MCP.
  • MCP Clients: Protocol clients that maintain 1:1 connections with servers.
  • MCP Servers: Lightweight programs that each expose specific capabilities through the standardized Model Context Protocol.
  • Local Data Sources: Your computer's files, databases, and services that MCP servers can securely access.
  • Remote Services: External systems available over the internet (e.g., through APIs) that MCP servers can connect to.

Building an MCP Server with TypeScript

Let's walk through building a simple MCP weather server using TypeScript.

Prerequisites

  • Familiarity with TypeScript.
  • Node.js version 16 or higher installed.

Setting Up the Project

First, create a new directory for your project and initialize it:

mkdir weather
cd weather
npm init -y

Install the necessary dependencies:

npm install @modelcontextprotocol/sdk zod
npm install -D @types/node typescript

Create the project structure:

mkdir src
touch src/index.ts

Update your package.json to include the build script:

{
  "type": "module",
  "bin": {
    "weather": "./build/index.js"
  },
  "scripts": {
    "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\""
  },
  "files": [
    "build"
  ]
}

Create a tsconfig.json file:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

Building the Server

Start by importing the necessary packages:

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

Initialize the server instance:

const NWS_API_BASE = "https://api.weather.gov";
const USER_AGENT = "weather-app/1.0";
 
const AlertsArgumentsSchema = z.object({
  state: z.string().length(2),
});
 
const ForecastArgumentsSchema = z.object({
  latitude: z.number().min(-90).max(90),
  longitude: z.number().min(-180).max(180),
});
 
const server = new Server(
  {
    name: "weather",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

Implement the tool listing:

server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "get-alerts",
        description: "Get weather alerts for a state",
        inputSchema: {
          type: "object",
          properties: {
            state: {
              type: "string",
              description: "Two-letter state code (e.g. CA, NY)",
            },
          },
          required: ["state"],
        },
      },
      {
        name: "get-forecast",
        description: "Get weather forecast for a location",
        inputSchema: {
          type: "object",
          properties: {
            latitude: {
              type: "number",
              description: "Latitude of the location",
            },
            longitude: {
              type: "number",
              description: "Longitude of the location",
            },
          },
          required: ["latitude", "longitude"],
        },
      },
    ],
  };
});

Implement the tool execution:

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;
 
  try {
    if (name === "get-alerts") {
      const { state } = AlertsArgumentsSchema.parse(args);
      const stateCode = state.toUpperCase();
 
      const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
      const alertsData = await makeNWSRequest(alertsUrl);
 
      if (!alertsData) {
        return {
          content: [
            {
              type: "text",
              text: "Failed to retrieve alerts data",
            },
          ],
        };
      }
 
      const features = alertsData.features || [];
      if (features.length === 0) {
        return {
          content: [
            {
              type: "text",
              text: `No active alerts for ${stateCode}`,
            },
          ],
        };
      }
 
      const formattedAlerts = features.map(formatAlert).slice(0, 20);
      const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`;
 
      return {
        content: [
          {
            type: "text",
            text: alertsText,
          },
        ],
      };
    } else if (name === "get-forecast") {
      const { latitude, longitude } = ForecastArgumentsSchema.parse(args);
 
      const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`;
      const pointsData = await makeNWSRequest(pointsUrl);
 
      if (!pointsData) {
        return {
          content: [
            {
              type: "text",
              text: `Failed to retrieve grid point data for coordinates: ${latitude}, ${longitude}. This location may not be supported by the NWS API (only US locations are supported).`,
            },
          ],
        };
      }
 
      const forecastUrl = pointsData.properties?.forecast;
      if (!forecastUrl) {
        return {
          content: [
            {
              type: "text",
              text: "Failed to get forecast URL from grid point data",
            },
          ],
        };
      }
 
      const forecastData = await makeNWSRequest(forecastUrl);
      if (!forecastData) {
        return {
          content: [
            {
              type: "text",
              text: "Failed to retrieve forecast data",
            },
          ],
        };
      }
 
      const periods = forecastData.properties?.periods || [];
      if (periods.length === 0) {
        return {
          content: [
            {
              type: "text",
              text: "No forecast periods available",
            },
          ],
        };
      }
 
      const formattedForecast = periods.map((period: ForecastPeriod) =>
        [
          `${period.name || "Unknown"}:`,
          `Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`,
          `Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`,
          `${period.shortForecast || "No forecast available"}`,
          "---",
        ].join("\n")
      );
 
      const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`;
 
      return {
        content: [
          {
            type: "text",
            text: forecastText,
          },
        ],
      };
    } else {
      throw new Error(`Unknown tool: ${name}`);
    }
  } catch (error) {
    if (error instanceof z.ZodError) {
      throw new Error(
        `Invalid arguments: ${error.errors
          .map((e) => `${e.path.join(".")}: ${e.message}`)
          .join(", ")}`
      );
    }
    throw error;
  }
});

Finally, implement the main function to run the server:

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Weather MCP Server running on stdio");
}
 
main().catch((error) => {
  console.error("Fatal error in main():", error);
  process.exit(1);
});

Using the Server with a Terminal JS App

To use the server with a terminal JS app, you need to configure the MCP client to connect to the server. Here's a basic example:

const { ClientSession, StdioServerParameters } = require('@modelcontextprotocol/sdk');
const { stdio_client } = require('@modelcontextprotocol/sdk/client/stdio');
 
async function connectToServer(serverScriptPath) {
  const serverParams = new StdioServerParameters({
    command: 'node',
    args: [serverScriptPath],
  });
 
  const transport = await stdio_client(serverParams);
  const session = new ClientSession(transport.stdio, transport.write);
 
  await session.initialize();
  return session;
}
 
async function main() {
  const session = await connectToServer('./build/index.js');
  const response = await session.listTools();
  console.log('Available tools:', response.tools);
}
 
main();

This script connects to the MCP server and lists the available tools. You can extend this to handle tool calls and process responses.

Conclusion

The Model Context Protocol (MCP) is a powerful tool for standardizing AI model integration. By following this guide, you can build and use an MCP server with TypeScript, enabling seamless integration with various data sources and tools. Whether you're building complex workflows or simple applications, MCP provides the flexibility and security needed to enhance your AI projects.

Reference


Want to read more tutorials? Check out our latest tutorial on Explore Improved Image and Video Segmentation with SAM 2 for Accurate Context-Aware Results.

Discuss Your Project with Us

We're here to help with your web development needs. Schedule a call to discuss your project and how we can assist you.

Let's find the best solutions for your needs.