Loomal

Build your first MCP server: a beginner's guide

From empty folder to a tool Claude Desktop can actually call — one file of TypeScript, one config entry, and a clear picture of what's happening underneath.

An MCP server sounds grander than it is. At its simplest, it's a small program that tells an AI client 'here are the things I can do' and then does them on request. The client — Claude Desktop, Cursor, and dozens of others — handles the conversation with the model; your server just exposes capabilities in a standard format.

This guide builds the simplest real server: one tool, running locally, connected to Claude Desktop. By the end you'll understand each moving part well enough to build something genuinely useful next.

What you're actually building

Three concepts cover almost everything. Tools are functions the model can choose to call — each has a name, a description (which the model reads to decide when to use it), and a typed input schema. The transport is how client and server talk; for a local first server that's stdio, meaning the client launches your program as a subprocess and exchanges JSON-RPC messages over stdin and stdout. The client is the app hosting the model, which discovers your tools at startup and invokes them mid-conversation.

One consequence of stdio worth knowing on day one: never print to stdout in a stdio server. That channel carries protocol messages — stray console.log calls corrupt it. Log to stderr instead.

Write the server

You need Node 18+ and two packages: the official SDK and zod for input schemas. The entire server — metadata, one tool, and the transport hookup — fits in a single file.

index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({ name: "dice", version: "1.0.0" });

server.registerTool(
  "roll_dice",
  {
    description: "Roll N six-sided dice and return the results",
    inputSchema: { count: z.number().int().min(1).max(20) },
  },
  async ({ count }) => {
    const rolls = Array.from({ length: count },
      () => 1 + Math.floor(Math.random() * 6));
    return { content: [{ type: "text", text: rolls.join(", ") }] };
  },
);

await server.connect(new StdioServerTransport());

Connect it to Claude Desktop

Claude Desktop reads its server list from claude_desktop_config.json — on macOS at ~/Library/Application Support/Claude/, on Windows under %APPDATA%\Claude\. Add an entry under mcpServers whose command runs your compiled file (for example, command "node" with args pointing at the absolute path of your built index.js). Absolute paths matter: the app won't resolve relative ones from where you expect.

Fully restart Claude Desktop — quit it, don't just close the window — and look for the tools indicator in a new conversation. Ask it to roll five dice; you should see it request permission to call your tool, then return the rolls.

When it doesn't work

Almost every first-run failure is one of three things: a relative path in the config, a JSON syntax error in the config file, or stdout pollution from a logging statement. Claude Desktop's MCP logs (Library/Logs/Claude on macOS) show exactly what the subprocess printed.

For tighter iteration, run MCP Inspector against your server before involving any client at all — it gives you a UI to list tools and invoke them directly, which separates 'my server is broken' from 'my config is broken.'

Where this goes next

Swap the dice roll for something with real utility — query a database, call an internal service, transform a file — and you have a genuinely useful local tool. The bigger step is going remote: hosting the server over Streamable HTTP so any agent on the internet can reach it, at which point it can also charge per call via x402. Browsing live listings on Loomal is a fast way to see what production servers expose and how they describe their tools.

FAQ

Do I need to know anything about AI models to build an MCP server?

No. Your server never talks to a model directly — it answers structured JSON-RPC requests from a client. If you can write a function with typed inputs and return text, you can write an MCP server; the model-facing complexity lives entirely in the client.

TypeScript or Python for a first server?

Whichever you already write. The official SDKs are equivalent for a beginner's purposes — this guide uses TypeScript; the FastMCP framework makes the Python path similarly short. The concepts (tools, schemas, transports) transfer one-to-one.

Why does the tool description matter so much?

The model chooses tools by reading their descriptions, the way a developer skims function docs. A vague description means your tool gets called at the wrong times or not at all. Write it like you're telling a new teammate exactly when to reach for this function.

Can my first server earn money?

Not while it's stdio-only — local servers have no request boundary to charge at. Once you host it remotely over Streamable HTTP, you can gate tools with x402 so agents pay per call in USDC, with prices starting at $0.01. That's a natural second project, not a day-one requirement.

See what real servers look like.

Browse live MCP listings — tools, transports, and pricing included.

Browse the Loomal marketplace