Build a local TypeScript server

Create the server factory

Build one function that registers capabilities and returns a fresh server for both local and remote transports.

8 minute lesson

~~~

An MCP server has two layers:

  • capabilities define what clients can discover and call
  • a transport moves protocol messages between client and server

Keep those layers separate. Create src/server.ts:

import { McpServer } from '@modelcontextprotocol/server'
import * as z from 'zod/v4'
import { notes } from './notes.js'

export function createServer() {
  const server = new McpServer({
    name: 'project-notes',
    version: '1.0.0'
  })

  return server
}

The factory becomes the only place where we register tools, resources, and prompts. Both transports will call it, so their public surface cannot drift apart.

The fresh-instance rule matters. serveStdio() pins one factory result to a connection. createMcpHandler() creates one for an HTTP request. Do not put durable application state inside the McpServer instance.

This would be a mistake:

let currentUser = ''

A module-level variable can be shared across callers or disappear when an instance restarts. Put durable notes in a database. Pass verified request identity through the request context when authorization is added.

The .js extension in the local import is correct for NodeNext TypeScript output, even though the source file is notes.ts.

Run npm run check. At this point the server has an identity but exposes no capability. That is a useful state: nothing is available until we register it explicitly.

Lesson completed

Take this course offline

Get every free book and course as PDF and EPUB files.

Get the download library →