Build an MCP server

A minimal MCP server is genuinely small: declare a tool, give it a schema, wire up stdio. This page gets you from zero to a server Claude can call, then points at the official docs for everything past that — it's a starting point, not a reference.

Pick an SDK

SDKPackageRuns on
TypeScript (v2)@modelcontextprotocol/serverNode.js ≥ 20
Python (v2)mcpPython ≥ 3.10

Both are maintained by the MCP project and both hit 2.0 in July 2026 alongside the current spec. One migration note before you copy code off a blog post: v1 of the TypeScript SDK was a single package called @modelcontextprotocol/sdk, and most older tutorials show it. New servers should start on v2.

A minimal TypeScript server

This is the official quickstart shape — one tool, validated input, stdio transport:

terminal
npm init -y
npm install @modelcontextprotocol/server
src/index.ts
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();

That's the whole contract: registerTool takes a name, a description plus Zod input schema, and a handler returning content blocks. The schema is what the model reads to decide when and how to call your tool — write the description for a reader who can't see your code, because that's exactly what the model is.

The Python SDK is the same idea with less ceremony — a type-hinted function and a docstring become the schema and description:

server.py
# pip install "mcp[cli]"
from mcp.server import MCPServer

mcp = MCPServer("Demo")

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

Test it with the Inspector

Don't debug through a chat window. The MCP Inspector connects to your server directly and shows the schema exactly as clients will see it:

terminal
npx -y tsx src/index.ts   # sanity check: should start and wait silently
npx @modelcontextprotocol/inspector npx -y tsx src/index.ts

List the tools, call greet with a name, read the result. One rule to internalize while you're here: never console.log in a stdio server — stdout is the protocol channel, and stray prints corrupt it. Log to stderr.

Then connect it to a real client:

terminal
claude mcp add greeter -- npx -y tsx /absolute/path/to/src/index.ts

Distribute it

A server nobody can install isn't finished. The conventions, so users can run yours with the one-liners every client config expects:

  • npm: publish with a bin entry so npx -y your-package just works. Naming convention: mcp-server-<thing>, or scoped @yourorg/mcp-<thing>.
  • PyPI: declare a script entry point so uvx your-package launches it. Convention: mcp-server-<thing>.
  • README: lead with the copy-paste config block for Claude, Cursor and VS Code, and an explicit list of required environment variables. That README is your setup UI.
  • Registry: list it in the official MCP registry so clients and directories can discover it.

Where to go deeper

This page stops where the real docs begin. For resources and prompts, streamable HTTP and hosting, OAuth, sessions, and the rest of the surface:

And before you ship anything that holds credentials, read our MCP security guide from the server author's side of the table: scoped keys, no secrets in tool arguments, and the knowledge that whatever your tools return goes straight into someone's model context.

Questions

Which language should I build an MCP server in?

TypeScript or Python — the two SDKs maintained by the MCP project itself. Official SDKs also exist for Kotlin, Java, C#, Go, Ruby, Rust, Swift and PHP, but the TypeScript and Python ones get features first and have the most examples to crib from.

What's the difference between @modelcontextprotocol/sdk and @modelcontextprotocol/server?

Versioning. v1 shipped as one monolithic package, @modelcontextprotocol/sdk. v2 — the current stable line, implementing the 2026-07-28 spec — splits it into @modelcontextprotocol/server, @modelcontextprotocol/client and framework adapters. New servers should start on v2; most tutorials you'll find online still show v1.

Do I need to implement resources and prompts too?

No. Tools alone make a useful server, and most servers ship nothing else. Resources (readable data) and prompts (reusable templates) are worth adding when a client you care about actually surfaces them.

stdio or HTTP — which transport should I ship?

Start with stdio: it's what every desktop client runs, and the transport is one import. Add streamable HTTP when you want a hosted server multiple users reach over the network — that's also the point where the spec's OAuth requirements apply.

How do users' API keys reach my server?

For local servers, through environment variables declared in the client's config — read process.env at startup and exit with a clear error if the key is missing. Never make users paste keys into tool arguments; that puts secrets into the model's context.