Tutorial11 min read·

How to set up an MCP server for Claude and Cursor (beginner-friendly, 2026)

MCP is everywhere in 2026 but most tutorials assume you already know what a server is. This guide starts from zero, ships a real working server, and walks you through wiring it to Claude, Cursor and Antigravity.

CT

CruxBit Team

Engineering & AI, CruxBit

On this page
  1. 011. Mental model in 30 seconds
  2. 022. Prerequisites
  3. 033. Scaffold the project
  4. 044. Write the server
  5. 055. Build and smoke-test
  6. 066. Wire it into Claude Desktop
  7. 077. Wire it into Cursor
  8. 088. Wire it into Antigravity
  9. 099. Debugging the inevitable "it doesn't show up"
  10. 1010. From toy to real — what to add next
  11. 1111. Patterns that will save you pain later
  12. 1212. Publishing your server
  13. 13TL;DR
  14. 14Frequently asked questions

We've written about [[mcp-explained-2026|what MCP is and why it matters]]. This post is the other half — actually building one. By the end you'll have a working MCP server in TypeScript that exposes a tool, a resource and a prompt, and it'll be wired into Claude Desktop, Cursor, and Antigravity. Total time: about an hour, including reading.

1. Mental model in 30 seconds

MCP — who talks to whom
┌─────────────┐    JSON-RPC over stdio / SSE / WebSocket
│ MCP CLIENT  │◄─────────────────────────────────────────►┌─────────────┐
│ (Claude,    │                                           │ MCP SERVER  │
│  Cursor,    │    list_tools, call_tool,                 │ (yours)     │
│  Antigrav…) │    list_resources, read_resource,         │             │
│             │    list_prompts, get_prompt               │   Tools     │
│             │                                           │   Resources │
│             │                                           │   Prompts   │
└─────────────┘                                           └─────────────┘
       │                                                          │
       │  The CLIENT decides when to call. The SERVER             │
       │  exposes the menu. The LLM inside the client             │
       │  picks from the menu when it needs to.                   │
       └──────────────────────────────────────────────────────────┘

Three things a server can offer: tools (functions the LLM can call), resources (data the client can attach to context), and prompts (templated user-invokable instructions). You don't need all three. Start with tools — that's where 90% of the value lives.

2. Prerequisites

  • Node.js 20+ installed (node -v to check)
  • An MCP client to test against (Claude Desktop is the easiest)
  • A text editor (VS Code, Cursor, whatever)

3. Scaffold the project

bash
mkdir my-first-mcp && cd my-first-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
npx tsc --init

Open the generated tsconfig.json and confirm "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext". Set "outDir": "./dist".

Then add "type": "module" to your package.json. MCP servers in 2026 are ESM-first.

4. Write the server

Create src/index.ts. This minimal server exposes one tool that returns the current weather (mocked) and one resource (a static markdown doc).

src/index.ts
typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from
  "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  ListResourcesRequestSchema,
  ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";

const server = new Server(
  { name: "weather-demo", version: "0.1.0" },
  { capabilities: { tools: {}, resources: {} } }
);

// ── Tools ────────────────────────────────────────────────────────
const GetWeatherInput = z.object({
  city: z.string().describe("City name, e.g. 'Bengaluru'"),
});

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "get_weather",
      description: "Get current weather for a city (mocked).",
      inputSchema: {
        type: "object",
        properties: {
          city: { type: "string", description: "City name" },
        },
        required: ["city"],
      },
    },
  ],
}));

server.setRequestHandler(CallToolRequestSchema, async (req) => {
  if (req.params.name !== "get_weather") {
    throw new Error(`Unknown tool: ${req.params.name}`);
  }
  const { city } = GetWeatherInput.parse(req.params.arguments);
  // Replace with a real API call when you're ready.
  return {
    content: [{
      type: "text",
      text: `Weather in ${city}: 27°C, partly cloudy. (mocked)`,
    }],
  };
});

// ── Resources ────────────────────────────────────────────────────
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
  resources: [
    {
      uri: "weather://docs/about",
      name: "About this server",
      mimeType: "text/markdown",
    },
  ],
}));

server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
  if (req.params.uri === "weather://docs/about") {
    return {
      contents: [{
        uri: req.params.uri,
        mimeType: "text/markdown",
        text: "# Weather Demo\nA tiny MCP server built as a teaching example.",
      }],
    };
  }
  throw new Error(`Unknown resource: ${req.params.uri}`);
});

// ── Start ────────────────────────────────────────────────────────
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("weather-demo MCP server running on stdio");

5. Build and smoke-test

bash
# Quick run (no build, useful in dev)
npx tsx src/index.ts

# Production build
npx tsc
node dist/index.js

The process should sit there silently (stdout is reserved for MCP). If it crashes, you'll see the error on stderr. Kill it with Ctrl+C; we'll let the client launch it next.

6. Wire it into Claude Desktop

