title: 'Server Setup with TypeScript' description: 'Building your first MCP server with the TypeScript SDK'

Server Setup with TypeScript

In this lesson, we'll build a complete MCP server using the TypeScript SDK. By the end, you'll have a working server that exposes tools and resources.

Prerequisites

Before starting, ensure you have:

  • Node.js 18 or higher installed
  • Basic TypeScript knowledge
  • A code editor (VS Code recommended)

Project Setup

Create a new directory and initialize a Node.js project:

mkdir my-mcp-server
cd my-mcp-server
npm init -y

Install the MCP SDK and dependencies:

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

Create a tsconfig.json:

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

Building Your First Server

Create src/index.ts with a basic server structure:

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

// Create server instance
const server = new Server(
  {
    name: "my-first-server",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "get_current_time",
        description: "Get the current time in a specified timezone",
        inputSchema: {
          type: "object",
          properties: {
            timezone: {
              type: "string",
              description: "IANA timezone (e.g., 'America/New_York')",
            },
          },
          required: ["timezone"],
        },
      },
    ],
  };
});

// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  if (name === "get_current_time") {
    const timezone = args.timezone as string;
    const now = new Date().toLocaleString("en-US", { timeZone: timezone });

    return {
      content: [
        {
          type: "text",
          text: `Current time in ${timezone}: ${now}`,
        },
      ],
    };
  }

  throw new Error(`Unknown tool: ${name}`);
});

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

main().catch((error) => {
  console.error("Server error:", error);
  process.exit(1);
});

Understanding the Code

Server Initialization

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

We create a server instance with metadata and declare capabilities. Here we're only enabling tools.

Request Handlers

MCP uses a request-handler pattern. We register handlers for different request types:

ListToolsRequestSchema: Returns available tools and their schemas

CallToolRequestSchema: Executes tool calls with provided arguments

Transport Layer

const transport = new StdioServerTransport();
await server.connect(transport);

We use stdio transport, meaning the server communicates via standard input/output. This is ideal for local development.

Adding More Tools

Let's add a calculation tool:

server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "get_current_time",
        description: "Get the current time in a specified timezone",
        inputSchema: {
          type: "object",
          properties: {
            timezone: { type: "string", description: "IANA timezone" },
          },
          required: ["timezone"],
        },
      },
      {
        name: "calculate",
        description: "Perform basic arithmetic calculations",
        inputSchema: {
          type: "object",
          properties: {
            operation: {
              type: "string",
              enum: ["add", "subtract", "multiply", "divide"],
              description: "The operation to perform",
            },
            a: { type: "number", description: "First operand" },
            b: { type: "number", description: "Second operand" },
          },
          required: ["operation", "a", "b"],
        },
      },
    ],
  };
});

// Update tool handler
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  if (name === "get_current_time") {
    // ... existing time tool code
  }

  if (name === "calculate") {
    const { operation, a, b } = args as {
      operation: string;
      a: number;
      b: number;
    };

    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":
        if (b === 0) throw new Error("Division by zero");
        result = a / b;
        break;
      default:
        throw new Error(`Unknown operation: ${operation}`);
    }

    return {
      content: [
        {
          type: "text",
          text: `${a} ${operation} ${b} = ${result}`,
        },
      ],
    };
  }

  throw new Error(`Unknown tool: ${name}`);
});

Testing Your Server

Add a build script to package.json:

{
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsx src/index.ts"
  }
}

Run in development mode:

npm run dev

The server is now waiting for JSON-RPC messages on stdin. You can test it manually by sending:

{"jsonrpc":"2.0","id":1,"method":"tools/list"}

Or integrate it with Claude Desktop by adding to your MCP configuration file.

Error Handling

Always validate inputs and handle errors gracefully:

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  try {
    const { name, arguments: args } = request.params;

    // Validate args exist
    if (!args) {
      throw new Error("Missing arguments");
    }

    // Tool logic here...

  } catch (error) {
    return {
      content: [
        {
          type: "text",
          text: `Error: ${error instanceof Error ? error.message : "Unknown error"}`,
        },
      ],
      isError: true,
    };
  }
});

Next Steps

You now have a working MCP server in TypeScript! In the next lesson, we'll build the same concepts using Python, then dive deeper into implementing more complex tools.

Key takeaways:

  • MCP servers use a request-handler pattern
  • Tools are defined with JSON Schema for inputs
  • stdio transport is great for local development
  • Always validate inputs and handle errors
Server Setup with TypeScript - Compass | Nick Treffiletti — MCP, AI Agents & Platform Engineering