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

- Category: Developer tools
- Version: 1.0
- Source: https://ownlyplans.com/bhaskar/build-your-first-mcp-server/

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

## Tech stack

- MCP
- Node.js
- TypeScript

## Build steps

### 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/node

Create a notes folder next to it with three or four short .md files to test with.

Prompt:

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

Prompt:

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

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

Prompt:

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

Prompt:

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

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

Prompt:

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

Prompt:

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

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

Prompt:

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

Prompt:

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

## Assets

### CLAUDE.md — Project root. Cursor users: save as AGENTS.md.

```
# Project rules: my first MCP server

## What we are building
A small MCP server in TypeScript that gives an AI assistant three tools over a local notes folder:
list_notes, read_note and search_notes. It runs over stdio and is used from Claude Code or Claude Desktop.

## Stack
- Node.js 20+, TypeScript, ES modules
- @modelcontextprotocol/sdk (official TypeScript SDK)
- zod for tool input schemas

## Rules
- Never write to stdout except through the MCP transport. Use console.error for logs (stdout is the protocol channel).
- Every tool validates input with zod and returns a clear text error instead of throwing.
- Read-only: no tool may create, change or delete files.
- Only files inside NOTES_DIR can be read. Resolve paths and reject anything outside it (no ../ escapes, no symlinks out).
- Keep tool descriptions short and specific: the model chooses tools by reading them.
- After each change run: npm run build. Fix type errors before moving on.
```

### MCP TypeScript SDK — Official SDK: README has the current server API.

https://github.com/modelcontextprotocol/typescript-sdk

### MCP Inspector — Official tool to call your server by hand.

https://github.com/modelcontextprotocol/inspector

### Model Context Protocol docs — Concepts: tools, resources, prompts, transports.

https://modelcontextprotocol.io


## Build instructions by tool

### Claude Code

Open a terminal in an empty folder, run `claude`, then paste the prompt.

```text
Build the "Build Your First MCP Server (TypeScript)" app from the OwnlyPlans blueprint at https://ownlyplans.com/bhaskar/build-your-first-mcp-server/.
Stack: MCP, Node.js, TypeScript.
Follow the plan's steps in order: Create the project → Start a server that does nothing (yet) → Add a safe path helper → Tool 1 and 2: list_notes and read_note → Tool 3: search_notes → Test by hand in the MCP Inspector → Connect it to Claude Code → Polish and share.
Download the full plan (Markdown) from the link above and keep it in the project root as PLAN.md.
Ask me before adding anything not in the plan. (Prepared for Claude Code.)
```

### Cursor

Open an empty folder in Cursor, start an Agent chat, then paste the prompt.

```text
Build the "Build Your First MCP Server (TypeScript)" app from the OwnlyPlans blueprint at https://ownlyplans.com/bhaskar/build-your-first-mcp-server/.
Stack: MCP, Node.js, TypeScript.
Follow the plan's steps in order: Create the project → Start a server that does nothing (yet) → Add a safe path helper → Tool 1 and 2: list_notes and read_note → Tool 3: search_notes → Test by hand in the MCP Inspector → Connect it to Claude Code → Polish and share.
Download the full plan (Markdown) from the link above and keep it in the project root as PLAN.md.
Ask me before adding anything not in the plan. (Prepared for Cursor.)
```

### Lovable

Start a new Lovable project and paste the prompt as your first message.

```text
Build the "Build Your First MCP Server (TypeScript)" app from the OwnlyPlans blueprint at https://ownlyplans.com/bhaskar/build-your-first-mcp-server/.
Stack: MCP, Node.js, TypeScript.
Follow the plan's steps in order: Create the project → Start a server that does nothing (yet) → Add a safe path helper → Tool 1 and 2: list_notes and read_note → Tool 3: search_notes → Test by hand in the MCP Inspector → Connect it to Claude Code → Polish and share.
Download the full plan (Markdown) from the link above and keep it in the project root as PLAN.md.
Ask me before adding anything not in the plan. (Prepared for Lovable.)
```

## Definition of done

- Every build step is complete and the app runs locally.
- The main user flow works end to end.
- Share your build on OwnlyPlans with a demo link, build time and tool.
