Before you start
This lesson follows the first-server tutorial in the TypeScript SDK's own repository, which runs TypeScript directly with tsx. The MCP build guide builds a similar weather server with a second tool and compiles it with tsc. Pick that one if you want a build step.
You need Node.js 20 or later, npm and an internet connection, because the tool calls the US National Weather Service API. The Inspector in the last step needs a patched Node release, 22.23.4, 24.22.1 or 26.12.1 or later on each line, which lesson 3 explains. Check the package name before you install anything, because TypeScript tutorials come in two generations.
| Package | SDK line | Where you meet it |
|---|---|---|
@modelcontextprotocol/server | v2, the current stable line | The official docs and this lesson |
@modelcontextprotocol/client | v2, for code that connects to servers | Clients and test scripts |
@modelcontextprotocol/sdk | v1.x, the older line, still maintained | Older tutorials and existing code |
Build the server in six steps
The code comes in two pieces, the tool and the serve call. Both go in one file, src/index.ts.
import { McpServer } from '@modelcontextprotocol/server';
import { serveStdio } from '@modelcontextprotocol/server/stdio';
import * as z from 'zod/v4';
const NWS_API = 'https://api.weather.gov';
interface AlertsResponse {
features: { properties: { event?: string; headline?: string } }[];
}
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 }) => {
const code = state.toUpperCase();
const url = `${NWS_API}/alerts/active?area=${code}`;
const res = await fetch(url, { headers: { 'User-Agent': 'mcp-weather-tutorial/1.0' } });
if (!res.ok) {
return { content: [{ type: 'text', text: `NWS API error: HTTP ${res.status}` }], isError: true };
}
const { features } = (await res.json()) as AlertsResponse;
if (features.length === 0) {
return { content: [{ type: 'text', text: `No active alerts for ${code}.` }] };
}
const lines = features.map(f => f.properties.headline ?? f.properties.event ?? 'Unnamed alert');
return { content: [{ type: 'text', text: lines.join('\n') }] };
}
);
return server;
}
void serveStdio(createServer);
console.error('weather MCP server running on stdio');
- Check Node with
node --version. It must report version 20 or later. - Create the project with
mkdir weather && cd weather, thennpm init -y, thennpm pkg set type=module. Thetype=modulesetting matters, because the SDK ships ES modules only. - Install the SDK, Zod and tsx with
npm install @modelcontextprotocol/server zod tsx, then create the source folder withmkdir src. - Create
src/index.tsand paste in the code above. - Start the server from the project root with
npx tsx src/index.ts. It printsweather MCP server running on stdioto stderr and then waits. Stop it with Ctrl+C. - Call the tool with
npx @modelcontextprotocol/inspector npx tsx src/index.ts. In the browser tab it opens, click Connect, open the Tools tab, pickget-alerts, enter a state code such asTXand run it.
What the code does
createServer is a factory that builds a new McpServer and registers one tool on it. registerTool takes a name, a config and an async handler. The inputSchema is a Zod schema, and from that one schema the SDK produces the JSON Schema for the model, checks the arguments before your handler runs and infers the handler's argument types.
The handler returns content, a list of typed blocks, here a single text block. Setting isError: true marks a failed result that the model can read and react to. Call get-alerts with { "state": "California" } and the SDK refuses before your handler runs. The model sees this message:
Input validation error: Invalid arguments for tool get-alerts: state: Too big: expected string to have <=2 characters
The factory shape is on purpose. The MCP build guide says serveStdio calls it whenever it needs an instance, so every call must return a fresh McpServer. The SDK README's shorter example wires a server by hand with server.connect(new StdioServerTransport()). The build guide says a server started that way only serves clients that open with the older 2025-era handshake, so serveStdio is the safer choice.
What happens when a model calls your tool
The model never sees your code. It sees the tool's name, its description and the JSON Schema derived from inputSchema, so those three decide whether the tool gets called at all. Plug the server into a host and a single answer takes six steps.
The host sends the question
In the SDK's example the host is VS Code. It sends the question to the model together with the name, description and input schema of every available tool.
The model picks a tool
It decides
get-alertsanswers the question and emits a call with the arguments it chose.The client calls your server
The MCP client inside the host sends a
tools/callrequest over stdio.The SDK runs your handler
It validates the arguments against
inputSchemafirst, then runs the handler.Your content goes back
The handler's
contentreturns over stdout as thetools/callresult.The model writes the answer
It reads the text block and writes the reply you see in the chat.
David Soria Parra on the stateless spec and the v2 SDKs
David Soria Parra, a member of technical staff at Anthropic and co-creator of MCP, announced the 2026-07-28 release on X the day it shipped. MCP became stateless, with semantics for requests that take several round trips, and the Python and TypeScript SDKs released v2.0.0 for it. That fits this lesson's factory: with no session tying a client to one object, serveStdio can build a fresh McpServer whenever it needs one.
The same post named extensions as the other headline: MCP Apps, Tasks, Enterprise Managed Auth and more, each a domain-specific way to use MCP. The TypeScript SDK's version 2.4.0 added Skills over MCP on 23 October. Read the SDK's releases before you build such a feature by hand.
Later that day he thanked the SDK maintainers on X and listed what supports the spec: v2.0.0 of the TypeScript, Python and C# SDKs, plus v1.7.0 of the Go SDK. The protocol is the same in each, so pick the SDK by the language your team writes.
Serving MCP just got much simpler and more scalable.
Screenshots of X's own embeds of the posts, taken on 5 November 2026, with the time in UTC. X cuts both posts short with Show more. Select one to open the post on X.
Common mistakes
- Using
console.log. On stdio, stdout is the protocol channel, and the host reportsSyntaxError: Unexpected token ... is not valid JSON. The quoted token is the first character of the stray line. Switch toconsole.error. - Leaving out the
type=moduleline in package.json from step 2. - Two copies of zod in the tree. TypeScript can fail with
TS2589: Type instantiation is excessively deep. Runnpm ls zodand make it list one version, with anoverridesentry in package.json if a dependency pins another. - Running Node 18 or older. Every SDK package needs a newer runtime, and the troubleshooting page says
ReferenceError: crypto is not definedmeans an older one. - Copying a 1.x tutorial. If it installs
@modelcontextprotocol/sdk, its imports will not match this lesson.
Try it yourself
- Call
get-alertsin the Inspector with{ "state": "California" }and compare the message with the one above. - Add a
console.log("hello")to the top of the file, run the Inspector command again and read what breaks. Then change it toconsole.error. - Add a second tool next to the first inside
createServerand check that the Tools tab lists both. Theget_forecasttool in the MCP build guide is a ready example.
Next: test it properly with the MCP Inspector
You called the tool once in the Inspector. Lesson 3 goes through every tab, the command line mode and what to check when a server misbehaves. If you want the Python version of this server, lesson 1 builds it.
For the bigger picture, what is MCP explains how hosts, clients and servers fit together.
Check yourself
Which package does the current TypeScript SDK use to build a server?
Show answer
C.
@modelcontextprotocol/serverThe current SDK is@modelcontextprotocol/server, while@modelcontextprotocol/sdkis the older 1.x line that older tutorials install.The host of your stdio server reports
SyntaxError: Unexpected token ... is not valid JSON. What is the likely cause?Show answer
A. A
console.logcall wrote to stdout, which is the protocol channel On stdio, stdout carries the protocol, so a stray line breaks it and you log withconsole.errorinstead.A model calls
get-alertswith{ "state": "California" }. What happens?Show answer
D. The SDK rejects it before your handler runs The SDK checks the arguments against
inputSchemafirst, and the model reads the validation message.What decides whether a model calls your tool at all?
Show answer
B. The tool's name, its description and its input schema The model never sees your code, only the name, the description and the JSON Schema built from
inputSchema.
Frequently asked questions
Can I still use the old @modelcontextprotocol/sdk package?
For now. It is the 1.x line, and the README says it keeps getting bug fixes and security updates for at least six months after v2 shipped on 27 July 2026. New code should start on @modelcontextprotocol/server.
What does the build guide's tsc setup add?
It compiles src into a build folder, and its package.json gains a bin entry, a files list and a build script that runs tsc. Hosts then run node on build/index.js, as in the guide's Claude Desktop config.
Do I have to use Zod for the input schema?
No. The README says tool and prompt schemas use Standard Schema, so Zod v4, Valibot, ArkType or any compatible library works. The tutorial uses Zod, and two copies of it in one dependency tree cause type errors.
Can I run the SDK on Bun or Deno?
Yes. The README says the SDK runs on Node.js, Bun and Deno. Install it with bun add @modelcontextprotocol/server or deno add npm:@modelcontextprotocol/server.
How do I serve the same server over HTTP?
Hand the same createServer factory to the SDK's HTTP serving instead of serveStdio. The tutorial says to do that when one endpoint should serve many clients. The SDK also publishes small adapter packages for Express, Fastify, Hono and Node's own HTTP server.
How do I log from a stdio server?
Use console.error, which writes to stderr, or a logging library that writes to stderr or files. A host captures a stdio server's stderr on its own, so nothing needs wiring up.
Sources
- MCP TypeScript SDK: README https://github.com/modelcontextprotocol/typescript-sdk
- MCP TypeScript SDK docs: Build your first server https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/get-started/first-server.md
- MCP TypeScript SDK docs: Plug into a real host https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/get-started/real-host.md
- MCP TypeScript SDK docs: Troubleshooting https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/troubleshooting.md
- MCP docs: Build an MCP server https://modelcontextprotocol.io/docs/develop/build-server
- MCP docs: MCP Inspector https://modelcontextprotocol.io/docs/tools/inspector
- MCP docs: Debugging https://modelcontextprotocol.io/docs/2026-07-28/tools/debugging
- MCP Inspector: README https://github.com/modelcontextprotocol/inspector
- MCP TypeScript SDK release v2.4.0 https://github.com/modelcontextprotocol/typescript-sdk/releases/tag/v2.4.0
- David Soria Parra on X: the MCP 2026-07-28 release https://x.com/dsp_/status/2082173429399142616
- David Soria Parra on X: the SDK releases that support the spec https://x.com/dsp_/status/2082225962402402384


