How to Build an MCP Server in Node.js with TypeScript
Build a Node.js MCP server with the official TypeScript SDK v2: one file, one Zod schema, tested in MCP Inspector and added to Claude Code or Claude Desktop.
The official TypeScript SDK is now split into packages. You build servers with @modelcontextprotocol/server (version 2.4.0 on npm in November 2026), which implements the 2026-07-28 MCP spec and runs on Node.js 20+, Bun and Deno. You write one Zod schema per tool; the SDK derives the JSON Schema the model sees and validates every call before your handler runs.
The v1 package @modelcontextprotocol/sdk still gets bug fixes and security updates for at least six months after the v2 release on 2026-07-27. The npm package fastmcp is a separate community framework, not the official SDK. This guide follows the SDK's own tutorial.
Good for
- Node.js and TypeScript developers exposing their own code or API to Claude
- Teams moving a v1 server from @modelcontextprotocol/sdk to v2
- Anyone who wants a server that also runs on Bun or Deno
Build, test and connect a TypeScript MCP server
- 1
Create the project
You need Node.js 20 or later. Run:
mkdir greeting-server && cd greeting-servernpm init -ynpm pkg set type=modulenpm install @modelcontextprotocol/server zod tsxmkdir src
type=modulematters because the SDK ships ES modules only.tsxruns TypeScript directly, so there is no build step while you develop. - 2
Write src/index.ts
Copy
src/index.tsfrom the Code section.new McpServer({ name, version })creates the server andregisterTooladds agreettool with a ZodinputSchema. The handler returnscontent, a list of typed blocks.The file ends with
serveStdio(createServer), which reads requests on stdin and writes responses on stdout. Log withconsole.error: oneconsole.logcorrupts the stream. - 3
Run it once
Run
npx tsx src/index.tsfrom the project root. It printsgreeting MCP server running on stdioto stderr and then waits. That is correct: a stdio server waits for a client. Stop it with Ctrl+C. - 4
Test it in MCP Inspector
Run
npx @modelcontextprotocol/inspector npx tsx src/index.ts. In the browser tab it opens, click Connect, open Tools, selectgreet, enter a name and run it. You getHello, <name>!, the same content a model receives.The Inspector needs Node.js 22.19 or later. A call with a wrong argument type is rejected before your handler runs.
- 5
Add it to Claude Code
From the project root, run
claude mcp add greeting -- npx tsx src/index.ts. Everything after--is the launch command.Start
claudein that directory and run/mcp:greetingshows as connected withgreetlisted. Add--scope projectto write it to.mcp.jsonso your team gets it.PromptUse the greeting server to greet Ada. - 6
Build it for Claude Desktop
Claude Desktop runs a compiled file with
node. Runnpm install -D typescript @types/node, add thetsconfig.jsonand thebuildscript from the Code section, thennpm run buildto producebuild/index.js.Add the server to
claude_desktop_config.jsonundermcpServerswith"command": "node"and the absolute path tobuild/index.js(file paths in the Code section). Fully quit Claude Desktop and reopen it; the server appears under + > Connectors. - 7
Check it answers old and new clients
List tools with the Inspector CLI in both protocol eras:
npx @modelcontextprotocol/inspector --cli node build/index.js --protocol-era modern --method tools/list- the same command with
--protocol-era legacy
Both should list
greet. A server wired withserver.connect(new StdioServerTransport())instead ofserveStdioonly answers 2025-era clients.
Code
mkdir greeting-server && cd greeting-server
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/server zod tsx
mkdir srcimport { McpServer } from '@modelcontextprotocol/server';
import { serveStdio } from '@modelcontextprotocol/server/stdio';
import * as z from 'zod/v4';
function createServer(): McpServer {
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}!` }]
})
);
return server;
}
void serveStdio(createServer);
console.error('greeting MCP server running on stdio');# Run it (prints one line to stderr, then waits)
npx tsx src/index.ts
# Open it in MCP Inspector (Node.js 22.19+)
npx @modelcontextprotocol/inspector npx tsx src/index.ts
# Add it to Claude Code (run from the project root)
claude mcp add greeting -- npx tsx src/index.ts{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"types": ["node"],
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}{
"type": "module",
"bin": {
"greeting-server": "./build/index.js"
},
"scripts": {
"build": "tsc && chmod 755 build/index.js"
},
"files": ["build"]
}{
"mcpServers": {
"greeting": {
"command": "node",
"args": ["/ABSOLUTE/PATH/TO/greeting-server/build/index.js"]
}
}
}Common errors and fixes
SyntaxError: Unexpected token ... is not valid JSON
Cause: A `console.log` (yours or a dependency's) wrote a line to stdout, which is the protocol channel on stdio.
Fix: Log with `console.error` instead. The quoted token is the first character of the stray line, which usually points to the call that wrote it.
claude-desktop, claude-code, vs-code Source
TS2589: Type instantiation is excessively deep and possibly infinite
Cause: Two copies of zod in the dependency tree.
Fix: Run `npm ls zod` and align on one Zod 4 version; force it with "overrides" (npm, pnpm) or "resolutions" (Yarn).
ReferenceError: crypto is not defined
Cause: The process runs on Node.js 18 or earlier. Every @modelcontextprotocol/* package requires Node.js 20.
Fix: Upgrade Node.js to 20 or later (22.19+ if you also use the Inspector).
Module '"@modelcontextprotocol/server"' has no exported member 'SSEServerTransport'
Cause: v2 no longer ships the server-side SSE transport.
Fix: Serve new code over Streamable HTTP. If a client needs SSE, import it from `@modelcontextprotocol/server-legacy/sse`.
Server lists its tools in the Inspector's legacy era but not with --protocol-era modern
Cause: The server was started with `server.connect(new StdioServerTransport())`, which only serves 2025-era clients.
Fix: End the file with `serveStdio(createServer)` and return a new McpServer from the factory on every call.
Server does not show up in Claude Desktop
Cause: No build output, a relative path in "args", or Claude Desktop was not fully restarted.
Fix: Run `npm run build`, use the absolute path to build/index.js, then quit with Cmd+Q (macOS) or Quit from the tray (Windows) and reopen. Logs: `~/Library/Logs/Claude/mcp*.log`.
claude-desktop Source
The server runs but Copilot in VS Code never calls it
Cause: Copilot Chat is not in Agent mode, or VS Code cached an old tool list.
Fix: Switch the chat mode to Agent, then run MCP: Reset Cached Tools from the Command Palette.
vs-code Source
Questions
Which npm package do I install to build an MCP server in Node.js?
For new servers, npm install @modelcontextprotocol/server (SDK v2, version 2.4.0 in November 2026). The client side is @modelcontextprotocol/client. The older single package @modelcontextprotocol/sdk is v1; it lives on the v1.x branch and gets bug fixes and security updates for at least six months after the 2026-07-27 v2 release.
What Node.js version do I need?
Node.js 20 or later for the SDK; every @modelcontextprotocol/* package requires it. The MCP Inspector v2 needs Node.js 22.19 or later, so use 22.19+ if you test with it.
Is there a FastMCP for TypeScript?
Yes, but it is not the official SDK. The npm package fastmcp (repository punkpeye/fastmcp) describes itself as a TypeScript framework for building MCP servers. The official SDK is @modelcontextprotocol/server, which this guide uses.
Do I need a build step?
Not while developing: tsx runs src/index.ts directly, and Claude Code can launch npx tsx src/index.ts. The modelcontextprotocol.io tutorial compiles with tsc to build/index.js for Claude Desktop, which runs it with node.
Which schema library can I use?
Tool and prompt schemas use Standard Schema, so Zod v4, Valibot, ArkType or any compatible library works. The SDK examples import Zod with import * as z from 'zod/v4'. Keep a single Zod copy in your dependency tree; two copies cause TS2589: Type instantiation is excessively deep.
How do I test a TypeScript MCP server?
Run npx @modelcontextprotocol/inspector npx tsx src/index.ts, click Connect, open Tools and call your tool. For scripted checks use the Inspector CLI: npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list.
How do I add it to Claude Code?
Run claude mcp add <name> -- npx tsx src/index.ts from the project root, or claude mcp add <name> -- node /absolute/path/to/build/index.js after a build. Check it with /mcp inside Claude Code.
Can it run on Bun, Deno or over HTTP?
Yes. Install with bun add @modelcontextprotocol/server or deno add npm:@modelcontextprotocol/server. To serve one URL that many clients use, serve the same createServer factory over Streamable HTTP; optional packages @modelcontextprotocol/node, @modelcontextprotocol/express, @modelcontextprotocol/fastify and @modelcontextprotocol/hono wire it into those runtimes.
Why does console.log break my server?
On stdio, stdout is the protocol channel: the host parses every line as JSON-RPC. A stray console.log shows up as SyntaxError: Unexpected token ... is not valid JSON and the host drops the connection. Use console.error, which writes to stderr.
Sources
Docs, issue trackers and discussions this page is built from.
Show all 9Hide
- MCP TypeScript SDK README · github.com
- @modelcontextprotocol/server on npm · npmjs.com
- Build your first server · github.com
- fastmcp on npm · npmjs.com
- Plug into a real host · github.com
- Build an MCP server · modelcontextprotocol.io
- MCP Inspector README · github.com
- Connect Claude Code to tools via MCP · code.claude.com
- Troubleshooting · github.com