Build an MCP Server With Claude Code Connected to It
Dated article; use the maintained reference for current instructions. Example version: FrontMCP 1.10.11. Technical checks recorded: 2026-10-26. How checks work.
Building an MCP server with a coding agent usually goes in a loop: the agent edits a tool, you restart the server, you reconnect it from Claude Code's /mcp menu, and you ask the agent to try again. The agent can write the code, but it can't see what the code does until you've done the middle two steps.
FrontMCP's frontmcp dev --stdio --serve takes those steps out. Claude Code starts it as the server's command, and it reloads your server whenever a file changes, in about half a second, without dropping Claude Code's connection. So the agent can save a change and call it in the same session, like it runs a test. We set it up on a fresh project, gave Claude Code two changes to make, and measured what happened, including what goes wrong when you connect the usual way.
Set it up
Three commands, from a new project (Installation) to a connected Claude Code:
npx frontmcp create notes-server
cd notes-server && npm install
claude mcp add --scope project notes-server -- npx frontmcp dev --stdio --serveThe last one writes .mcp.json in the project, so everyone who opens it in Claude Code gets the same server:
{
"mcpServers": {
"notes-server": {
"type": "stdio",
"command": "npx",
"args": ["frontmcp", "dev", "--stdio", "--serve"],
"env": {}
}
}
}
Start claude in the project's folder. Claude Code runs that command itself, as it runs any stdio server, so there's no separate dev server to start, and the project's add tool is there from the first message.
What happened when we asked it to change the server
We gave Claude Code (2.1.310, with Claude Opus 5.5) this prompt, in the project frontmcp create made:
This project is an MCP server, and you're connected to it as `notes-server`, running with hot reload.
1. Call its `add` tool with a=2 and b=3.
2. Add a `multiply` tool next to `add`: src/tools/multiply.tool.ts, registered in src/calc.app.ts.
3. Call the new `multiply` tool with a=4 and b=5, through the MCP server, not by running code.
4. Change `add` so it refuses negative numbers with an error that says which one is negative. Then call `add` with a=-1 and b=2 through the MCP server.
Tell me what each call returned. If a tool isn't available to you, say so instead of working around it.It did all four in one session, in 58 seconds:
addwith 2 and 3 returned{"result":5}.- It wrote
src/tools/multiply.tool.ts, modeled onadd, and registered it insrc/calc.app.ts. Each save restarted the server: 0.44 seconds for the new file, 0.62 for the registration. - Its first look for the new tool found
mcp__notes-server__multiply, and the call returned{"result":20}. - It changed
add, the server restarted in 0.53 seconds, and its first call, with -1 and 2, returned the new error:Negative numbers are not allowed: a (-1).
This is the tool it wrote, a @Tool class like the scaffold's add:
import { Tool, ToolContext, z } from '@frontmcp/sdk';
@Tool({
name: 'multiply',
description: 'Multiply two numbers',
inputSchema: {a: z.number(), b: z.number()},
outputSchema: {result: z.number()}
})
export default class MultiplyTool extends ToolContext {
async execute(input: { a: number, b: number }) {
return {
result: input.a * input.b,
};
}
}
And the check it added to add, before the sum, with this.fail():
const negativeInputs = Object.entries(input)
.filter(([, value]) => value < 0)
.map(([name, value]) => `${name} (${value})`);
if (negativeInputs.length > 0) {
this.fail(new InvalidInputError(`Negative numbers are not allowed: ${negativeInputs.join(', ')}`));
}
Nothing was restarted by hand and nothing was reconnected. Its report ended with what it hadn't done: it didn't run a type check or the tests, and didn't add tests for either change.
How it works
frontmcp dev --stdio --serve sits between Claude Code and your server (dev --stdio):
- Claude Code talks to the bridge over stdin and stdout, as it would to any stdio server. The bridge runs your server as its own child process and talks to it over a pipe, so it needs no port.
- The bridge watches the project's files. When one changes, it restarts the server and holds any call that arrives meanwhile. For a client that started with
initialize, it replays that handshake to the new server, so the session carries on. - Then it tells Claude Code. It sends
notifications/tools/list_changed, and Claude Code's nexttools/listsees the new tools and descriptions. In a separate check, the notification arrived 0.6 seconds after the save. - Your server's own output goes to
.frontmcp/dev.log, so stdout carries only MCP messages.
One thing doesn't survive a reload: anything the server kept for the session starts again, so a tool that counts calls in memory counts from zero.
Why a bridge, and not a stdio server that restarts
Claude Code's docs say what happens on each side. When a server sends list_changed, an interactive session fetches the updated list, so you don't need to reconnect (Dynamic tool updates). But when a stdio server's process exits in the middle of an interactive session, Claude Code marks it as failed, and you start it again with Reconnect in /mcp (Stdio servers that exit or fail to start). A server you run with a file watcher of its own exits on every change. The bridge is the process Claude Code started, and it doesn't exit: only the server behind it restarts.
Why not just npm run dev
The project's npm run dev also reloads: it runs frontmcp dev, which serves HTTP on port 3000 and restarts the server with tsx --watch. The scaffold's own config for Claude Code points there, at http://127.0.0.1:3000/mcp. We ran the same prompt that way too, with npm run dev in a terminal, and it also finished, but not as smoothly:
frontmcp dev --stdio --serve | HTTP to npm run dev | |
|---|---|---|
| A restart, from the save to serving again | About half a second | About 6 seconds: tsx --watch printed Waiting for graceful termination... before starting the new server |
The new multiply tool | Found at the first look | Not found twice, then found once the server was back |
| A call during a restart | Held, then answered | ECONNREFUSED; the agent retried, and the retry worked |
Over HTTP, Claude Code 2.1.310 spoke MCP 2026-07-28: it called server/discover and kept a subscriptions/listen stream open for change notifications. After each restart it opened that stream again and listed the tools, which is when it saw multiply. It got there, but the agent spent turns waiting and retrying, and a less careful agent could take ECONNREFUSED for a bug in its code.
npm run dev is still the right command when a person drives the server, with the MCP Inspector or curl. For an agent that edits and calls the same server, the bridge is the better fit.
When a save breaks the server
Agents save code that doesn't compile. We saved a syntax error into multiply.tool.ts with a client connected through the bridge:
- The connection stays. The new server exits while it starts, and the bridge logs
restart-failedand keeps the client connected. - Calls wait. A call sent while the code is broken gets no answer yet. When the fix is saved, the bridge starts the server, replays the handshake, sends
list_changedand answers the waiting call. - Or they time out. If nothing fixes it within 30 seconds, the call gets a JSON-RPC error:
-32097,dev_reload_deadline, with{"deadlineMs":30000}.
The error message names the deadline, not the syntax error: that's in .frontmcp/dev.log. So it's worth telling Claude Code where to look. A note like this in the project's CLAUDE.md does it; we didn't test it in a run, it points at the log the bridge writes:
## The dev server
Claude Code runs this server through `frontmcp dev --stdio --serve`, as `notes-server`. After changing a tool, call it through `notes-server` to check it. If a call fails with `dev_reload_deadline` or `dev_server_unreachable`, read the end of `.frontmcp/dev.log`, fix the error it shows, and call again.
Tips
- Ask for tests too. Calling a tool shows it works once. The agent in our run didn't run
npm run testor add tests until asked; the project's@frontmcp/testingtests keep the change working. - Give Claude Code FrontMCP's docs too. With the docs MCP server connected, it reads the docs for the release your project uses instead of writing FrontMCP code from memory:
claude mcp add --transport http frontmcp-docs https://mcp.frontmcp.dev. - 2026-07-28 clients need
--serve. Without it, the bridge talks to your server's HTTP port and doesn't pass on the headers MCP 2026-07-28 needs (dev --stdio).
What we ran
One machine, on October 26, 2026: Claude Code 2.1.310 with Claude Opus 5.5, in headless mode (claude -p, which refreshes the tool list on list_changed; an interactive session fetches tools, prompts and resources) with the project's .mcp.json, FrontMCP 1.10.11 and Node 24.18.1, on a project from frontmcp create notes-server. Each setup ran once with the same prompt. The restart times are from each setup's log; the notification timing and the broken-save behavior are from a separate client that drove the bridge directly. Your times will depend on the project and the machine.