Claude Code/
Lesson

Existing MCPWhat is mcp?Model Context Protocol - a standard that lets AI tools connect to external services like databases, issue trackers, or APIs. servers cover many common use cases, but sometimes you need something custom. Maybe your company has an internal APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses.. Maybe you want to connect Claude to a service that nobody has built a server for yet. In those cases, you build your own.

This lesson walks you through building a simple MCP server in TypeScript, from setup to testing.

When to build a custom server

Build a custom MCPWhat is mcp?Model Context Protocol - a standard that lets AI tools connect to external services like databases, issue trackers, or APIs. server when:

  • Your system has no existing MCP server: Your company's internal APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses., a niche SaaS tool, a custom database
  • You need custom business logic: Existing servers expose raw operations, but you want guided workflows
  • You will reuse the integration: If you only need it once, a direct API call in your code is simpler
  • You want to share it: Building an MCP server means anyone with an MCP-compatible AI tool can use it

Do not build a custom server when a direct API call in a script or function would suffice. MCP adds value through standardization and reusability, if neither matters for your use case, keep it simple.

Good to know
If you only need a quick integration for yourself, skip MCP and write a direct API call. MCP adds value when the integration is reusable across conversations, shared with teammates, or used by multiple AI tools. For one-off tasks, the overhead of building a server isn't worth it.
02

Project setup

Start by creating a new Node.js project:

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

Create a tsconfig.json:

json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}

The key dependencies:

  • @modelcontextprotocol/sdk, the official MCPWhat is mcp?Model Context Protocol - a standard that lets AI tools connect to external services like databases, issue trackers, or APIs. SDKWhat is sdk?A pre-built library from a service provider that wraps their API into convenient functions you call in your code instead of writing raw HTTP requests. for TypeScript
  • zod, schemaWhat is schema?A formal definition of the structure your data must follow - which fields exist, what types they have, and which are required. validation library (used by the SDK for input validation)

03

A minimal MCPWhat is mcp?Model Context Protocol - a standard that lets AI tools connect to external services like databases, issue trackers, or APIs. server

Create src/index.ts:

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

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

