PlugWiki

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. 1

    Create the project

    You need Node.js 20 or later. Run:

    • mkdir greeting-server && cd greeting-server
    • npm init -y
    • npm pkg set type=module
    • npm install @modelcontextprotocol/server zod tsx
    • mkdir src

    type=module matters because the SDK ships ES modules only. tsx runs TypeScript directly, so there is no build step while you develop.

  2. 2

    Write src/index.ts

    Copy src/index.ts from the Code section. new McpServer({ name, version }) creates the server and registerTool adds a greet tool with a Zod inputSchema. The handler returns content, a list of typed blocks.

    The file ends with serveStdio(createServer), which reads requests on stdin and writes responses on stdout. Log with console.error: one console.log corrupts the stream.

  3. 3

    Run it once

    Run npx tsx src/index.ts from the project root. It prints greeting MCP server running on stdio to stderr and then waits. That is correct: a stdio server waits for a client. Stop it with Ctrl+C.

  4. 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, select greet, enter a name and run it. You get Hello, <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. 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 claude in that directory and run /mcp: greeting shows as connected with greet listed. Add --scope project to write it to .mcp.json so your team gets it.

    Prompt
    Use the greeting server to greet Ada.
  6. 6

    Build it for Claude Desktop

    Claude Desktop runs a compiled file with node. Run npm install -D typescript @types/node, add the tsconfig.json and the build script from the Code section, then npm run build to produce build/index.js.

    Add the server to claude_desktop_config.json under mcpServers with "command": "node" and the absolute path to build/index.js (file paths in the Code section). Fully quit Claude Desktop and reopen it; the server appears under + > Connectors.

  7. 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 with server.connect(new StdioServerTransport()) instead of serveStdio only answers 2025-era clients.

Code

terminal
mkdir greeting-server && cd greeting-server
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/server zod tsx
mkdir src
Node.js 20 or later. The SDK ships ES modules only, so type=module is required.
src/index.ts
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: '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');
The README's greet tool, served with serveStdio as in the SDK's first-server tutorial. Run: npx tsx src/index.ts
terminal
# 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
Commands from the TypeScript SDK tutorials.
tsconfig.json
{
  "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"]
}
From the modelcontextprotocol.io tutorial. Needed only when you compile with tsc (npm install -D typescript @types/node).
package.json (excerpt)
{
  "type": "module",
  "bin": {
    "greeting-server": "./build/index.js"
  },
  "scripts": {
    "build": "tsc && chmod 755 build/index.js"
  },
  "files": ["build"]
}
Then run npm run build to produce build/index.js.
claude_desktop_config.json
{
  "mcpServers": {
    "greeting": {
      "command": "node",
      "args": ["/ABSOLUTE/PATH/TO/greeting-server/build/index.js"]
    }
  }
}
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json. Windows: %AppData%\Claude\claude_desktop_config.json (use double backslashes in paths). Fully quit and reopen Claude Desktop.

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).

    Source

  • 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).

    Source

  • 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`.

    Source

  • 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.

    Source

  • 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 9
  1. MCP TypeScript SDK README · github.com
  2. @modelcontextprotocol/server on npm · npmjs.com
  3. Build your first server · github.com
  4. fastmcp on npm · npmjs.com
  5. Plug into a real host · github.com
  6. Build an MCP server · modelcontextprotocol.io
  7. MCP Inspector README · github.com
  8. Connect Claude Code to tools via MCP · code.claude.com
  9. Troubleshooting · github.com

Updated Nov 1, 2026.