Build Your First MCP Server (TypeScript)
Give Claude new tools: a read-only MCP server over your notes folder with list, read and search, tested with the MCP Inspector.
Use it with Claude or any AI tool. Copy the prompt and paste it in. The link inside gives the AI the whole page as Markdown.
Overview
MCP is how you plug your own data and tools into Claude and other AI apps. This plan builds a real, small server from an empty folder: three tools over a folder of Markdown notes, safe by design (read-only, no path escapes), tested in the official Inspector and then connected to Claude Code.
Once you have done it once, the same shape works for anything: your database, an internal API, a CLI you use every day.
Before you start: Node.js 20+, Claude Code (or Claude Desktop) and a folder with a few .md files. About 1 hour.
Build it step by step
Sign in to tick steps off-
1
Create the project
Make an empty folder and save CLAUDE.md in it. Then run:
npm init -y
npm i @modelcontextprotocol/sdk zod
npm i -D typescript @types/nodeCreate a notes folder next to it with three or four short .md files to test with.
Read CLAUDE.md. Set up this folder as a TypeScript ES module project: tsconfig.json targeting ES2022 with module and moduleResolution set to Node16, outDir build, rootDir src. Add "type": "module", a "build" script (tsc) and a "bin" entry pointing at build/index.js. Do not write server code yet.
Done when npm run build runs without errors (on an empty src/index.ts).
-
2
Start a server that does nothing (yet)
Get the skeleton right first: a server object, a stdio transport, and logging that goes to stderr only. Printing to stdout breaks the protocol, which is the number one beginner bug.
Using the official TypeScript SDK (check its README for the current API), write src/index.ts that creates an MCP server named "notes" version 0.1.0 and connects it over stdio. Read NOTES_DIR from the environment and exit with a clear message on stderr if it is missing or not a folder. Log "notes server ready" to stderr.
Done when NOTES_DIR=./notes node build/index.js starts and waits silently, with "notes server ready" on stderr.
Uses MCP TypeScript SDK
-
3
Add a safe path helper
Before any tool reads a file, make it impossible to read outside the notes folder. This is the security core of the server, so it gets its own function and its own test.
Add src/paths.ts with resolveNote(name: string): string. It must resolve the path inside NOTES_DIR (use realpath), only allow .md files, and throw a friendly error for anything outside NOTES_DIR (../, absolute paths, symlinks that point out). Add a tiny test script (node --test) with cases: a valid note, ../secret.md, /etc/passwd, a symlink pointing out.
Done when All four test cases behave as expected.
-
4
Tool 1 and 2: list_notes and read_note
Two tools with zod input schemas and short, specific descriptions. The model decides which tool to call by reading those descriptions, so write them like documentation for a colleague.
Register two tools. list_notes (no input): returns each note file name with its first heading and size in KB. read_note ({ name: string }): returns the full text of one note using resolveNote. Return errors as text content with isError true instead of throwing. Keep each description under 20 words.Done when npm run build passes and both tools are registered.
Uses CLAUDE.md
-
5
Tool 3: search_notes
Search is what makes the server genuinely useful: the assistant can find the right note without reading all of them.
Add search_notes ({ query: string, limit?: number default 5, max 20 }). Case-insensitive search over all .md files in NOTES_DIR; return the best matches with file name, line number and a one-line snippet around each hit, ranked by number of hits. Cap total output at about 4,000 characters and say when results were cut.Done when Searching a word you know is in two notes returns both with line numbers.
-
6
Test by hand in the MCP Inspector
The Inspector lets you call your tools yourself before any AI touches them. Run:
npx @modelcontextprotocol/inspector node build/index.js
Set NOTES_DIR in its environment panel, connect, open Tools and try each one, including bad input.
I am testing in the MCP Inspector. Give me a checklist of 8 calls to try (valid and invalid input for each tool, including a path escape attempt) and what the correct response looks like for each.
Done when Every call on the checklist returns what it should, and the path escape is refused.
Uses MCP Inspector
-
7
Connect it to Claude Code
Add the server to Claude Code with an absolute path to the build and your notes folder. Then ask questions about your notes in plain language.
Give me the exact claude mcp add command for this server (stdio, absolute path to build/index.js, NOTES_DIR set with --env). Then suggest three natural questions I can ask Claude that should make it use each tool.
Done when In Claude Code, /mcp shows notes as connected and a question about your notes triggers search_notes.
-
8
Polish and share
A good README is what makes other people (and future you) able to use it. Then publish it to GitHub and share the repo here on OwnlyPlans as an MCP server.
Write README.md: what the server does, the three tools with example inputs, setup for Claude Code and Claude Desktop (claude_desktop_config.json example), the safety rules, and limits. Keep it under 120 lines.
Done when A friend can set it up from the README alone.
Files and links
-
MCP TypeScript SDK (opens in a new tab) modelcontextprotocol/typescript-sdk Official SDK: README has the current server API.
-
MCP Inspector (opens in a new tab) modelcontextprotocol/inspector Official tool to call your server by hand.
-
Model Context Protocol docs (opens in a new tab) modelcontextprotocol.io Concepts: tools, resources, prompts, transports.
What people built
Show yoursNobody has shared a build yet. Yours could be the first.
Show what you built
Built this plan? Sign in to share your demo video, live app and screenshots.