← Back to Articles
TutorialTypeScriptMCP Server

How to Build Your First MCP Server: A Step-by-Step Tutorial

Learn how to create your own MCP server from scratch using TypeScript. This hands-on tutorial walks you through building a functional MCP server.

By Web MCP GuideFebruary 13, 20264 min read


Building your first MCP server is easier than you might think. In this tutorial, we'll create a simple but functional MCP server that exposes tools for AI applications to use.

If you're new to MCP, start with our introduction to the Model Context Protocol first.

Prerequisites

Before we start, make sure you have:

  • Node.js 18 or later installed

  • Basic TypeScript knowledge

  • A code editor (VS Code recommended)

  • npm or yarn package manager
  • Step 1: Project Setup

    First, create a new directory and initialize your project:

    mkdir my-mcp-server
    cd my-mcp-server
    npm init -y
    npm install @modelcontextprotocol/sdk zod
    npm install -D typescript @types/node tsx

    Create a tsconfig.json:

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

    Step 2: Create the Server

    Create src/index.ts:

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

    // Create the MCP server
    const server = new McpServer({
    name: "my-first-server",
    version: "1.0.0",
    });

    // Define a simple tool
    server.tool(
    "greet",
    "Greet someone by name",
    {
    name: z.string().describe("The name to greet"),
    },
    async ({ name }) => {
    return {
    content: [
    {
    type: "text",
    text: Hello, ${name}! Welcome to MCP.,
    },
    ],
    };
    }
    );

    // Define a calculation tool
    server.tool(
    "calculate",
    "Perform basic math operations",
    {
    operation: z.enum(["add", "subtract", "multiply", "divide"]),
    a: z.number().describe("First number"),
    b: z.number().describe("Second number"),
    },
    async ({ operation, a, b }) => {
    let result: number;
    switch (operation) {
    case "add":
    result = a + b;
    break;
    case "subtract":
    result = a - b;
    break;
    case "multiply":
    result = a * b;
    break;
    case "divide":
    result = b !== 0 ? a / b : NaN;
    break;
    }
    return {
    content: [
    {
    type: "text",
    text: ${a} ${operation} ${b} = ${result},
    },
    ],
    };
    }
    );

    // Start the server
    async function main() {
    const transport = new StdioServerTransport();
    await server.connect(transport);
    console.error("MCP Server running on stdio");
    }

    main().catch(console.error);

    Step 3: Understanding the Code

    Let's break down what we just wrote:

    Server Initialization:

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

    This creates a new MCP server with a name and version that clients can identify.

    Tool Definition:

    server.tool(
    "greet", // Tool name
    "Greet someone", // Description
    { name: z.string() }, // Input schema using Zod
    async ({ name }) => { / handler / }
    );

    Tools are functions that AI can call. We define the name, description, input schema, and handler function.

    Transport:

    const transport = new StdioServerTransport();

    STDIO transport means communication happens through standard input/output — perfect for local servers.

    Step 4: Test Your Server

    Add a script to package.json:

    {
    "scripts": {
    "start": "tsx src/index.ts"
    }
    }

    You can test the server using the MCP Inspector:

    npx @modelcontextprotocol/inspector tsx src/index.ts

    This opens a web interface where you can interact with your server and test your tools.

    Step 5: Connect to Claude Desktop

    To use your server with Claude Desktop, add it to your configuration. On macOS, edit ~/Library/Application Support/Claude/claude_desktop_config.json:

    {
    "mcpServers": {
    "my-first-server": {
    "command": "npx",
    "args": ["tsx", "/path/to/my-mcp-server/src/index.ts"]
    }
    }
    }

    Restart Claude Desktop, and your tools will be available!

    Adding Resources

    MCP servers can also expose resources — read-only data that provides context:

    server.resource(
    "config",
    "config://app",
    async (uri) => ({
    contents: [
    {
    uri: uri.href,
    mimeType: "application/json",
    text: JSON.stringify({
    version: "1.0.0",
    environment: "development",
    }),
    },
    ],
    })
    );

    Adding Prompts

    Prompts are reusable templates for common interactions:

    server.prompt(
    "code-review",
    "Template for code review requests",
    { language: z.string() },
    ({ language }) => ({
    messages: [
    {
    role: "user",
    content: {
    type: "text",
    text: Please review the following ${language} code...,
    },
    },
    ],
    })
    );

    Best Practices

    1. Validate all inputs: Use Zod schemas to ensure type safety
    2. Write clear descriptions: AI uses these to understand when to call your tools
    3. Handle errors gracefully: Return meaningful error messages
    4. Keep tools focused: Each tool should do one thing well
    5. Log for debugging: Use console.error (not console.log) for debug output

    Next Steps

    Now that you've built your first MCP server, explore these resources:

  • MCP Tools vs Resources vs Prompts — Understand the core primitives in depth

  • MCP Security Best Practices — Keep your integrations secure

  • Top 10 MCP Servers — See how others have built their servers

  • MCP Architecture Deep Dive — Understand the protocol internals
  • Prefer Python? Check out Getting Started with MCP in Python.

    The MCP ecosystem is growing rapidly, and your server could be the next great integration!