Chapter 7

Start With the Current MCP TypeScript SDK v2

An SDK saves time exactly as long as you take its current version. In stable v2 the client and server are split into separate packages, and Zod 4 is used for runtime validation and JSON Schema generation. The main mistake at the start is to carry over imports and the initialization flow from a 2025 tutorial: the SDK API changes faster than the protocol idea, and old code will compile silently but speak a different revision.

For a new project the install looks like this - as separate packages.

Bash
npm install @modelcontextprotocol/server@2 zod@4
npm install @modelcontextprotocol/client@2
npm install -D typescript tsx @types/node

And creating the server object looks like this: a name, a version, and short instructions that give the server its narrow role.

TypeScript
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/server";
import { serveStdio } from "@modelcontextprotocol/server/stdio";
import * as z from "zod/v4";

import {
  getReleaseSnapshot,
  getRunbook,
  listReleaseSnapshots,
  reviewRelease,
  searchRunbooks
} from "../domain/release-knowledge.js";

export function createDeveloperMcpServer(): McpServer {
  const server = new McpServer(
    { name: "developer-release-mcp", version: "1.0.0" },
    { instructions: "Use release resources as evidence. Never invent a release snapshot." }
  );

To avoid dragging along old reflexes, it helps to keep in view what exactly changed and why it matters.

Old reflex · Current approach · Why it matters

  • One @modelcontextprotocol/sdk package - Separate @modelcontextprotocol/server and /client; A smaller surface and clearer dependencies
  • Code from a random blog - Official examples of the same major version; The SDK API changes faster than the protocol idea
  • Treating initialize as an eternal schema - Explicitly check the modern/legacy era; The 2026-07-28 revision changed the lifecycle
  • any around handlers - A Zod schema and strict inferred types; Wire input is untrusted

Every row of the table is a trap you can fall into out of inertia. The single @modelcontextprotocol/sdk package became a split server and client - a smaller surface and clearer dependencies. Code from a random blog is worth replacing with official examples of the same major version. And the habit of treating initialize as an eternal schema has to go: the 2026-07-28 revision changed the lifecycle itself, and we'll take that apart in a chapter of its own.

The book shows the modern era. The companion client includes versionNegotiation: { mode: "auto" }, and the stdio server starts through a helper that can serve both modern and legacy peers. Check the negotiated revision right in the integration test - it's a cheap insurance against "silent" incompatibility.

The tools are in place - we write the first server. And we'll start with what makes a server narrow and predictable: the domain.

Links