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