Your first MCP server in 90 minutes
No theory, no protocol-spec chapter. You build a server, you connect it, you watch it run.
MCP servers are the one thing that turns your Claude or Codex from "chatbot with knowledge" into "extended arm on my system". Most beginner tutorials try to explain the protocol first. That ends with no one ever building one. Here we go the other way: we build something, it works, then you understand why.
If you go through this in full, in 90 minutes you have your own MCP server doing a concrete task (summarizing files in a folder), wired into Claude Desktop, running on your machine. That's the basis for everything Level 6 makes more complex.
1. Sort prerequisites
You need: Node.js 20 or newer (node --version in a terminal), a text editor (VS Code, Sublime, whatever you have), and Claude Desktop installed (claude.ai/download). That's all. No cloud account, no Docker, no TypeScript prerequisites.
A tip: if you've never installed Node, use nvm or the installer from nodejs.org. Don't brew install node if you might switch Node versions later, leads to version chaos.
2. Project folder
Create a new folder somewhere, e.g. ~/code/my-first-mcp. Inside:
npm init -y
npm install @modelcontextprotocol/sdk
That installs Anthropic's official SDK. You don't need more.
A tip: when noting the folder path, write it absolute (with /Users/yourname/... on Mac or C:\Users\yourname\... on Windows). Claude Desktop later needs absolute, not relative.
3. Write the server file
Create server.js in the project folder. Full content:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { readdir, readFile } from "node:fs/promises";
import { join } from "node:path";
const server = new Server(
{ name: "my-first-mcp", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
server.setRequestHandler("tools/list", async () => ({
tools: [
{
name: "folder_summary",
description: "Counts files in a folder and shows the first lines of each",
inputSchema: {
type: "object",
properties: {
path: { type: "string", description: "Absolute path to the folder" }
},
required: ["path"]
}
}
]
}));
server.setRequestHandler("tools/call", async (request) => {
if (request.params.name !== "folder_summary") {
throw new Error("Unknown tool");
}
const path = request.params.arguments.path;
const files = await readdir(path);
const summaries = await Promise.all(
files.slice(0, 10).map(async (file) => {
try {
const content = await readFile(join(path, file), "utf8");
const firstLines = content.split("\n").slice(0, 3).join("\n");
return `## ${file}\n${firstLines}`;
} catch {
return `## ${file}\n(can't be read)`;
}
})
);
return {
content: [
{ type: "text", text: `Folder: ${path}\nFiles: ${files.length}\n\n${summaries.join("\n\n")}` }
]
};
});
const transport = new StdioServerTransport();
await server.connect(transport);
That's a complete MCP server. It can do exactly one thing: take a folder path, list files, show the first three lines of each.
A tip: copy this 1:1. No changes the first time. Once it runs, you understand what each line does and can add stuff.
4. Adjust package.json
Open the package.json that npm init created. Add "type": "module" near the top so the import statements work. It should look something like:
{
"name": "my-first-mcp",
"version": "1.0.0",
"type": "module",
"main": "server.js",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0"
}
}
A tip: if you see JSON errors later, it's often a missing comma. An online JSON validator saves five minutes of guessing.
5. Local test
Before we wire up Claude, check the server even starts. Terminal in the project folder:
node server.js
If nothing happens and the cursor just blinks: good. MCP servers wait for input over stdin. Ctrl+C to abort. If an error appears, read it, it's usually a missing import or a typo.
A tip: Node error messages are often long but the first line is the important one. The rest is just the stack trace telling you exactly where.
6. Let Claude Desktop find it
Now the actual wiring. Open Claude Desktop, go to Settings, then "Developer", then "Edit Config". That opens claude_desktop_config.json. Add the server so the file looks roughly like:
{
"mcpServers": {
"my-first-mcp": {
"command": "node",
"args": ["/Users/yourname/code/my-first-mcp/server.js"]
}
}
}
Adjust the path to your own. Absolute, not relative. Save the file, fully quit Claude Desktop (not just close window. Cmd+Q on Mac, right-click → Quit on Windows), restart.
A tip: if Claude can't find the server, 90% of the time it's the path. Copy it from terminal with pwd in the project folder, then you know it's right.
7. First real call in Claude
Open a new chat in Claude Desktop. Ask something like: "Can you summarize what's in my folder /Users/yourname/Documents/notes?" (Adjust path to a real folder.) Claude should recognize that folder_summary is the right tool and call it.
You see a small tool-call icon in Claude, brief status, then the result. If that works, you just put your first MCP server into production.
A tip: the first time feels like magic. Normal. You just gave Claude a new ability that OpenAI and Anthropic don't ship by default.
8. What can break and how to fix it
The usual errors: wrong path in the config, Node version too old, Claude Desktop not restarted after config change, file permissions on the folder you want summarized. Claude Desktop has a log file at ~/Library/Logs/Claude/mcp*.log on Mac that shows what the server says on start.
A tip: if something doesn't work, first question: is the server in the config in the right place? Second question: do you see the server in Claude's developer settings tab? If both yes and it still doesn't work, the bug is in the code. Read logs, don't guess.
9. Why you understand this now
Without protocol theory you just learned what an MCP server really is: a local program waiting on stdin, answering JSON requests, and reporting tools that Claude can call. That's the entire spec in one sentence.
Everything that's built around production servers (OAuth, HTTP transport, database integration, TypeScript types, tests, logging, error handling, rate limiting) is decoration on top of this core. Once you've understood the core, you understand the rest by reading.
A tip: don't build a "pretty" server next. Build one that solves a real task for you. An MCP server that summarizes your weekly Notion page. One that reads your last three calendar entries. One that searches your local Markdown notes. The value of MCP is what it personally does for you, not how professional it looks.
10. Continuing
Once you have a running server after these 90 minutes, you've cleared the hurdle that stops 95% of all people interested. From here it gets easier. Add tools (extend tools/list, extend tools/call accordingly), build a second one. Go deeper into TypeScript and SDK types. Later an HTTP server instead of stdio so others can use it. Even later: OAuth so strangers can safely connect. All step by step, each manageable on its own.
A tip: after the 90 minutes, write down three tool ideas you want to add to your server next. That keeps momentum. If you only say "I should do MCP again sometime", it never comes back.
How it goes from here
Level 6 of the Academy walks this path structurally: plan your own server, TypeScript skeleton, tool design, deployment to your own domain, sales via marketplaces. After this 90-minute sprint, you're ready for Level 6.
And if you want your setup even cleaner: Playbook "Use memory portably" shows how your self-built servers run alongside existing memory servers, and how everything together is available simultaneously in Claude, Codex and Cursor.
For now: server runs, Claude talks to it. That's the proof. Everything else is just more of that.