Kipt

Your prompts and skills, one keystroke away.

Explore

How to build an MCP server: a minimal working example in TypeScript and Python

How to build an MCP server with the official SDKs: a tested TypeScript and Python example, testing with MCP Inspector, and connecting it to Claude Code.

By the Kipt team · Updated 26 October 2026

To build an MCP server, install an official Model Context Protocol SDK, create a server, register at least one tool with a name, a description and an input schema, and serve it over stdio. Then check it with the MCP Inspector and add it to an app such as Claude Code. The whole thing fits in about 30 lines of TypeScript or 15 lines of Python.

This guide builds the same tiny server in both languages, following the official Build an MCP server tutorial and the current v2 SDKs. Both examples were run on 26 October 2026 with @modelcontextprotocol/server 2.4.0 on Node.js 24, and mcp 2.3.0 on Python 3.12, and tested with MCP Inspector 2.12.0.

If you are new to the protocol, read what is MCP first.

What will you build?

A server called word-tools with one tool, count_words, that takes a piece of text and returns its word and character counts. It needs no API key or network access, so nothing outside the code can break it. Once it works, you swap in your own logic: a database query, an internal API call, a file lookup.

The server uses the stdio transport. The app launches it as a local process and exchanges JSON-RPC messages over standard input and output. That is the simplest setup and the one the official tutorial uses.

What do you need before you start?

  • TypeScript: Node.js 20 or newer. The tutorial says "you'll need Node.js version 20 or higher."
  • Python: Python 3.11 or newer and uv. The tutorial asks for Python 3.10 or higher, and the Python SDK README asks for 3.11 or higher, so 3.11 satisfies both.
  • An app to connect it to, such as Claude Code or Claude Desktop.

One warning before any code. Many tutorials online were written for version 1 of the SDKs. The TypeScript SDK's v2 migration guide says "The single @modelcontextprotocol/sdk package is split," with servers now in @modelcontextprotocol/server, and the Python migration guide explains that FastMCP became MCPServer and that importing the old mcp.server.fastmcp module now fails. If a snippet imports either of those, it targets the old SDK.

How do you build an MCP server in TypeScript?

Create the project and install the SDK, Zod and TypeScript:

mkdir word-tools
cd word-tools
npm init -y
npm install @modelcontextprotocol/server zod
npm install -D @types/node typescript
mkdir src

In package.json, add "type": "module" and a build script:

{
  "type": "module",
  "scripts": {
    "build": "tsc"
  }
}

Create tsconfig.json with the settings from the official tutorial:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "types": ["node"],
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

Now write src/index.ts:

import { McpServer } from "@modelcontextprotocol/server";
import { serveStdio } from "@modelcontextprotocol/server/stdio";
import { z } from "zod";

// serveStdio calls this whenever it needs a server instance,
// so it must return a fresh McpServer every time.
function createServer() {
  const server = new McpServer({ name: "word-tools", version: "1.0.0" });

  server.registerTool(
    "count_words",
    {
      description: "Count the words and characters in a piece of text",
      inputSchema: z.object({
        text: z.string().describe("The text to count"),
      }),
    },
    async ({ text }) => {
      const words = text.trim().split(/\s+/).filter(Boolean).length;
      return {
        content: [
          { type: "text", text: `${words} words, ${text.length} characters` },
        ],
      };
    },
  );

  return server;
}

serveStdio(createServer, {
  onerror: (error) => console.error("Server error:", error),
});
console.error("word-tools MCP server running on stdio");

Build it:

npm run build

Three details matter here. registerTool takes the tool name, a config with a description and a Zod input schema, and a handler that returns a content array. serveStdio takes a factory function rather than a server object; the tutorial explains that it "negotiates the protocol revision with each client," so the same server answers clients on the current 2026-07-28 spec and older clients that still use the 2025-era initialize handshake. And the startup message uses console.error, not console.log, for a reason covered below.

How do you build the same server in Python?

Create a project with uv and add the SDK:

uv init word-tools-py --python 3.12
cd word-tools-py
uv add "mcp[cli]"

The --python 3.12 flag is there because uv init otherwise picks up whatever Python is on your machine. On a Mac with the system Python 3.9, uv add "mcp[cli]" fails because the SDK needs a newer version.

Create server.py:

from mcp.server import MCPServer

mcp = MCPServer("word-tools")


@mcp.tool()
def count_words(text: str) -> str:
    """Count the words and characters in a piece of text.

    Args:
        text: The text to count
    """
    words = len(text.split())
    return f"{words} words, {len(text)} characters"


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

The tutorial notes that "The MCPServer class uses Python type hints and docstrings to automatically generate tool definitions." The text: str hint becomes the input schema, the docstring becomes the description, and the -> str return type also produces an output schema, so the result comes back as both text and structured content.

How do you test an MCP server?

Test with the MCP Inspector before you involve an AI app. Its CLI mode talks to your server directly and prints the JSON it gets back.

List the tools of the TypeScript server, from the word-tools folder:

npx @modelcontextprotocol/inspector --cli node build/index.js --protocol-era modern --method tools/list

Call the tool:

npx @modelcontextprotocol/inspector --cli node build/index.js --protocol-era modern --method tools/call --tool-name count_words --tool-arg 'text=hello there MCP world'

You should see a result containing "text": "4 words, 21 characters". For the Python server, run the same commands from the word-tools-py folder with uv run server.py in place of node build/index.js.

The tutorial points out that the Inspector "defaults to the legacy era," which only exercises the older handshake, and recommends running each check once with --protocol-era modern and once with --protocol-era legacy. Both examples in this guide pass in both eras. The Python SDK README also suggests uv run mcp dev server.py to open the server in the Inspector.