// Define a tool
server.tool(
  "get_weather",
  "Get the current weather for a city. Use this when the user asks about weather conditions, temperature, or forecasts for a specific location.",
  {
    city: {
      type: "string",
      description: "The city name, e.g. 'Paris' or 'New York'"
    }
  },
  async ({ city }) => {
    // In a real server, you would call a weather API here
    const weather = {
      city,
      temperature: 22,
      condition: "Partly cloudy",
      humidity: 65
    };

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

// Connect using stdio transport
const transport = new StdioServerTransport();
await server.connect(transport);

Let's break down each part.

The server instance

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

The McpServer class is the core of your server. The name and version identify your server to clients. Keep the name descriptive, it appears in the AI application's server list.

Defining a tool

server.tool(
  "get_weather",           // Tool name
  "Get the current...",    // Description (the AI reads this!)
  { city: { ... } },       // Input schema
  async ({ city }) => { }  // Handler function
);

The four arguments to server.tool():

  1. Name: A short, descriptive identifier. Use snake_case. The AI uses this name internally.
  2. Description: A natural-language explanation of what the tool does and when to use it. This is the most important part, the AI reads this description to decide whether to call this tool. Be specific.
  3. Input schemaWhat is schema?A formal definition of the structure your data must follow - which fields exist, what types they have, and which are required.: A JSONWhat is json?A text format for exchanging data between systems. It uses key-value pairs and arrays, and every programming language can read and write it. Schema object describing the parameters. Each parameter has a type and description.
  4. Handler: An async function that receives the validated inputs and returns a result.

The input schema

The input schema uses JSON Schema types:

{
  city: {
    type: "string",
    description: "The city name"
  },
  units: {
    type: "string",
    description: "Temperature units: 'celsius' or 'fahrenheit'",
    enum: ["celsius", "fahrenheit"]
  },
  include_forecast: {
    type: "boolean",
    description: "Whether to include a 5-day forecast"
  }
}

Common types: string, number, boolean, array, object. Use enum to restrict allowed values. Use description on every parameter, the AI reads these to understand what to pass.

AI pitfall
When AI generates MCP tool definitions, it often writes vague parameter descriptions like "the input value" or "data to process." The AI that calls your tool reads these descriptions to decide what to pass, if they're vague, you'll get garbage inputs. Always write descriptions as if explaining to a colleague who has never seen your code.
Schema propertyPurposeExample
typeData type of the parameter"string", "number", "boolean"
descriptionWhat the AI reads to understand the parameter"The city name, e.g. 'Paris'"
enumRestrict to specific allowed values["celsius", "fahrenheit"]
defaultFallback if not provided"celsius"

Returning results

Tools return a content array with typed blocks:

return {
  content: [
    {
      type: "text",
      text: "The weather in Paris is 22°C and partly cloudy."
    }
  ]
};

For structured data, return JSON as a string:

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

Error handling

When something goes wrong, return an error result instead of throwing:

server.tool("get_weather", "...", { city: { type: "string" } },
  async ({ city }) => {
    try {
      const data = await fetchWeatherAPI(city);
      return {
        content: [{ type: "text", text: JSON.stringify(data) }]
      };
    } catch (error) {
      return {
        isError: true,
        content: [
          {
            type: "text",
            text: `Failed to fetch weather for "${city}": ${error.message}`
          }
        ]
      };
    }
  }
);

The isError: true flag tells the AI that the tool call failed, so it can inform the user or try a different approach.

The transport layer

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

MCP supports two transport types:

  • Stdio: Communication via standard input/output. This is the most common for local servers. The host application starts your server as a child process and communicates over stdin/stdout.
  • SSEWhat is sse?Server-Sent Events - a one-way, server-to-client push mechanism built on plain HTTP, simpler than WebSockets for server push. (Server-Sent EventsWhat is server-sent events?A one-way, server-to-client push mechanism built on plain HTTP - simpler than WebSockets when clients never need to send data back.): Communication over HTTPWhat is http?The protocol browsers and servers use to exchange web pages, API data, and other resources, defining how requests and responses are formatted.. Used for remote servers that run on a different machine.

For local development, always use stdio. It is simpler and does not require networking.

04

Writing good tool descriptions

The description is what the AI reads to decide when to use your tool. Bad descriptions lead to tools being ignored or misused.

Bad description:

"Gets weather"

Too short. The AI does not know when to use it or what it returns.

Good description:

"Get the current weather for a city. Use this when the user asks about
weather conditions, temperature, humidity, or forecasts for a specific
location. Returns temperature in Celsius, condition description, and
humidity percentage."

A good description answers three questions:

  1. What does this tool do?
  2. When should the AI use it?
  3. What does it return?

05

Testing with MCPWhat is mcp?Model Context Protocol - a standard that lets AI tools connect to external services like databases, issue trackers, or APIs. Inspector

Before connecting your server to Claude, test it with the MCP Inspector, a web-based tool for debugging MCP servers:

npx @modelcontextprotocol/inspector node dist/index.js

This starts a local web UI where you can:

  • See all tools, resources, and prompts your server exposes
  • Call tools with custom inputs and see the results
  • Debug connection issues and inspect the protocolWhat is protocol?An agreed-upon set of rules for how two systems communicate, defining the format of messages and the expected sequence of exchanges. messages

The Inspector is essential for development. Always test here before configuring your server in Claude Desktop or Claude Code.

Edge case
If your MCP server crashes silently (no error output, just stops responding), the AI application will hang waiting for a response. Always add error handling around your tool handlers and log errors to stderr so you can debug connection issues. The MCP Inspector shows protocol messages that help identify where communication breaks down.
06

Adding your server to Claude

Once your server works in the Inspector, add it to your AI application.

For Claude Desktop, add to claude_desktop_config.json:

json
{
  "mcpServers": {
    "weather": {
      "command": "node",
      "args": ["/absolute/path/to/my-mcp-server/dist/index.js"]
    }
  }
}

For Claude Code:

claude mcp add weather -- node /absolute/path/to/my-mcp-server/dist/index.js

Restart the application. Your tools should now appear in the available tools list, and the AI will use them when relevant.