How to Build an MCP Server

A complete, hands-on developer guide to building, testing, deploying, and publishing custom Model Context Protocol servers in TypeScript and Python. New to MCP itself? Start with What is an MCP? first.

Overview & Core Concepts

An MCP Serveris a lightweight process that exposes capabilities — tools, data resources, and prompt templates — over standard JSON-RPC 2.0. Any MCP-compatible AI client (Claude Desktop, Claude Code, Cursor, and others) can connect to your server and use those capabilities on demand, without you writing custom integration code for each client.

Choosing Your Stack

MCP has officially maintained SDKs in several languages. TypeScript and Python are the most mature, with the largest ecosystem of example servers to learn from — this guide covers both in full. If your project already lives in another language, these SDKs work the same way conceptually:

Building with TypeScript

Install the official SDK and a schema validator:

npm install @modelcontextprotocol/sdk zod

Here is a complete working TypeScript server that exposes a tool, a resource, and a prompt:

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

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

// --- Tool: calculate_sum ---
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "calculate_sum",
      description: "Add two numbers together",
      inputSchema: {
        type: "object",
        properties: {
          a: { type: "number", description: "First number" },
          b: { type: "number", description: "Second number" }
        },
        required: ["a", "b"]
      }
    }
  ]
}));

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === "calculate_sum") {
    const { a, b } = request.params.arguments as { a: number; b: number };
    return { content: [{ type: "text", text: String(a + b) }] };
  }
  throw new Error("Tool not found");
});

// --- Resource: app config ---
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
  resources: [
    {
      uri: "config://app",
      name: "Application Config",
      description: "Static app configuration as JSON",
      mimeType: "application/json"
    }
  ]
}));

server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  if (request.params.uri === "config://app") {
    return {
      contents: [
        {
          uri: "config://app",
          mimeType: "application/json",
          text: JSON.stringify({ maxResults: 10, environment: "production" })
        }
      ]
    };
  }
  throw new Error("Resource not found");
});

// --- Prompt: summarize ---
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
  prompts: [
    {
      name: "summarize",
      description: "Summarize the provided text in one paragraph",
      arguments: [{ name: "text", description: "The text to summarize", required: true }]
    }
  ]
}));

server.setRequestHandler(GetPromptRequestSchema, async (request) => {
  if (request.params.name === "summarize") {
    const text = request.params.arguments?.text ?? "";
    return {
      messages: [
        {
          role: "user",
          content: { type: "text", text: `Summarize the following text in one paragraph:\n\n${text}` }
        }
      ]
    };
  }
  throw new Error("Prompt not found");
});

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
}

main().catch(console.error);

Building with Python

Python developers can use the official mcplibrary’s FastMCP wrapper, which turns plain decorated functions into MCP primitives:

pip install "mcp[cli]"

The same tool, resource, and prompt as above, in a few lines of Python:

from mcp.server.fastmcp import FastMCP
import json

mcp = FastMCP("My Python Tool")

@mcp.tool()
def add_numbers(a: int, b: int) -> int:
    """Add two numbers together."""
    return a + b

@mcp.resource("config://app")
def get_config() -> str:
    """Return static app configuration as JSON."""
    return json.dumps({"max_results": 10, "environment": "production"})

@mcp.prompt()
def summarize(text: str) -> str:
    """Summarize the provided text in one paragraph."""
    return f"Summarize the following text in one paragraph:\n\n{text}"

if __name__ == "__main__":
    mcp.run()

Tools, Resources & Prompts

  • Tools: Functions that execute side effects or perform calculations. Always provide clear parameters and a descriptive JSON schema so the AI model knows when and how to call them — see calculate_sum / add_numbers above.
  • Resources: Read-only data sources identified by URIs (e.g. config://app, file:///logs/app.log, or db://users/123). Clients can list and read them without invoking a tool.
  • Prompts: Reusable template workflows, like summarizeabove, that users can invoke directly inside their AI client’s interface.

Testing Locally

Test your server directly in your browser without wiring it into a full AI client, using the official MCP Inspector:

npx @modelcontextprotocol/inspector node dist/index.js

This launches an interactive UI where you can call tools, read resources, and get prompts, while watching the raw JSON-RPC messages in real time.

Once it works in Inspector, point Claude Desktop at it directly:

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

For a Python server, swap the command for uv run --directory /absolute/path/to/project server.py. Fully restart the client (quit and reopen) after editing its config — see the LLM Agents Guide for the exact config file locations. Claude Code can add a local server directly from the command line instead:

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

Deploying a Remote Server

Everything above uses the stdio transport: your AI client launches the server as a local subprocess. For a server that multiple people share, or that needs to run somewhere other than the user’s machine, expose it over HTTP/SSE instead as a remote server. One common way to host a remote MCP server is on Cloudflare Workers using the agents package’s McpAgent class:

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

export class MyMCP extends McpAgent {
  server = new McpServer({ name: "my-remote-mcp", version: "1.0.0" });

  async init() {
    this.server.tool(
      "calculate_sum",
      { a: z.number(), b: z.number() },
      async ({ a, b }) => ({
        content: [{ type: "text", text: String(a + b) }]
      })
    );
  }
}

export default {
  fetch(request: Request, env: unknown, ctx: ExecutionContext) {
    const url = new URL(request.url);
    if (url.pathname === "/mcp") {
      return MyMCP.serve("/mcp").fetch(request, env, ctx);
    }
    return new Response("Not found", { status: 404 });
  }
};
npx wrangler deploy

See Cloudflare’s MCP documentation for authentication, session handling, and other remote-server details.

Publishing & Listing on AllMCPs

Once your server works locally, get it in front of users:

  1. Publish your package to npm (Node.js) or PyPI (Python), or host it on GitHub with a tagged release.
  2. Write a README.md with a clear claude_desktop_config.json snippet and a list of any required environment variables or API keys.
  3. Pick an OSS license — MIT and Apache-2.0 are the most widely accepted for MCP servers.
  4. Tag a semantic-versioned release so users can pin a specific version.
  5. Head over to our Submit Page to list your server on AllMCPs and reach thousands of AI developers.

FAQ

Which language should I use to build an MCP server?

TypeScript and Python have the most mature official SDKs and the most existing example servers to learn from, so most developers start there. Go, Java/Kotlin, and C# SDKs are also officially maintained if they better match your existing stack.

Do I need to host my MCP server, or can it run locally?

Most MCP servers start as local processes that your AI client launches for you (the stdio transport) and never need hosting at all. You only need a remote, hosted server (over HTTP/SSE) if multiple people need to share one instance, or if it must run somewhere other than the user’s machine.

Is MCP the same as OpenAI-style function calling?

No. Function calling is a model feature for invoking a single function schema you define inline in your prompt. MCP is a standardized client-server protocol: one MCP server can expose many tools, resources, and prompts, and any MCP-compatible client can connect to it without custom integration code.

How do I test my server without restarting Claude Desktop every time?

Use the official MCP Inspector (npx @modelcontextprotocol/inspector) to run your server and call its tools, resources, and prompts directly in a browser UI, with live JSON-RPC logs, before wiring it into a full AI client.

Does my MCP server need authentication?

A local stdio server inherits the permissions of the user running it, so it typically doesn’t need its own auth layer. A remote HTTP server should require a bearer token or similar credential, since anyone who can reach the URL can otherwise call its tools.

How do I get my MCP server listed on AllMCPs?

Publish it to npm, PyPI, or GitHub with a clear README and setup instructions, then submit it through the AllMCPs submission form for review.

Further Reading