Back to Codex
Dev Tools·
advanced
·15 min read·Apr 4, 2026

How to Build a Custom MCP Server from Scratch

Learn how to create your own MCP server using the TypeScript or Python SDK. Expose custom tools and resources for AI agents to use.

custom serverTypeScriptPythonSDKdevelopmenttools

Build a Custom MCP Server from Scratch

When existing MCP servers don't cover your use case, you can build your own. This guide walks through creating a custom MCP server using the official SDKs.

Choosing Your Language

MCP provides official SDKs for:

  • TypeScript/Node.js
    code
    @modelcontextprotocol/sdk
  • Python
    code
    mcp
    package

TypeScript Server Example

Project Setup

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

Basic Server Structure

typescript
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';

// Create the server
const server = new Server(
  { name: 'my-custom-server', version: '1.0.0' },
  { capabilities: { tools: {} } }
);

// Define available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: 'get_weather',
        description: 'Get current weather for a location',
        inputSchema: {
          type: 'object',
          properties: {
            city: { type: 'string', description: 'City name' },
          },
          required: ['city'],
        },
      },
    ],
  };
});

// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === 'get_weather') {
    const { city } = request.params.arguments;
    // Your custom logic here
    const weather = await fetchWeather(city);
    return {
      content: [
        { type: 'text', text: JSON.stringify(weather) },
      ],
    };
  }
  throw new Error(`Unknown tool: ${request.params.name}`);
});

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

Python Server Example

python
import asyncio
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent

app = Server("my-custom-server")

@app.list_tools()
async def list_tools():
    return [
        Tool(
            name="get_weather",
            description="Get current weather",
            inputSchema={
                "type": "object",
                "properties": {
                    "city": {"type": "string"}
                },
                "required": ["city"]
            }
        )
    ]

@app.call_tool()
async def call_tool(name, arguments):
    if name == "get_weather":
        weather = await fetch_weather(arguments["city"])
        return [TextContent(type="text", text=str(weather))]

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

asyncio.run(main())

Adding Resources

Resources expose data that AI agents can read:

typescript
server.setRequestHandler(ListResourcesRequestSchema, async () => {
  return {
    resources: [
      {
        uri: 'myapp://config',
        name: 'Application Config',
        mimeType: 'application/json',
      },
    ],
  };
});

Testing Your Server

Use the MCP Inspector:

bash
npx @modelcontextprotocol/inspector your-server-command

Publishing

Publish to npm for easy distribution:

bash
npm publish

Then users can configure it as:

json
{
  "mcpServers": {
    "my-server": {
      "command": "npx",
      "args": ["-y", "my-mcp-server-package"]
    }
  }
}