← Back to Blog
tutorialmcp-servernodejs

Building Your First MCP Server from Scratch

Write an MCP server in Node.js with a working tool, test it locally, and get it ready for mcpm publishing.

·3 min read·xapable

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";

// 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:

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

  1. tools/list tells clients what tools are available
  2. tools/call handles actual invocations
  3. inputSchema defines parameters — make them clear and descriptive
  4. 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