Build Your First Server
Purpose and Scope
This page walks through the first productive server path in the TypeScript SDK: create a small project, construct an MCP server, register a callable tool, expose it over standard input and output, and run it as a local process. In MCP terms, a server is the program that offers capabilities to a host or client, while a tool is a function the connected model may choose to call. The tutorial example is intentionally concrete: a weather server exposes one alert lookup tool for two-letter US state codes and returns text content that a model can read.
Sources: docs/get-started/first-server.md
The first-server flow uses the high-level server API because it removes protocol bookkeeping from the beginner path. You define the server name and version, provide a schema for tool arguments, and write the handler that performs useful work. The SDK turns that schema into the information a model sees and validates incoming arguments before the handler runs. That is the main difference from the low-level server API, where a developer must declare capabilities, answer protocol methods, dispatch tool names, and validate tool arguments manually.
Sources: docs/get-started/first-server.md, docs/advanced/low-level-server.md
Relevant Source Files
- docs/get-started/first-server.md — Primary tutorial for project setup, the weather server example, tool registration, stdio serving, and running the server.
- docs/advanced/low-level-server.md — Explains the protocol-level
ServerAPI and shows whatMcpServer.registerToolautomates for tool listing, dispatch, and validation. - docs/clients/server-requests.md — Provides context on client-declared capabilities and server-originated request handling, useful when the first server later grows beyond simple tool calls.
- docs/.vitepress/theme/index.ts — Shows the v2 documentation site theme entry point that installs the shared banner above guide pages.
- docs/v1/.vitepress/theme/index.ts — Shows the v1 documentation site theme entry point and the shared CSS relationship with the v2 site.
- docs/servers/completion.md — Demonstrates another high-level server feature where helper registration automatically advertises a capability, paralleling the beginner-friendly
registerToolpattern.
Core Primitives
The essential primitive in the first server is McpServer, created with metadata such as a name and version. That object is the place where server capabilities are registered. In the tutorial, the server registers a tool named get-alerts. A tool registration has three pieces: a stable tool name, a configuration object with a description and input schema, and an asynchronous handler. The description and schema are not only documentation; they become part of the contract that a client and model can inspect before deciding whether and how to call the tool.
Sources: docs/get-started/first-server.md
The second primitive is the schema. The guide uses Zod v4 to describe an object with a state field that must be exactly two characters long. The SDK derives the JSON Schema visible to the model from that one schema, validates call arguments before invoking the handler, and infers the TypeScript argument type used by the handler. That means the handler can focus on the weather lookup rather than defensive parsing for ordinary malformed calls. A value like a full state name is rejected as an input validation failure before the network request is attempted.
Sources: docs/get-started/first-server.md
The third primitive is result content. The weather handler returns a result object with a content list, using a text block for either alert headlines or a human-readable message. When the upstream National Weather Service request fails, the handler returns text plus isError: true. That shape is important because tool failures are still model-readable results. The model can see the error text and decide what to do next, instead of receiving only a transport failure or an opaque thrown exception.
Sources: docs/get-started/first-server.md
Step-by-Step Flow
Start by making a Node project, marking it as an ES module package, installing the server package, Zod, and tsx, and creating a source directory. The guide calls out type=module because the SDK ships ES modules only. It also uses tsx so a new user can run TypeScript directly without configuring a build pipeline. That keeps the tutorial focused on the MCP contract: metadata, tool schema, handler, and transport. The resulting project has one application file, commonly src/index.ts, that both defines the server factory and starts serving.
Sources: docs/get-started/first-server.md
mkdir weather && cd weather
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/server zod tsx
mkdir srcInside the application file, create a factory function that returns a fresh McpServer. The guide names the server weather with version 1.0.0, then registers get-alerts. The handler normalizes the supplied state to uppercase, calls the National Weather Service alerts endpoint, and formats response features into one text response. The important pattern is reusable beyond weather: validate a small argument object, call whatever domain service your integration owns, and return typed content blocks that the host can present or feed back into the model.
Sources: docs/get-started/first-server.md
import { McpServer } from '@modelcontextprotocol/server';
import { serveStdio } from '@modelcontextprotocol/server/stdio';
import * as z from 'zod/v4';
function createServer(): McpServer {
const server = new McpServer({ name: 'weather', version: '1.0.0' });
server.registerTool(
'get-alerts',
{
description: 'Get the active weather alerts for a US state',
inputSchema: z.object({
state: z.string().length(2).describe('Two-letter US state code, e.g. CA')
})
},
async ({ state }) => ({
content: [{ type: 'text', text: `Look up alerts for ${state.toUpperCase()}.` }]
})
);
return server;
}Serving Over Stdio
For the first transport, hand the factory to serveStdio. The stdio transport reads JSON-RPC requests from stdin and writes responses to stdout, while calling the factory to build the server instance for the connection. This is the common local-process integration pattern: a host starts your server as a child process and speaks MCP over its standard streams. Because stdout is the protocol channel, the guide warns not to use console.log for diagnostics. Logs belong on stderr, so the tutorial banner uses console.error.
Sources: docs/get-started/first-server.md
void serveStdio(createServer);
console.error('weather MCP server running on stdio');Run the server from the project root with npx tsx src/index.ts. Seeing the banner does not mean the server has completed work; it means the process is waiting for an MCP client to begin the JSON-RPC conversation over stdin. This can feel unusual compared with an HTTP server, because there is no port to visit in a browser. Stop the process with Ctrl+C while developing. When paired with a real host, that host is responsible for launching the process and sending initialization and tool-call messages.
Sources: docs/get-started/first-server.md
npx tsx src/index.tsChoosing the Right Server Layer
Use the high-level McpServer for a first server and for most integrations that expose tools, prompts, resources, or completion callbacks. The low-level Server API is useful when you need direct protocol control, but it requires more explicit work. The low-level guide shows that you must declare the tools capability in the constructor, register a tools/list handler, provide raw JSON Schema yourself, and register a single tools/call handler that dispatches on the requested tool name. If the capability is not declared, registering the handler throws.
Sources: docs/advanced/low-level-server.md
That distinction matters for correctness. In the low-level example, a client calling search with the wrong argument type reaches the handler because the protocol layer only checks that arguments are an object. The handler then crashes when it treats a number as a string, producing a protocol error rather than a readable tool result. The guide later adds explicit validation with fromJsonSchema. By contrast, the first-server path gets validation from the Zod inputSchema supplied to registerTool, so beginners start with safer behavior and less duplicated schema logic.
Sources: docs/get-started/first-server.md, docs/advanced/low-level-server.md
Growing Beyond the First Tool
After the first tool works, the same registration style extends to other server features. The completion guide shows completable wrapping prompt arguments and notes that the first completable field registers the completion handler and advertises the completions capability automatically. That mirrors the beginner experience: high-level helpers declare the relevant capability when you use the feature. Client-side server requests add another dimension. A server that elicits input or requests sampling depends on clients declaring those capabilities and registering handlers, so those features should be added deliberately after the basic tool path is understood.
Sources: docs/servers/completion.md, docs/clients/server-requests.md
The documentation site itself separates current and v1 guide surfaces through VitePress theme entry points. The v2 theme imports its banner and custom CSS directly, while the v1 theme imports its own banner and shares the v2 custom CSS through a relative path. For this page, that mainly signals that the first-server tutorial belongs to the v2 documentation spine, while older production users may still consult the v1 documentation area. When following these steps, keep package names and imports aligned with the v2 guide.
Sources: docs/.vitepress/theme/index.ts, docs/v1/.vitepress/theme/index.ts
Next Steps
Build the weather server first, then replace the placeholder domain logic with a tool from your own system. Keep schemas narrow, return model-readable text for expected failures, and log only to stderr when using stdio. Once the stdio flow is comfortable, continue to serving guides for HTTP transports, the tools guide for richer tool contracts, and the low-level server guide only if you need manual protocol handling. If the server needs to ask the user or call back into a model, read the client server-requests material before adding those capabilities.