Setup and Your First Server
Prerequisites
Section titled “Prerequisites”| Tool | Version | Why |
|---|---|---|
| Node.js | 22.19 or newer | Runs the TypeScript SDK and the MCP Inspector (the Inspector requires ≥ 22.19) |
| Python | 3.10 or newer | Runs the Python SDK |
uv | any recent | Fast Python project and dependency manager; uvx runs published servers without installing them |
Check with node --version, python3 --version, and uv --version. Everything else is fetched by the package managers.
Create the project
Section titled “Create the project”mkdir hello-mcp && cd hello-mcpnpm init -ynpm install @modelcontextprotocol/server zodnpm install -D tsx typescriptuv init hello-mcp && cd hello-mcpuv add mcp@modelcontextprotocol/server is the server half of the TypeScript SDK (the client half is a separate package). The Python package mcp contains both halves.
Write a one-tool server
Section titled “Write a one-tool server”The server below exposes a single tool, get-forecast, and speaks stdio — the transport a host uses to run a server as a subprocess.
import { McpServer } from '@modelcontextprotocol/server';import { serveStdio } from '@modelcontextprotocol/server/stdio';import * as z from 'zod/v4';
serveStdio(() => { const server = new McpServer({ name: 'weather', version: '1.0.0' });
server.registerTool( 'get-forecast', { description: 'Get the weather forecast for a city', inputSchema: z.object({ city: z.string() }), }, async ({ city }) => ({ content: [{ type: 'text', text: `Sunny in ${city} all week.` }], }), );
return server;});from mcp.server import MCPServer
mcp = MCPServer("weather")
@mcp.tool()def get_forecast(city: str) -> str: """Get the weather forecast for a city.""" return f"Sunny in {city} all week."
if __name__ == "__main__": mcp.run() # stdio is the default transportWhat each piece does:
McpServer/MCPServer— the server object.nameandversionbecomeio.modelcontextprotocol/serverInfoin results and theserver/discoverresponse.registerTool/@mcp.tool()— registers a tool. TypeScript takes an explicit ZodinputSchema(imported fromzod/v4) and hands you typed arguments. Python derives the JSON Schema from the function’s type hints and the description from its docstring.- The handler’s return — TypeScript returns a result object with a
contentarray. Python lets you return a plain string, which the SDK wraps as a text content block. serveStdio/mcp.run()— wires the server to stdin/stdout.serveStdiotakes a factory so the SDK can build a fresh server per connection; the same factory shape is used later for HTTP.
On the wire, this server answers tools/list with:
{ "resultType": "complete", "tools": [ { "name": "get-forecast", "description": "Get the weather forecast for a city", "inputSchema": { "type": "object", "properties": { "city": { "type": "string" } }, "required": ["city"] } } ]}That is the whole contract a host needs.
Run it in the Inspector
Section titled “Run it in the Inspector”The MCP Inspector is the reference developer tool: one npx package with a web UI, a CLI, and a terminal UI, all sharing the same connection code. Point it at your server’s launch command:
npx @modelcontextprotocol/inspector npx tsx server.tsnpx @modelcontextprotocol/inspector uv run server.py# or the SDK's shortcut, which launches the Inspector for you:uv run mcp dev server.pyThe command prints a URL with a one-time session token. Open it, and you get a Tools tab listing get-forecast, a form generated from its inputSchema, and a monitoring sidebar that shows every JSON-RPC message as it crosses — the server/discover, the tools/list, and your tools/call. Reading that pane against the previous lesson is the fastest way to make the wire format real.
sequenceDiagram participant You as Browser participant I as Inspector participant S as server.ts (subprocess) You->>I: open session URL I->>S: spawn, write JSON-RPC to stdin I->>S: server/discover S-->>I: capabilities (tools) I->>S: tools/list S-->>I: get-forecast You->>I: fill city, click Run I->>S: tools/call get-forecast S-->>I: content: Sunny in Lisbon all week.
Scriptable check from the CLI
Section titled “Scriptable check from the CLI”For CI, shell pipelines, or a quick sanity check, the same package has a CLI mode that runs one method and exits:
npx @modelcontextprotocol/inspector --cli npx tsx server.ts --method tools/listnpx @modelcontextprotocol/inspector --cli npx tsx server.ts \ --method tools/call --tool-name get-forecast --tool-arg city=Lisbonnpx @modelcontextprotocol/inspector --cli uv run server.py --method tools/listnpx @modelcontextprotocol/inspector --cli uv run server.py \ --method tools/call --tool-name get_forecast --tool-arg city=LisbonAdd --format json and pipe to jq when you want to assert on the output.
The stdio golden rule
Section titled “The stdio golden rule”Over stdio, stdout is the protocol channel. The client reads it one line at a time and parses each line as JSON-RPC. The server must not write anything else there. One stray console.log("starting…") or print("debug") and the client sees a line that is not JSON, and the connection breaks in a way that looks like the server hung.
Log to stderr instead. The transport spec explicitly allows the server to write UTF-8 to stderr for informational, debug, and error output, and tells clients not to treat stderr as an error signal.
console.error('starting weather server'); // fine: stderr// console.log(...) // never in a stdio serverimport sysprint("starting weather server", file=sys.stderr) # fine: stderr# print("...") # never in a stdio serverThe same rule applies to any library your server imports: if a dependency prints a banner to stdout on import, it will break your server before the first message.
What you have
Section titled “What you have”A working MCP server, a way to exercise it, and the wire traffic to prove what it is doing. From here the course builds outward: the Server Primitives module fills the server with tools, resources, and prompts; the Transports module moves it from a subprocess to an HTTPS endpoint.