Back to MCP

Building Your First MCP Server: From Zero to Claude Integration

Stop reading docs and start building. Here's a working MCP server in under an hour.

·12 min read

What We're Building

A minimal MCP server that exposes:

  • Resources: A list of tasks from a JSON file
  • Tools: Ability to add new tasks

By the end, you'll ask Claude "What tasks do I have?" and it will read your data.


Prerequisites

# Node.js 18+ required
node --version  # v18.0.0 or higher

# Create project
mkdir my-mcp-server && cd my-mcp-server
npm init -y

Step 1: Install Dependencies

npm install @modelcontextprotocol/sdk zod

@modelcontextprotocol/sdk is the official MCP SDK. zod helps with schema validation.


Step 2: Create the Data Source

// tasks.json
{
  "tasks": [
    { "id": 1, "title": "Review Q1 metrics", "status": "pending" },
    { "id": 2, "title": "Prepare board deck", "status": "in_progress" },
    { "id": 3, "title": "Schedule team offsite", "status": "completed" }
  ]
}

Step 3: Build the Server

// server.js
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { readFileSync, writeFileSync } from "fs";
import { z } from "zod";

// Load tasks
const loadTasks = () => {
  const data = readFileSync("./tasks.json", "utf-8");
  return JSON.parse(data);
};

const saveTasks = (data) => {
  writeFileSync("./tasks.json", JSON.stringify(data, null, 2));
};

// Create server
const server = new Server(
  { name: "task-manager", version: "1.0.0" },
  { capabilities: { resources: {}, tools: {} } }
);

// List available resources
server.setRequestHandler("resources/list", async () => ({
  resources: [{
    uri: "tasks://all",
    name: "All Tasks",
    description: "Complete list of tasks with status",
    mimeType: "application/json"
  }]
}));

// Read resource content
server.setRequestHandler("resources/read", async (request) => {
  const uri = request.params.uri;

  if (uri === "tasks://all") {
    const data = loadTasks();
    return {
      contents: [{
        uri,
        mimeType: "application/json",
        text: JSON.stringify(data.tasks, null, 2)
      }]
    };
  }

  throw new Error(`Unknown resource: ${uri}`);
});

// List available tools
server.setRequestHandler("tools/list", async () => ({
  tools: [{
    name: "add_task",
    description: "Add a new task to the task list",
    inputSchema: {
      type: "object",
      properties: {
        title: { type: "string", description: "Task title" },
        status: {
          type: "string",
          enum: ["pending", "in_progress", "completed"],
          default: "pending"
        }
      },
      required: ["title"]
    }
  }]
}));

// Execute tool
server.setRequestHandler("tools/call", async (request) => {
  const { name, arguments: args } = request.params;

  if (name === "add_task") {
    const data = loadTasks();
    const newTask = {
      id: Math.max(...data.tasks.map(t => t.id)) + 1,
      title: args.title,
      status: args.status || "pending"
    };
    data.tasks.push(newTask);
    saveTasks(data);

    return {
      content: [{
        type: "text",
        text: `Added task: "${newTask.title}" with ID ${newTask.id}`
      }]
    };
  }

  throw new Error(`Unknown tool: ${name}`);
});

// Start server
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Task Manager MCP Server running");

Step 4: Configure package.json

{
  "name": "my-mcp-server",
  "version": "1.0.0",
  "type": "module",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  }
}

Step 5: Connect to Claude Desktop

Edit Claude Desktop's config file:

// macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
// Windows: %APPDATA%\Claude\claude_desktop_config.json

{
  "mcpServers": {
    "task-manager": {
      "command": "node",
      "args": ["/full/path/to/my-mcp-server/server.js"]
    }
  }
}

Restart Claude Desktop.


Step 6: Test It

Open Claude Desktop and try:

  • "What tasks do I have?"
  • "Add a task: Review MCP documentation"
  • "Show me my pending tasks"

Claude will use your MCP server to read and modify your tasks.


What Just Happened

User: "What tasks do I have?"
           ↓
Claude: Recognizes it needs task data
           ↓
Claude: Calls MCP resources/list
           ↓
Your Server: Returns available resources
           ↓
Claude: Calls MCP resources/read for "tasks://all"
           ↓
Your Server: Returns task JSON
           ↓
Claude: Formats response for user
           ↓
User: Sees formatted task list

Common Issues

Server Not Appearing

  • Check the path in config is absolute
  • Restart Claude Desktop completely
  • Check server logs in Console (macOS) or Event Viewer (Windows)

Connection Errors

  • Ensure type: "module" is in package.json
  • Verify Node.js version is 18+
  • Check file permissions on tasks.json

Next Steps

  1. Add more resources: Connect to a real database
  2. Add more tools: Update, delete, filter tasks
  3. Add authentication: Protect sensitive data
  4. Deploy it: Run as a service for team access

You just built a working MCP server. Your AI can now access your data. What will you connect next?

Skip the Setup

Xtended is MCP-ready out of the box. Connect your knowledge base to Claude in minutes, not hours.

Get Started Free