Building Custom MCP Servers: A Step-by-Step Guide

Ready to build your own MCP server? This guide covers everything from project setup to publishing, with complete TypeScript and Python examples.

Why Build a Custom MCP Server?

While there are hundreds of pre-built MCP servers, you might need one that:

Building an MCP server is surprisingly straightforward — you can have a working server in under 100 lines of code.

Choose Your Language

MCP has official SDKs for both TypeScript and Python:

We'll show both in this guide. Choose based on your team's preference.

TypeScript: Building a Weather Server

Let's build a practical example — an MCP server that provides weather data.

Project Setup

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

Update tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true
  }
}

The Server Code

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

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

// Define a tool
server.tool(
  "get_weather",
  "Get current weather for a city",
  {
    city: z.string().describe("City name"),
    units: z.enum(["celsius", "fahrenheit"]).default("celsius")
  },
  async ({ city, units }) => {
    // In production, call a real weather API
    const response = await fetch(
      `https://api.weatherapi.com/v1/current.json?key=${process.env.WEATHER_API_KEY}&q=${city}`
    );
    const data = await response.json();

    const temp = units === "fahrenheit"
      ? data.current.temp_f + "°F"
      : data.current.temp_c + "°C";

    return {
      content: [{
        type: "text",
        text: `Weather in ${city}: ${temp}, ${data.current.condition.text}`
      }]
    };
  }
);

// Define a resource
server.resource(
  "supported-cities",
  "weather://cities",
  async (uri) => ({
    contents: [{
      uri: uri.href,
      mimeType: "application/json",
      text: JSON.stringify(["New York", "London", "Tokyo", "Paris", "Sydney"])
    }]
  })
);

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

Build and Test

# Build
npx tsc

# Test locally (the server communicates via stdin/stdout)
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | node dist/index.js

Add to Claude Desktop

{
  "mcpServers": {
    "weather": {
      "command": "node",
      "args": ["/path/to/mcp-weather-server/dist/index.js"],
      "env": {
        "WEATHER_API_KEY": "your_key"
      }
    }
  }
}

Python: Building the Same Server

# server.py
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
import httpx
import os
import json

server = Server("weather")

@server.list_tools()
async def list_tools():
    return [
        Tool(
            name="get_weather",
            description="Get current weather for a city",
            inputSchema={
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "City name"},
                    "units": {"type": "string", "enum": ["celsius", "fahrenheit"], "default": "celsius"}
                },
                "required": ["city"]
            }
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict):
    if name == "get_weather":
        city = arguments["city"]
        units = arguments.get("units", "celsius")

        async with httpx.AsyncClient() as client:
            resp = await client.get(
                "https://api.weatherapi.com/v1/current.json",
                params={"key": os.environ["WEATHER_API_KEY"], "q": city}
            )
            data = resp.json()

        temp = f"{data['current']['temp_f']}°F" if units == "fahrenheit" else f"{data['current']['temp_c']}°C"
        return [TextContent(type="text", text=f"Weather in {city}: {temp}, {data['current']['condition']['text']}")]

async def main():
    async with stdio_server() as (read, write):
        await server.run(read, write, server.create_initialization_options())

import asyncio
asyncio.run(main())

Key Concepts

Tools vs Resources vs Prompts

Error Handling

server.tool("risky_operation", "...", { input: z.string() },
  async ({ input }) => {
    try {
      const result = await doSomethingRisky(input);
      return { content: [{ type: "text", text: result }] };
    } catch (error) {
      return {
        content: [{ type: "text", text: `Error: ${error.message}` }],
        isError: true
      };
    }
  }
);

Input Validation

The MCP SDK uses Zod (TypeScript) or JSON Schema (Python) for input validation. Always define strict schemas — they help the AI understand what parameters to provide.

Publishing Your Server

npm (TypeScript)

# Add bin entry to package.json
{
  "bin": { "mcp-weather": "./dist/index.js" },
  "files": ["dist"]
}

# Add shebang to index.ts
#!/usr/bin/env node

# Publish
npm publish

PyPI (Python)

# Use pyproject.toml with entry point
[project.scripts]
mcp-weather = "mcp_weather.server:main"

# Build and publish
python -m build
twine upload dist/*

Submit to MCP Hub

Once published, submit your server to the MCP Hub directory so others can discover it!

Best Practices

FAQ

What language should I use to build an MCP server?

Both TypeScript and Python have official MCP SDKs with excellent documentation. Choose based on your team's expertise. TypeScript is more common in the MCP ecosystem, but Python servers work equally well.

How do I publish my MCP server?

Publish to npm (for TypeScript) or PyPI (for Python). Then submit it to MCP Hub for listing in the directory. Most servers are also hosted on GitHub.

Can MCP servers access the internet?

Yes. MCP servers are regular programs that can make HTTP requests, access databases, read files, or do anything else. The MCP protocol just standardizes how they communicate with AI hosts.

Submit Your Server

Built something cool? Get it listed in the MCP Hub directory

Submit Server →