Find your Claude Desktop config:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
  • Linux: ~/.config/Claude/claude_desktop_config.json
claude_desktop_config.json
json
{
  "mcpServers": {
    "weather-demo": {
      "command": "node",
      "args": [
        "/absolute/path/to/my-first-mcp/dist/index.js"
      ]
    }
  }
}

Restart Claude Desktop (fully quit, not just close the window). You should see a tools icon (🔌) in the conversation input. Click it — your weather-demo server should be listed with one tool. Try: "What's the weather in Bengaluru using the get_weather tool?"

7. Wire it into Cursor

~/.cursor/mcp.json
json
{
  "mcpServers": {
    "weather-demo": {
      "command": "node",
      "args": ["/absolute/path/to/my-first-mcp/dist/index.js"]
    }
  }
}

Restart Cursor. Confirm in Settings → Features → MCP — green dot = connected. Ask the agent to use your tool.

8. Wire it into Antigravity

Same shape, different file: ~/.antigravity/mcp.json. See our [[how-to-use-google-antigravity-2026|Antigravity guide]] for the full path and options.

9. Debugging the inevitable "it doesn't show up"

  1. 1

    Use an absolute path

    relative paths won't work; the client doesn't know where you launched from

  2. 2

    Check stderr

    run the same command in a terminal first. If it crashes there, it'll crash for the client

  3. 3

    Confirm the build ran

    ls dist/index.js. The most common bug is editing TS and forgetting to rebuild

  4. 4

    Fully restart the client

    Claude Desktop, Cursor and Antigravity all cache the MCP list; a soft reload won't pick up new servers

  5. 5

    Use the MCP Inspector

    npx @modelcontextprotocol/inspector node dist/index.js launches a UI you can hit your server with directly. Best debugging tool by far

10. From toy to real — what to add next

  • Replace mock with real APIs — swap the weather mock for an OpenWeather or Open-Meteo call
  • Auth — read API keys from process.env and pass them in via the env block in the client config
  • SSE / HTTP transport — for servers that need to run remotely or across a network, switch from StdioServerTransport to the SSE transport. Same handler code
  • Prompts — add ListPromptsRequestSchema and GetPromptRequestSchema to expose reusable templated prompts the user can invoke from the client
  • Pagination on resources — for large catalogs, support cursor in ListResourcesRequestSchema

11. Patterns that will save you pain later

  • One tool, one job. Don't build a Swiss-army do_stuff with a mode parameter. Models pick from a menu — keep the menu legible
  • Tool names like English verbs. create_invoice, not invoiceMgmtAction. The LLM reads names to decide what to call
  • Detailed input schemas with descriptions. Every field needs .describe(). The model uses these to know what to pass
  • Structured errors. Return { isError: true, content: [...] } with a clear message. Don't throw for expected failures — the model can recover from a clear error message; it can't recover from a crashed server
  • Idempotent tools where possible. Models retry; a tool that double-charges on retry is a bad day

12. Publishing your server

If your server is useful beyond your team, publish it. The community catalog at github.com/modelcontextprotocol/servers accepts PRs. Internal-only servers can ship as a private npm package — clients will run them via npx the same way.

TL;DR

  • MCP server = small Node process exposing tools / resources / prompts over JSON-RPC
  • Use the official @modelcontextprotocol/sdk; never write the wire format yourself
  • Log to stderr, not stdout (stdout is the protocol)
  • Use absolute paths in client configs; fully restart the client after editing
  • Debug with the MCP Inspector before debugging through a client
  • One tool, one job, English verb names, descriptive schemas

Building an MCP server for a client product or internal tooling and want a code review or production-hardening pass? Drop us a line — we ship MCP servers for clients almost every week and would be glad to take a look.


Frequently asked questions

How do I set up an MCP server for Claude Desktop?

First, build your server using Node.js and TypeScript. Then, configure Claude Desktop by editing the claude_desktop_config.json file to include your server's command and arguments. Restart Claude Desktop to see your server listed with its tools.

What are the prerequisites for building an MCP server?

You need Node.js 20+, an MCP client like Claude Desktop for testing, and a text editor such as VS Code.

Why should I log to stderr and not stdout in MCP servers?

stdout is reserved for the MCP transport channel, and writing to it can corrupt the protocol. Logging to stderr avoids this issue.

What should I do if my MCP server doesn't show up in the client?

Ensure you're using an absolute path, check for errors on stderr, confirm that the build ran, and fully restart the client. You can also use the MCP Inspector for debugging.

How can I make my MCP server more robust?

Replace mock data with real APIs, implement authentication, consider using SSE/HTTP transport for remote servers, and add structured error handling and idempotent tools.

#MCP#Tutorial#AI#TypeScript#Claude#Cursor

Have a project?

Building something we've just written about?

Drop us a line. We respond within 24 hours with a candid, no-pressure take on whether we're the right partner.