Build a local TypeScript server
Set up the TypeScript project
Create a small Node.js project using the current MCP TypeScript server package, Zod schemas, and a repeatable development command.
Before going into the code, complete the MCP Course. I’ll assume you already know why tools, resources, and prompts are three different things.
We’re going to build one small server called project-notes. It exposes a few project notes to an AI client. The same server will run locally over stdio and remotely over HTTP, with no duplicated code between the two.
You need Node.js 20 or later. Let’s create the project:
mkdir project-notes-mcp
cd project-notes-mcp
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/server zod tsx
npm install --save-dev typescript @types/node
mkdir src
@modelcontextprotocol/server is the v2 SDK package. Version 2 is the stable line now. zod gives us the schemas that describe what every tool accepts and returns. tsx runs TypeScript directly, so we don’t need a build step while developing.
Pin the exact versions in your lockfile and write them down when you test the server. MCP moves fast, and “it worked” means little without the version it worked on.
Add two scripts to package.json:
{
"scripts": {
"dev": "tsx src/index.ts",
"check": "tsc --noEmit"
}
}
dev starts the server. check asks the TypeScript compiler to type-check everything without writing any file. We’ll run check at the end of every lesson.
Create tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noEmit": true,
"types": ["node"]
},
"include": ["src/**/*.ts"]
}
Two settings matter here. type=module in package.json is required because the SDK ships as ES modules. NodeNext is the reason local imports will use a .js extension even when the source file ends in .ts. It looks odd the first time. It’s correct, and you’ll see it in the next lesson.
Now run npm run check. If src is still empty, TypeScript complains:
error TS18003: No inputs were found in config file 'tsconfig.json'.
That’s not a problem with our config. tsc needs at least one file to look at. Create a placeholder src/index.ts containing the single line export {} and run the check again. This time it prints nothing and exits with code 0.
That silent exit is our baseline. From now on, when the check breaks, we know the problem is in the code we just added and not in the setup. The placeholder goes away in a few lessons, when the real stdio entry point takes its place.
Lesson completed