Building a Notes MCP Server for Claude Desktop
Prerequisites
Before we crack on, you’ll need:
- Node.js installed (v18+ ideally, but honestly who’s counting)
- Claude Desktop app
- A notes folder (Obsidian vault, Nextcloud sync, or literally any folder of markdown files gathering dust)
- A mass-produced motivational mug that says “Keep Calm and Code On”
- The willingness to give an AI access to your unhinged 3am brain dumps
What We’re Building
Right. So you’ve got a pile of markdown notes somewhere on your machine. Maybe its an Obsidian vault, maybe its a folder you swore you’d organise “next weekend” six months ago. Wouldn’t it be lovely if Claude could just… read them? Search them? Write new ones for you?
That’s exactly what we’re building. A custom MCP server that lets Claude Desktop read, write, search, and list your local markdown notes, turning Claude into a proper knowledge management partner. Think of it as giving Claude a library card to your brain.

The Approach
Here’s the plan, and it doesnt take long:
- Understand MCP architecture
- Set up a TypeScript project
- Implement the notes tools
- Configure Claude Desktop to connect
Four steps. No faffing about.
Step 1: Understand MCP
Model Context Protocol is an open standard for connecting AI assistants to external tools. It uses a client-server architecture, which sounds fancy but is actually quite straightforward:
- MCP Servers expose tools and resources
- MCP Clients (Claude Desktop) connect to servers
- Transports handle communication (stdio, HTTP)
That’s it. The server says “here are the things I can do,” and the client says “brilliant, do this one.” No magic. Just a well-defined handshake.

Step 2: Set Up the Project
Spin up a new project and pull in the MCP SDK:
mkdir notes-mcp-server
cd notes-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk
npm install -D @types/node typescript
Nothing controversial there. Moving on.
Step 3: Implement the Tools
This is where the fun lives. Create src/index.ts with four tools: list, read, write, and search. The recursive file walker grabs every .md and .txt file in your notes directory, no matter how deeply you’ve nested them (we dont judge your folder structure).
import { Server } from '@modelcontextprotocol/sdk';
import * as fs from 'fs/promises';
import * as path from 'path';
const NOTES_DIR = '/path/to/your/notes';
async function getNotesRecursively(dir: string): Promise<string[]> {
const entries = await fs.readdir(dir, { withFileTypes: true });
const files = await Promise.all(entries.map(async (entry) => {
const res = path.resolve(dir, entry.name);
if (entry.isDirectory()) {
return getNotesRecursively(res);
} else if (entry.name.endsWith('.md') || entry.name.endsWith('.txt')) {
return res;
}
return null;
}));
return files.flat().filter((f): f is string => f !== null);
}
const server = new Server({
tools: {
list_notes: async () => {
const notes = await getNotesRecursively(NOTES_DIR);
return notes.map(n => path.relative(NOTES_DIR, n));
},
read_note: async ({ filename }) => {
const content = await fs.readFile(
path.join(NOTES_DIR, filename),
'utf-8'
);
return content;
},
write_note: async ({ filename, content }) => {
await fs.writeFile(
path.join(NOTES_DIR, filename),
content,
'utf-8'
);
return 'Note saved';
},
search_notes: async ({ query }) => {
const notes = await getNotesRecursively(NOTES_DIR);
const results = [];
for (const note of notes) {
const content = await fs.readFile(note, 'utf-8');
if (content.toLowerCase().includes(query.toLowerCase())) {
results.push(path.relative(NOTES_DIR, note));
}
}
return results;
}
}
});
server.start();
The whole thing is gloriously simple. Four tools, one recursive walker, zero dependencies beyond the SDK. You could absolutely make this fancier with fuzzy search or frontmatter parsing, but lets walk before we run.
Step 4: Configure Claude Desktop
Nearly there. Add this to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"notes": {
"command": "node",
"args": ["/path/to/notes-mcp-server/dist/index.js"]
}
}
}
Restart Claude Desktop and you should see the notes tools appear. If you dont, check the path is correct. It’s always the path.

The Result
Claude can now rummage through your notes like a helpful colleague who actually remembers where you put things:
- “List all my notes” shows your full notes hierarchy
- “Search for Docker” finds every note mentioning Docker
- “Read my project ideas note” retrieves specific content
- “Create a new note about X” writes directly to your notes folder

What I’d Do Differently
Add proper TypeScript type guards from the start. The MCP SDK’s arguments aren’t strongly typed, which leads to runtime errors without validation. I spent a solid chunk of my two hours chasing one of those. Learn from my suffering.
This took me about 2 hours including debugging. If it helped you, or if you’ve built something similar, give me a shout on Twitter/Bluesky.