← Blog

How to Build an MCP Server in Python or TypeScript

September 30, 2026

AI Agents & ToolsAI Coding

Here's how to build an MCP server in about fifteen lines: install an official SDK, write one function, mark it as a tool, and run it over stdio. The host application launches your file, the model sees your function, and it can call it.

That's the whole loop. Five steps:

  • Pick an SDK. Python (mcp on PyPI) or TypeScript (@modelcontextprotocol/server on npm).
  • Define a tool. A function with typed inputs and a description the model reads.
  • Serve it over stdio. The host starts your server as a child process and talks to it over stdin and stdout.
  • Test it in the MCP Inspector before any AI touches it.
  • Register it with a client, such as Claude Code, with one command.

One warning before you copy code from anywhere: both official SDKs shipped a v2 alongside the 2026-07-28 protocol revision, and a lot of tutorials still show v1 code. If you see FastMCP imported from mcp.server.fastmcp, or a TypeScript import from @modelcontextprotocol/sdk, you're reading the old shape. Everything below follows the current SDK READMEs as of September 2026.

Python vs TypeScript: which SDK to build with

The official quickstart covers eight languages, including Java, Kotlin, C#, Ruby, Rust, and Go. Most first servers are Python or TypeScript, so those are the two covered here.

Python SDKTypeScript SDK
Packagemcp (add the cli extra for the dev tools)@modelcontextprotocol/server
RuntimePython 3.10+Node.js 20+, Bun, or Deno
Where the input schema comes fromYour type hints and docstringA Zod schema you pass in
Local test commanduv run mcp dev server.pynpx @modelcontextprotocol/inspector plus your launch command
v1 name in older tutorialsFastMCP@modelcontextprotocol/sdk

Pick the language your tool's logic already lives in. If the server wraps a pandas script or a Python client library, use Python. If it sits next to a Node service or calls an npm package, use TypeScript. The protocol is identical either way, and a client can't tell which one you used.

Not sure you need a server at all? If the goal is one integration inside your own app, a plain API call may be simpler. Our MCP vs API breakdown covers when the protocol earns its overhead. And if you want the background on hosts, clients, and servers first, start with what MCP is.

How to build an MCP server in Python

The Python SDK needs Python 3.10 or later. The official docs use uv for project setup; plain pip install "mcp[cli]" works too.

uv init demo
cd demo
uv add "mcp[cli]"

The cli extra adds the mcp command, which gives you mcp dev, mcp run, and mcp install. You'll use all three.

Now create server.py:

from mcp.server import MCPServer

mcp = MCPServer("Demo")


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


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

That's a complete server. Three things are doing the work:

  • MCPServer("Demo") creates the server. In v2 this class replaced FastMCP, and the v2 release notes are blunt that the old import path is gone rather than deprecated.
  • @mcp.tool() registers the function as a tool. The SDK builds the tool definition from the function itself: the name from the function name, the description from the docstring, and the input schema from a: int, b: int. You never write JSON Schema.
  • mcp.run() with no arguments starts a stdio server. Keep it under the __main__ guard, because the SDK's own commands import your file and an unguarded call would start a server on import.

Adding a resource or a prompt works the same way, with @mcp.resource() and @mcp.prompt(). Start with tools. They're what most people mean by an MCP server.

Not ready to migrate a v1 project? pip install mcp now installs 2.x. The Python maintainers suggest pinning your requirement below version 2 until you've ported, and v1.x still receives critical fixes and security patches.

How to build a TypeScript MCP server

The TypeScript SDK v2 split into separate packages: @modelcontextprotocol/server for servers and @modelcontextprotocol/client for clients. Schemas use Standard Schema, so Zod v4 is the common choice.

mkdir greeting
cd greeting
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/server zod tsx

type=module matters because the SDK ships ES modules only. tsx runs TypeScript directly, so there's no build step while you're developing.

Create src/index.ts. This is the minimal example from the SDK README:

import { McpServer } from '@modelcontextprotocol/server';
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
import * as z from 'zod/v4';

const server = new McpServer({ name: 'greeting-server', version: '1.0.0' });

server.registerTool(
    'greet',
    {
        description: 'Greet someone by name',
        inputSchema: z.object({ name: z.string() })
    },
    async ({ name }) => ({
        content: [{ type: 'text', text: `Hello, ${name}!` }]
    })
);

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

main();

registerTool takes a name, a config, and a handler. The Zod schema is the only schema you write: the SDK turns it into the JSON Schema the model sees and validates every call against it before your handler runs. The handler returns content, a list of typed blocks.

Start it with npx tsx src/index.ts. Nothing visible happens, and that's correct. A stdio server sits waiting for a client to open the conversation.

Test your server with the MCP Inspector

Don't debug through a chatbot. The MCP Inspector is the official tool for calling a server's tools directly: it launches your server, connects over stdio, and shows you exactly what a model would receive.

LanguageCommand
Pythonuv run mcp dev server.py
TypeScriptnpx @modelcontextprotocol/inspector npx tsx src/index.ts
Any stdio servernpx @modelcontextprotocol/inspector followed by your launch command

Open the URL it prints, click Connect, open the Tools tab, and run your tool. Call add with 1 and 2 and you should get 3 back.

