Building Your First MCP Server from Scratch
Before publishing to mcpm, you need an MCP server. This tutorial builds one from zero — no prior MCP experience required.
What You'll Build
A Todo MCP Server with two tools:
add_todo — add a task
list_todos — list all tasks
Prerequisites
- Node.js 18+
- Basic JavaScript knowledge
Step 1: Initialize the Project
mkdir todo-mcp && cd todo-mcp
npm init -y
npm install @modelcontextprotocol/sdk
Step 2: Write the Server
Create index.js:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const todos = [];
const server = new Server({
name: "todo-mcp",
version: "1.0.0",
}, {
capabilities: { tools: {} }
});
server.setRequestHandler("tools/list", async () => ({
tools: [
{
name: "add_todo",
description: "Add a new todo item to the list",
inputSchema: {
type: "object",
properties: {
task: {
type: "string",
description: "The task description"
},
priority: {
type: "string",
enum: ["low", "medium", "high"],
description: "Task priority (default: medium)"
}
},
required: ["task"]
}
},
{
name: "list_todos",
description: "List all current todos",
inputSchema: {
type: "object",
properties: {
filter: {
type: "string",
enum: ["all", "low", "medium", "high"],
description: "Filter by priority (default: all)"
}
}
}
}
]
}));
server.setRequestHandler("tools/call", async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case "add_todo": {
const todo = {
id: todos.length + 1,
task: args.task,
priority: args.priority || "medium",
created: new Date().toISOString()
};
todos.push(todo);
return {
content: [{
type: "text",
text: `✅ Added: "${args.task}" (priority: ${todo.priority})`
}]
};
}
case "list_todos": {
const filter = args.filter || "all";
const filtered = filter === "all"
? todos
: todos.filter(t => t.priority === filter);
if (filtered.length === 0) {
return {
content: [{ type: "text", text: "No todos found. 📭" }]
};
}
const list = filtered
.map(t => `[${t.priority.toUpperCase()}] ${t.task}`)
.join("\n");
return {
content: [{
type: "text",
text: `📋 Todos:\n${list}`
}]
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
});
const transport = new StdioServerTransport();
await server.connect(transport);
Step 3: Test Locally
Run the server:
node index.js
The server starts and listens on stdio. To test with a real MCP client, configure Claude Desktop or Cursor to point to this server.
Claude Desktop Configuration
Add to claude_desktop_config.json:
{
"mcpServers": {
"todo": {
"command": "node",
"args": ["/absolute/path/to/todo-mcp/index.js"]
}
}
}
Restart Claude Desktop. Ask: "Add a todo: buy groceries with high priority." Then: "List all todos."
Step 4: Prepare for mcpm
Write a README (see our Publishing tutorial) and ensure your package.json is complete. Then:
mcpm-dev publish
Key Takeaways
tools/list tells clients what tools are available
tools/call handles actual invocations
inputSchema defines parameters — make them clear and descriptive
- Return structured content — not raw text
Next Steps
- Add more tools (delete, update, search)
- Replace in-memory storage with SQLite or PostgreSQL
- Add authentication
- Publish to mcpm
#MCP #Tutorial #NodeJS #mcpm #AIAgents
Building Your First MCP Server from Scratch
Before publishing to mcpm, you need an MCP server. This tutorial builds one from zero — no prior MCP experience required.
What You'll Build
A Todo MCP Server with two tools:
add_todo— add a tasklist_todos— list all tasksPrerequisites
Step 1: Initialize the Project
mkdir todo-mcp && cd todo-mcp npm init -y npm install @modelcontextprotocol/sdkStep 2: Write the Server
Create
index.js:import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; // In-memory storage (replace with a database in production) const todos = []; const server = new Server({ name: "todo-mcp", version: "1.0.0", }, { capabilities: { tools: {} } }); // ── List available tools ────────────────────────── server.setRequestHandler("tools/list", async () => ({ tools: [ { name: "add_todo", description: "Add a new todo item to the list", inputSchema: { type: "object", properties: { task: { type: "string", description: "The task description" }, priority: { type: "string", enum: ["low", "medium", "high"], description: "Task priority (default: medium)" } }, required: ["task"] } }, { name: "list_todos", description: "List all current todos", inputSchema: { type: "object", properties: { filter: { type: "string", enum: ["all", "low", "medium", "high"], description: "Filter by priority (default: all)" } } } } ] })); // ── Handle tool calls ──────────────────────────── server.setRequestHandler("tools/call", async (request) => { const { name, arguments: args } = request.params; switch (name) { case "add_todo": { const todo = { id: todos.length + 1, task: args.task, priority: args.priority || "medium", created: new Date().toISOString() }; todos.push(todo); return { content: [{ type: "text", text: `✅ Added: "${args.task}" (priority: ${todo.priority})` }] }; } case "list_todos": { const filter = args.filter || "all"; const filtered = filter === "all" ? todos : todos.filter(t => t.priority === filter); if (filtered.length === 0) { return { content: [{ type: "text", text: "No todos found. 📭" }] }; } const list = filtered .map(t => `[${t.priority.toUpperCase()}] ${t.task}`) .join("\n"); return { content: [{ type: "text", text: `📋 Todos:\n${list}` }] }; } default: throw new Error(`Unknown tool: ${name}`); } }); // ── Start the server ───────────────────────────── const transport = new StdioServerTransport(); await server.connect(transport);Step 3: Test Locally
Run the server:
The server starts and listens on stdio. To test with a real MCP client, configure Claude Desktop or Cursor to point to this server.
Claude Desktop Configuration
Add to
claude_desktop_config.json:{ "mcpServers": { "todo": { "command": "node", "args": ["/absolute/path/to/todo-mcp/index.js"] } } }Restart Claude Desktop. Ask: "Add a todo: buy groceries with high priority." Then: "List all todos."
Step 4: Prepare for mcpm
Write a README (see our Publishing tutorial) and ensure your
package.jsonis complete. Then:Key Takeaways
tools/listtells clients what tools are availabletools/callhandles actual invocationsinputSchemadefines parameters — make them clear and descriptiveNext Steps
#MCP #Tutorial #NodeJS #mcpm #AIAgents