How to build an MCP server, and when to skip it
Build a small MCP server in about thirty lines of JavaScript, connect it to Claude Code, and learn when an existing server or gateway is the better choice.
The looot team · · 7 min read
On this page
You have a tool you want your agent to use. A script that checks your own database, a function that formats a report, a lookup against an internal service. The agent cannot call it until it speaks the Model Context Protocol, so you search for how to build an MCP server.
This guide builds one from nothing in about thirty lines of JavaScript, connects it to Claude Code, and then covers the part most tutorials skip: when you should not build a server at all. If the data you need already sits behind an API that someone else keeps running, writing your own server is extra work.
What do I need to build an MCP server?
You need three things: a list of tools, a handler for each tool and a transport. A tool has a name, a description and an input schema. The handler runs when the agent calls it. The transport is how the agent connects, either a local process over standard input and output or a remote endpoint over HTTP.
If these terms are new, what is an MCP server explains the protocol in plain words. The official Model Context Protocol site holds the specification, and the TypeScript SDK is the library the example below uses.
How do I write a minimal MCP server?
Create a project, install the SDK and write one tool. Pin exact versions, as with any dependency you run on your machine.
mkdir word-count-mcp && cd word-count-mcp
npm init -y
npm install --save-exact @modelcontextprotocol/sdk zodThen write the server in a file called `server.mjs`. This one has a single tool that counts the words in a text.
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: "word-count", version: "1.0.0" });
server.tool(
"count_words",
"Count the words in a text",
{ text: z.string() },
async ({ text }) => ({
content: [{ type: "text", text: String(text.split(/\s+/).filter(Boolean).length) }],
}),
);
await server.connect(new StdioServerTransport());The SDK changes its method names between versions, so check the SDK page for the current way to register a tool. The shape stays the same: a name, a description, a schema and a handler that returns content.
How do I connect my MCP server to Claude Code?
A local server runs as a child process, so you give Claude Code the command that starts it.
claude mcp add word-count node /full/path/to/server.mjsStart a new Claude Code session and ask it to count the words in a paragraph. It lists the tool, calls it and shows the result. If the tool does not appear, run the server by hand and read its error. The usual causes are a wrong path, a missing dependency or output written to standard output that is not protocol messages.
That last cause matters. A stdio server must write only protocol messages to standard output. Send logs to standard error instead.
What makes a good MCP tool?
Three habits separate a tool an agent uses well from one it misuses.
A description that says when to use it. The model picks a tool from its name and description. "Count the words in a text" is clear. "Utility function" is not.
A narrow input schema. Ask for what the tool needs and nothing more. A schema with a dozen optional fields gives the model a dozen ways to be wrong.
A result that is short and structured. Return the answer, not a page of logs. Every token of output goes into the agent's context.
A good test of all three is to read the tool list as if you were the model. If two tools sound alike, merge them or rename one. If a description needs a paragraph to explain, the tool does too much. Split it into two.
Add one more for anything that costs money or changes data: a limit. Cap the rows a call can write and the spend a call can cause, and refuse the rest with a clear message.
When should I skip building an MCP server?
Skip it when the job is calling someone else's data. A server that wraps a third-party API means you now run code, hold a key, handle errors and keep up with their changes, to do something an existing connection already does.
| The tool is | Build your own | Use an existing server or gateway |
|---|---|---|
| Your database or internal service | Yes | No |
| A script only you run | Yes | No |
| Search, scraping, SEO or enrichment data | No | Yes |
| Social data from public profiles | No | Yes |
| A vendor's full product with its own server | No | Yes |
For data jobs, one connection to a gateway replaces a server per provider. The MCP gateway post covers when that is the right call, and MCP vs API for agents covers when a plain API is simpler still.
What does looot's server do that mine does not?
looot runs a hosted server at `https://api.looot.ai/mcp` that gives an agent a short set of tools to search a catalog of data endpoints, inspect one, and run it with a price shown first. You did not write a tool per provider, and you do not hold the provider keys.
The two are not rivals. Build yours for the tools only you have, and connect the looot server for the public data. An agent can use both in one session. The Claude MCP servers post explains how to keep that list short.
How do I test and harden my server?
Test the handler before you test the agent. Call the function with a normal input, an empty input and an input ten times larger than you expect. A tool that crashes on an empty string will crash in front of the model, and the model will not know why.
Then harden it in three steps. Validate every input with the schema, and reject what does not fit. Return errors as short messages that say what to change, because the model reads them and tries again. And log every call to standard error with the arguments and the time, so you can see what the agent actually asked.
If the tool reads files or runs commands, restrict it to one folder and one list of allowed commands. An agent can be talked into things by the text it reads, so a tool with broad access is a risk even when you wrote it yourself.
Try it: a prompt for your agent
This prompt checks your build and your connection to the data server in one go.
Prompt for your agent
Check my MCP setup
What it cannot do
- This server is local. It runs when Claude Code starts it and stops when the session ends. A server other people can reach needs a remote transport, hosting and authentication, which this guide does not cover.
- It has no authentication or spend limit. Add both before the tool touches anything that matters.
- It does not run on a timer. A job that runs overnight needs scheduled runs, not available yet in looot, or your own scheduler.
- The SDK moves quickly. The example is a starting shape, so check the current docs before you copy it into a project.
Last checked 2026-10-03 against the looot skill file. The SDK example was not run for this post, so run it once before you rely on it. Author: the looot team.
Questions
Keep reading
What is an MCP server? A plain explanation
An MCP server gives an AI agent tools it can call. What a tool call looks like, and how one connection can reach many providers.
Walid Boulanouar · · 5 min read
MCP gateway: what it does and when an agent needs one
An MCP gateway puts many tools behind one connection, one login and one bill. How it works, when you need one, and what looot's gateway does.
The looot team · · 7 min read
MCP vs API for agents: when each one fits
MCP and a plain API both let an agent reach outside data. What the agent handles in each, when to pick which, and a prompt to test both on your job.
Walid Boulanouar · · 5 min read
Try it with the agent you already use
Start your workspace, top up, and paste one prompt into your agent.