Building your first MCP server, step by step
Code6 min read · 27 September 2026
MCP stands for Model Context Protocol: a standard way to give a model like Claude direct access to a service, a database, or any tool you’ve written yourself. If you’ve ever connected an existing MCP connector, you already know what it does from the user’s side. This time, we’re switching sides: writing your own MCP server, minimal but working, using only the official documentation and SDKs.
MCP, concretely: who talks to whom
The official architecture describes three roles. The MCP host is the application that uses the AI, for example Claude Code or Claude Desktop. The MCP client is the component the host creates to maintain a dedicated connection to a given server. The MCP server, the one you’re about to write, is the program that provides context or actions to whichever clients connect to it. A local server (the most common case when you’re starting out) communicates over the process’s standard input/output (the “stdio” transport); a remote server, hosted somewhere, communicates over HTTP instead.
The three building blocks your server can expose
The official documentation defines three core primitives on the server side:
- Tools: executable functions the AI application can invoke to take action (call an API, query a database, manipulate a file).
- Resources: data sources that provide context (a file’s contents, a database record, an API response).
- Prompts: reusable prompt templates that structure an interaction with the model (a system prompt, few-shot examples).
For a first server, one single tool is plenty: it’s the simplest building block to understand and the most immediately useful.
Writing a minimal server in TypeScript
Create a project folder, then install the official TypeScript SDK along with the validation library it uses to describe a tool’s input:
npm install @modelcontextprotocol/server
npm install zod
Create a file (say, index.ts) with this content, taken from the SDK’s official repository:
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();
This code does three things: it creates a server named greeting-server, it registers a greet tool whose expected input is validated by a Zod schema ({ name: string }), then it connects the server to the stdio transport, the one MCP hosts use to talk to a server running locally as a plain process.
Notice the description field on the tool itself. It plays the same role as a skill’s description: the connected AI application reads it to decide whether this particular tool is the right one to call for a given request, especially once your server exposes more than one. A vague description here causes the same problem as a vague skill description — the tool either never gets used, or gets used for the wrong request — so it’s worth being just as specific here as anywhere else.
If you’d rather use Python
The official Python SDK gets you to the same place with a decorator-based syntax. Install it with:
uv add "mcp[cli]"
(or pip install "mcp[cli]" if you’re not using uv). The minimal server fits in a few lines:
from mcp.server import MCPServer
mcp = MCPServer("Demo")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
No schema to write by hand: Python’s type hints (a: int, b: int) are enough for the SDK to generate the tool’s description automatically.
Testing it with the MCP Inspector
Before connecting anything to Claude, test your server with the MCP Inspector, the official reference tool built for exactly this. It runs through npx, no install needed (it requires Node 22.19.0 or newer):
npx @modelcontextprotocol/inspector node path/to/index.js
This opens a local web interface where you can list the tools your server exposes, call greet with a name as a parameter, and see the response come straight back. It’s the step that saves you from discovering a schema mistake only after connecting the server to Claude. If you’d rather stay in the terminal, the Inspector also offers a CLI mode (--cli) and a TUI mode (--tui) that do the same checks without a browser.
For a Python server launched with mcp dev, the Inspector opens automatically:
uv run mcp dev server.py
Connecting it to Claude
Once the server checks out, you can register it in Claude Code with claude mcp add, keeping Claude Code’s own options (before the --) clearly separated from the command that launches your server (after the --):
claude mcp add --transport stdio greeting-server -- node path/to/index.js
Claude Code registers the server right away. You can confirm it shows up with claude mcp list, and remove it with claude mcp remove greeting-server if needed. From that point on, in a conversation, Claude can discover your greet tool and call it exactly like any other MCP tool.
Going further: a resource, and serving more than one client
A tool is only one of the three possible building blocks. The Python SDK exposes a resource in much the same way as a tool, with its own dedicated decorator:
@mcp.resource("config://app-name")
def get_app_name() -> str:
"""Application name, exposed read-only as a resource."""
return "greeting-server"
The real difference between the two: a tool is meant to act (it can have side effects), while a resource is meant to be read, a bit like a GET request that changes nothing. If your server only needs to expose data that already exists, a resource is often the more honest thing to expose, rather than a tool.
Second thing worth knowing before you go further: a server running over the stdio transport, like the one in this article, only serves one client at a time — perfectly fine for local development or strictly personal use. The day you want the same server reachable by several people at once, the official documentation recommends switching to the Streamable HTTP transport, built exactly for that case: the server then runs continuously somewhere (often hosted), and each client opens its own HTTP connection to it, with no need for a dedicated local process per user.
If this MCP server is part of graded coursework (a lab, an end-of-module project), check your school’s AI usage rules first: getting help from Claude to write or debug your code isn’t necessarily treated the same way from one course to another.
Key takeaways
- MCP connects a host (like Claude Code), a client, and a server you write yourself.
- Three possible primitives on the server side: tools (actions), resources (data), prompts (interaction templates). One tool is enough to start.
- Official TypeScript SDK:
npm install @modelcontextprotocol/server, anMcpServerinstance, aregisterToolcall, a stdio transport. - Official Python SDK:
uv add "mcp[cli]", anMCPServerinstance, a tool declared with@mcp.tool(). - Always test before connecting anything:
npx @modelcontextprotocol/inspector node your-server.js. - Connecting to Claude Code:
claude mcp add --transport stdio <name> -- <command>, thenclaude mcp listto confirm.
Sources
- Architecture overview — Model Context Protocol · accessed 27 September 2026
- MCP Inspector — Model Context Protocol · accessed 27 September 2026
- typescript-sdk (README) — GitHub modelcontextprotocol · accessed 27 September 2026
- MCP Python SDK — Get started · accessed 27 September 2026
- python-sdk (README) — GitHub modelcontextprotocol · accessed 27 September 2026
- Connect Claude Code to tools via MCP — Claude Code Docs · accessed 27 September 2026






