# Build an MCP Server With Claude Code Connected to It

> Develop an MCP server with Claude Code connected through frontmcp dev --stdio --serve: hot reload in half a second, and the next call runs the new code.

Source: https://frontmcp.dev/blog/claude-code-hot-reload
Published: 2026-10-26
Author: David Antoon
Example version: FrontMCP 1.10.11
Technical checks recorded: 2026-10-26
Scope: Dated article; use the maintained reference for current instructions. Its presence in a release snapshot does not verify its code against that release.

*[Illustration: A Claude Code session connected to notes-server calls add with 2 and 3 and gets 5, then writes src/tools/multiply.tool.ts and edits src/calc.app.ts. The save goes to frontmcp dev --stdio --serve, which restarts the server in about half a second, holds calls meanwhile and keeps the session, and sends list_changed back. The server now has add and a new multiply tool, and the same session calls multiply with 4 and 5 and gets 20, then calls add with -1 and 2 and gets the new error, Negative numbers are not allowed.]*

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.

<a id="set-it-up"></a>

## Set it up

Three commands, from a new project ([Installation](https://frontmcp.dev/learn/installation)) to a connected Claude Code:

```bash
npx frontmcp create notes-server
cd notes-server && npm install
claude mcp add --scope project notes-server -- npx frontmcp dev --stdio --serve
```

The last one writes `.mcp.json` in the project, so everyone who opens it in Claude Code gets the same server:

```json
{
  "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](https://frontmcp.dev/reference/deployment/mcp-clients#over-stdio), so there's no separate dev server to start, and the project's `add` tool is there from the first message.

<a id="what-happened-when-we-asked-it-to-change-the-server"></a>

## 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:

```text title="The prompt"
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:

1. `add` with 2 and 3 returned `{"result":5}`.
2. It wrote `src/tools/multiply.tool.ts`, modeled on `add`, and registered it in `src/calc.app.ts`. Each save restarted the server: 0.44 seconds for the new file, 0.62 for the registration.
3. Its first look for the new tool found `mcp__notes-server__multiply`, and the call returned `{"result":20}`.
4. 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`](https://frontmcp.dev/reference/sdk/tool) class like the scaffold's `add`:

```ts
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()`](https://frontmcp.dev/reference/sdk/fail):

```ts
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.

<a id="how-it-works"></a>

## How it works

*[Illustration: Claude Code, the project's src folder, frontmcp dev --stdio --serve and your server. Claude Code calls add with 2 and 3 through the bridge, which passes it to the server over a pipe, and gets 5 back. Claude Code writes multiply.tool.ts; the bridge's watcher sees the change and restarts the server, in about half a second, while calls wait. The bridge sends notifications/tools/list_changed to Claude Code, which asks for tools/list, gets add and multiply, calls multiply with 4 and 5, and gets 20.]*

`frontmcp dev --stdio --serve` sits between Claude Code and your server ([`dev --stdio`](https://frontmcp.dev/reference/cli#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`](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#list-changed-notification), and Claude Code's next `tools/list` sees 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.

<a id="why-a-bridge-and-not-a-stdio-server-that-restarts"></a>

### 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](https://code.claude.com/docs/en/mcp#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](https://code.claude.com/docs/en/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.

<a id="why-not-just-npm-run-dev"></a>

## 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:

*[Illustration: One edit in two setups, from the moment Claude Code saves add.tool.ts. HTTP to npm run dev: tsx --watch restarts the server, which isn't listening for about 6 seconds while tsx waits for the old server to exit; a call to add with -1 and 2 inside that window gets ECONNREFUSED, and a retry after it gets the new error. frontmcp dev --stdio --serve: the restart takes about 0.5 seconds, and the same call gets the new error, Negative numbers are not allowed: a (-1).]*

| | `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](https://frontmcp.dev/reference/cli#frontmcp-inspector) or `curl`. For an agent that edits and calls the same server, the bridge is the better fit.

<a id="when-a-save-breaks-the-server"></a>

## 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-failed` and 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_changed` and 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:

```md
## 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.
```

<a id="tips"></a>

## Tips

- **Ask for tests too.** Calling a tool shows it works once. The agent in our run didn't run `npm run test` or add tests until asked; the project's [`@frontmcp/testing`](https://frontmcp.dev/reference/testing) tests keep the change working.
- **Give Claude Code FrontMCP's docs too.** With the [docs MCP server](https://frontmcp.dev/learn/docs-for-your-agent) 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`](https://frontmcp.dev/reference/cli#dev---stdio)).

<a id="what-we-ran"></a>

## 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.