Two things are worth checking in the Inspector, not just "does it run":

  • Read your tool's description as the model will. The name, description, and input schema are what the model uses to decide whether to call your tool. A vague docstring like "Does stuff with data" is why a model ignores a perfectly working tool.
  • Send it bad input. The TypeScript SDK rejects arguments that fail the schema before your handler runs, and the model reads that error. Make sure the message is one it can act on.

Connect your MCP server to Claude Code and other clients

Connecting a local server is always the same act: you tell the host the command that starts your server. Only where you put that command changes.

Claude Code takes it on the command line. Everything after the -- is the launch command, passed to your server untouched:

# Python
claude mcp add demo -- uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py

# TypeScript, from the project root
claude mcp add greeting -- npx tsx src/index.ts

Then run /mcp inside a Claude Code session to confirm the server is connected and its tools are listed. Scopes, config files, and why a server sometimes loads the wrong definition are covered in our Claude Code MCP configuration guide.

Other hosts take the same command in a JSON file:

HostWhere the command goes
Claude Codeclaude mcp add, no file to edit
Claude Desktopclaude_desktop_config.json, or run uv run mcp install server.py and the Python SDK writes the entry for you
Cursor.cursor/mcp.json in the project root, under mcpServers
VS Code.vscode/mcp.json in the project root, under servers

For Claude Desktop, fully quit the app and reopen it after any config change. Closing the window doesn't reload servers.

Would rather not scaffold by hand? Anthropic publishes an official mcp-server-dev plugin for Claude Code. Install it, run its build skill, and Claude asks about your use case and scaffolds either a remote HTTP or a local stdio server.

Five mistakes that break a first MCP server

Almost every "it runs but the host can't see it" report is one of these.

SymptomCauseFix
Server connects, then fails with garbled messagesYou printed to stdout. In a stdio server, stdout is the protocol channelLog to stderr: Python's logging module, or console.error in Node
ImportError on mcp.server.fastmcpv1 tutorial code on the v2 SDKImport MCPServer from mcp.server, or pin mcp below 2
Host can't find the server fileRelative path in the configUse an absolute path. The host launches from its own working directory
Host can't find uv or npxHosts spawn servers with a minimal PATHReplace the bare command with its absolute path (which uv)
Your API key is missing inside the serverThe host doesn't inherit your shell's environmentPass it explicitly, for example --env on claude mcp add

The first row is the one that costs people an afternoon. One stray print() or console.log() corrupts the JSON-RPC stream, and the error you see rarely points back to it.

Local stdio vs a remote MCP server

Everything above runs your server on the same machine as the host. That's the right shape for a personal tool. It stops being enough when other people need the server and don't have your file.

Local stdioRemote (Streamable HTTP)
Who runs itThe host launches it as a child processYou host it; clients connect by URL
What you hand outA commandA URL
Best forPersonal tools, local files, scriptsShared tools, teams, public integrations
Extra workAlmost noneHosting, and authentication if it touches private data

The same server object can serve both. The Python SDK serves it over HTTP with uv run mcp run server.py --transport streamable-http, and the TypeScript SDK ships Streamable HTTP transports plus optional middleware for Express, Fastify, and Hono.

Authentication is its own project, and not one to improvise. Claude Code's docs point server builders to Anthropic's connector building guide for authentication, testing, and Directory submission, and each SDK's docs cover its OAuth helpers. Build and test the local version first. When you want to see how production servers are laid out, read the source of the reference servers in the official MCP repositories.

If you'd rather run a setup than build one

Building a server makes sense when the tool you need doesn't exist yet. Often it does: someone already wired the integration, got the prompts right, and made it work. What's missing is a way to use their working setup without rebuilding it.

That's the gap Taku sits in. It's an AI-native desktop workspace where you mirror an AI app, skill, or workflow someone already got working, run it, remix it for your own files, and keep it. It won't write your server for you, and the SDKs above are the right tools when you're the one building. But if your real goal was the result rather than the server, the free app library is a shorter road. Taku is in Beta, and the Mac app is available now.

FAQ

What do I need to build an MCP server?

One of the official SDKs and a language runtime: Python 3.10+ for the Python SDK, or Node.js 20+ (Bun and Deno also work) for the TypeScript SDK. For testing, the MCP Inspector. You don't need an API key for any model, because the server never talks to the model directly. The host does.

Is FastMCP still the way to create an MCP server in Python?

Not in the official SDK. In v2 the high-level class was renamed to MCPServer and moved to mcp.server. The decorators work the same way, so porting a simple v1 server is mostly a two-line change. The SDK's migration guide lists the other renames.

How do I test an MCP server without Claude?

Use the MCP Inspector. uv run mcp dev server.py opens it for a Python server, and npx @modelcontextprotocol/inspector followed by your launch command works for any stdio server. You can call each tool directly and see the exact result a model would get.

Why does my MCP server work in the Inspector but not in Claude Desktop?

Usually a relative path, a command that isn't on the host's PATH, or a config change the app never reloaded. Use absolute paths, the absolute path to uv or npx, and fully quit the app before reopening it.

Should I build a local or a remote MCP server?

Start local. A stdio server is the fastest way to prove your tools work. Move to Streamable HTTP when other people need to connect without having your code, and plan for authentication at that point.