How do you connect your server to Claude Code or Claude Desktop?

In Claude Code, register a stdio server with claude mcp add. The Claude Code MCP docs give the syntax as claude mcp add [options] <name> -- <command> [args...], where everything after -- is the command that starts your server. Use absolute paths:

claude mcp add --transport stdio word-tools -- node /ABSOLUTE/PATH/TO/word-tools/build/index.js

For the Python version:

claude mcp add --transport stdio word-tools-py -- uv --directory /ABSOLUTE/PATH/TO/word-tools-py run server.py

Run claude mcp list to check the connection, or /mcp inside a session. By default the server is added at local scope, available only to you in the current project; add --scope project to write it to a shared .mcp.json file, or --scope user for all your projects. Claude Code MCP covers scopes in more detail.

For Claude Desktop, the tutorial adds the server under mcpServers in claude_desktop_config.json (on macOS, in ~/Library/Application Support/Claude/) and then restarts the app:

{
  "mcpServers": {
    "word-tools": {
      "command": "node",
      "args": ["/ABSOLUTE/PATH/TO/word-tools/build/index.js"]
    }
  }
}

Other hosts work the same way with their own config files. See Cursor MCP, Codex MCP and Gemini CLI MCP.

What mistakes break an MCP server?

  • Printing to stdout. In a stdio server, standard output carries the protocol. The tutorial is blunt: "Writing to stdout will corrupt the JSON-RPC messages and break your server." Use console.error in Node and the logging module in Python, which writes to stderr.
  • Relative paths. The app starts your server from its own working directory, so node build/index.js in a config file usually fails. Use full paths, and if uv is not found, put its full path in the command field, as the tutorial suggests.
  • Forgetting to rebuild. A TypeScript server runs from build/, so edits to src/ do nothing until you run npm run build again.
  • Mixing SDK versions. Copying a v1 snippet into a v2 project produces import errors. Check the package names above.
  • Wiring the transport by hand. The tutorial warns that a TypeScript server started with server.connect(new StdioServerTransport()) "only serves 2025-era clients." Use serveStdio.

How do you write tools that models use well?

The code is the easy part. Whether a model calls your tool at the right moment depends mostly on its name and description, because that is all the model sees.

Anthropic's tool definition guidance calls detailed descriptions "by far the most important factor in tool performance" and suggests at least three to four sentences per tool description. It also recommends prefixing names with the service when tools span several systems, such as github_list_prs, and returning only "high-signal information" so results do not flood the context. The MCP architecture page makes the same point about names, preferring calculator_arithmetic over a bare calculate.

A good description says what the tool does, when to use it and when not to, what each parameter means, and what it does not return. Our guide to tool calling explains how the model reads these definitions.

If you plan to build many tools, Anthropic's mcp-builder skill guides a coding agent through planning, building, testing and evaluating an MCP server.

When should you move to HTTP?

Stdio suits a server that lives on the same machine as the app. When a server should serve a team or the public, use the Streamable HTTP transport. The architecture overview explains that remote servers "will typically serve many MCP clients," and that "MCP recommends using OAuth to obtain authentication tokens." The TypeScript SDK offers optional adapter packages for Express, Fastify, Hono and Node, and its serving guide covers Streamable HTTP and where to verify bearer tokens.

A remote server also brings the security rules into play: validate tokens issued for your server only, scope permissions narrowly, and read the official security best practices before you ship.

Where do the prompts for your server live?

Once the server works, you will write prompts that drive it, such as "count the words in each section of this draft and flag any over 300." Kipt keeps prompts like that, and any Agent Skills that call your tools, as Markdown in a private GitHub repo you own, with {{name:default}} blanks for the parts that change.

Read what is tool calling to see how a model decides when to call count_words, and browse best MCP servers for well-built examples to learn from.

Questions

What do I need to build an MCP server?

You need one of the official MCP SDKs and a language runtime. For TypeScript that is Node.js 20 or newer and the @modelcontextprotocol/server package; for Python it is a recent Python and the mcp package, which the official tutorial installs with uv. You also want the MCP Inspector to test the server before connecting it to an app.

Which language is easiest for an MCP server?

Python and TypeScript have the most official examples, and both SDKs are Tier 1. Python's MCPServer turns a typed function and its docstring into a tool definition, so a tool is a few lines. TypeScript needs a build step but gives you a Zod schema for each tool's input.

Why does my MCP server not show up or crash on start?

The most common cause in a stdio server is writing to standard output, because stdout carries the protocol messages; use console.error in Node or the logging module in Python instead. Other common causes are a relative path in the client config, forgetting to rebuild a TypeScript server, and following an older tutorial written for version 1 of the SDKs.

How do I test an MCP server without an AI app?

Use the MCP Inspector. Its CLI mode can list a server's tools and call one directly, for example with npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list. The official tutorial recommends running it once with --protocol-era modern and once with --protocol-era legacy to check both protocol generations.

Should my MCP server use stdio or HTTP?

Use stdio for a server that runs on the same machine as the app, such as a personal tool or a local file helper; the app starts it as a child process. Use Streamable HTTP for a server that many users reach over the network, which also means handling authentication, ideally with OAuth as the specification recommends.

More in Learn

All guides

Keep your prompts and skills in one place, in your own GitHub. Install every skill with one command.

Get started

Kipt is an independent product, not affiliated with or endorsed by Anthropic, OpenAI, Cursor or GitHub. Claude, ChatGPT, Codex, Cursor and GitHub are trademarks of their owners.