●TEACHERS — Anthropic launches Claude for Teachers, giving verified US K-12 educators free premium access, teaching skills, and curriculum links aligned to standards in all 50 states●ADMIN — The Admin API is now in beta for every Claude Enterprise organization: member and invite calls need no beta header, while group and custom-role calls do●M365 — The Microsoft 365 connector gains write tools — draft, send, and organize email, manage calendar events and mailbox settings, and create or update files in OneDrive and SharePoint●MCP — Fixed servers from --mcp-config or .mcp.json ignoring a per-server request_timeout_ms, which left long-running tool calls timing out at the 60s default in fresh sessions●SUBAGENT — A new --forward-subagent-text flag and CLAUDE_CODE_FORWARD_SUBAGENT_TEXT variable include subagent text and thinking in stream-json output●DEADLINE — Opus 4.7 fast mode is removed on July 24, seven days out. speed: "fast" will error, so confirm your move to Opus 4.8●TEACHERS — Anthropic launches Claude for Teachers, giving verified US K-12 educators free premium access, teaching skills, and curriculum links aligned to standards in all 50 states●ADMIN — The Admin API is now in beta for every Claude Enterprise organization: member and invite calls need no beta header, while group and custom-role calls do●M365 — The Microsoft 365 connector gains write tools — draft, send, and organize email, manage calendar events and mailbox settings, and create or update files in OneDrive and SharePoint●MCP — Fixed servers from --mcp-config or .mcp.json ignoring a per-server request_timeout_ms, which left long-running tool calls timing out at the 60s default in fresh sessions●SUBAGENT — A new --forward-subagent-text flag and CLAUDE_CODE_FORWARD_SUBAGENT_TEXT variable include subagent text and thinking in stream-json output●DEADLINE — Opus 4.7 fast mode is removed on July 24, seven days out. speed: "fast" will error, so confirm your move to Opus 4.8
The MCP Server I Declared Vanished from the List — Designing Config for a Namespace You Share with the Vendor
When a vendor reserves an MCP server name, your .mcp.json declaration goes quiet instead of failing. Here is the preflight check and prefix convention I use to turn that silent gap into a loud stop before an unattended run.
One line in a changelog stopped me: "Claude Browser" is now reserved as an MCP server name. Like "Claude Preview," a user-configured server cannot register under it.
My first thought was not about blast radius. It was about an assumption I had been carrying without examining it. I had always treated the names in .mcp.json as identifiers I owned. A local file. A string I typed. Nothing to collide with.
That was wrong. Those names live in a namespace I share with the vendor.
The reserved name might already be in your config file
As an indie developer running content automation across four sites, my .mcp.json files grew organically. I named servers after what they did: github, browser, sheets. Short and readable, chosen for no deeper reason than that.
Short, readable names are attractive to vendors too. That is exactly why they get reserved.
This is a different failure class from a dependency version conflict. A package conflict fails at install time, in red text. Here, my file does not change by a single byte. Only the interpretation of it changes, on the other side of an update. Looking at the file tells you nothing.
A name collision shows up as absence, not as an error
The first thing I wanted to know was what actually comes back when a declared name hits a reserved one.
I expected an explicit refusal at startup. "This name is unavailable" would have been enough — I could stop there. As far as I could observe, no such line surfaced. The server simply does not appear in the /mcp listing.
Absence does not carry an error message.
The texture is familiar. It is the same feeling as a setting you wrote being ignored while the screen stays perfectly calm, and you cannot tell for a while whether the fault is in your syntax or in the implementation. The difference is that a timeout eventually shows itself if you wait. A name collision never does.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Catch a missing MCP server before the session starts with one command, instead of debugging it days later
✦Understand why an unattended run finishes with SUCCESS when a tool quietly disappears, and turn it fail-loud with roughly 60 lines of Node
✦Apply a server-name prefix convention and reserved-name list across several projects in the same shape
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Under unattended execution, losing one tool does not stop anything
Interactively, you still have a chance. You ask for an operation, Claude says the tool is unavailable, and you notice.
Unattended, nothing stops. This is the part I find genuinely alarming.
A scheduled session receives exactly one thing: the set of tools that actually loaded. If one is missing, Claude takes that set as given. It has no reason to mourn a tool it never saw. It does the best it can with what is in hand, and it finishes.
The log then says Status: SUCCESS.
Where you might notice
Interactive
Unattended
Startup error
None
None
Tool call failure
Stops on screen
Never invoked at all
Final status
A human senses something is off
Exits SUCCESS
Time to discovery
Minutes
Days to weeks
Silent success is the most expensive outcome. Anything that runs while nobody is watching should be made to stop, not to degrade.
Three options, and the one I threw away
Option 1: avoid names that look reserve-able, then hope
Steer clear of generic nouns like browser or preview from the start. Zero cost, and it works most of the time.
The problem is that you have no way to learn about the day it stops working. The reserved list does not grow on my schedule, and I did not like my odds in the business of predicting what gets reserved next.
Option 2: check /mcp by eye on every startup
Reliable, and impossible to run unattended. The whole reason a job is on a schedule is that nobody is watching. Demanding a human eyeball there breaks the premise.
Option 3: compare the declaration against reality, and stop on drift
Take the list of names in .mcp.json. Take the list of names that actually loaded. Compare them mechanically, and exit non-zero on any difference.
I chose this one, because it works without knowing what is on the reserved list. Instead of chasing reservations, it asks a single question: is everything I declared actually here? A reservation, a typo, a bad path — all of them land in the same net.
Designs that enumerate causes in advance tend to miss one. Designs that only inspect the shape of the result last longer.
Writing the preflight that compares declaration to reality
About 60 lines of Node, run once before the scheduled task itself starts.
The comparison
#!/usr/bin/env node// mcp-preflight.mjs// Compare what .mcp.json declares against what actually loaded.// Exit non-zero on drift so the scheduled task never starts.import { readFileSync } from "node:fs";import { execFileSync } from "node:child_process";// Known vendor-reserved names. Do not expect this to be exhaustive.const RESERVED = new Set(["claude preview", "claude browser"]);/** Server names declared in .mcp.json */function declaredServers(path = ".mcp.json") { const raw = JSON.parse(readFileSync(path, "utf8")); return Object.keys(raw.mcpServers ?? {});}/** * Server names that actually loaded. * The CLI output format is not a contract, so all parsing lives here. * When the format changes, there is exactly one place to fix. */function loadedServers() { const out = execFileSync("claude", ["mcp", "list"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], }); return out .split("\n") .map((line) => line.match(/^\s*([A-Za-z0-9_.\- ]+?)\s*:/)) .filter(Boolean) .map((m) => m[1].trim()) .filter((name) => name.length > 0);}function main() { const declared = declaredServers(); const loaded = new Set(loadedServers().map((n) => n.toLowerCase())); const missing = declared.filter((n) => !loaded.has(n.toLowerCase())); const collided = declared.filter((n) => RESERVED.has(n.toLowerCase())); if (collided.length > 0) { console.error(`Collides with a reserved name: ${collided.join(", ")}`); } if (missing.length > 0) { console.error(`Declared but not loaded: ${missing.join(", ")}`); console.error(`Loaded: ${[...loaded].join(", ") || "(none)"}`); process.exit(1); } console.log(`✅ MCP preflight: all ${declared.length} servers loaded`);}main();
Expected output:
✅ MCP preflight: all 3 servers loaded
And when something is missing:
Declared but not loaded: browser
Loaded: github, sheets
It finishes in roughly 40ms on my machine. Adding it to every session start is not something you feel.
Why the CLI parsing is quarantined in one function
The regex in loadedServers() depends on the output format I happen to observe today. That is a weak assumption. Formats change without notice, and when they do, this preflight will report "nothing loaded" and halt a perfectly healthy pipeline.
I kept it anyway. A false positive fails loudly; a missed detection succeeds quietly. The loud one costs me a morning. The quiet one costs me weeks.
If you cannot drop a weak assumption, at least collect it in one place. Knowing where to go when it breaks changes the character of the debt considerably.
Do not pipe the check into anything
One operational note that matters more than the code. When you call this preflight from a shell, do not pipe its output into head or tail.
# Wrong: the exit code becomes head's, so failures return 0node mcp-preflight.mjs | head -5# Right: call the check bare, capture output separately if needednode mcp-preflight.mjs || exit 1
I hit this myself on a different gate. I thought I had added a check; the pipe was swallowing the exit code, and it was guarding nothing. set -e does not fire either. Designing something to fail loud is wasted if the caller silences it again.
A prefix convention so you never land there at all
The preflight detects. It does not reduce collisions. Naming does that.
The convention I rolled across all four projects fits in one line: every server name carries an owner prefix.
Before
After
Intent
browser
dolice-browser
Do not occupy a bare generic noun
github
dolice-github
Separate vendor names from my config
sheets
dolice-sheets
Stay clear of future official connectors
A prefix carves out your own district inside a shared namespace. The vendor takes the claude- side; I take the dolice- side. The boundary is agreed in advance rather than discovered on impact.
I left this at warning level. Renaming existing servers means updating every reference to them, and that is not a change I want to make all at once against a running production pipeline. Let the warning nag, and fix each one while you are already in the file. That pace suited this case.
Tool-name collisions across bundled servers are a separate layer with a separate fix, and worth keeping distinct in your head — server names and tool names fail in different ways.
The trade-offs this design accepts
Worth being honest about.
False positives go up. A CLI format change, a slow server that has not appeared in the listing yet, a transient permission failure — all of these turn the preflight red. I added a short wait in the caller to cover startup lag, but the race condition is still there underneath.
The reserved list is always stale. Hand-maintaining RESERVED will always lag. But it is not doing the real work. Detection lives in missing; RESERVED only exists to hand a human a hint about the cause. It sits in a position where being out of date does not break anything.
The runbook gets one line longer. Short procedures are elegant. I still prefer a long, noisy procedure over a short one that fails silently.
None of these are solved. I put it into production unsolved, which is the accurate way to say it.
The one command to run tomorrow morning
If you have a .mcp.json in front of you, try just this:
It prints the server names you declared. Compare that list against claude mcp list once, by eye. That alone tells you whether something is missing right now.
If there is no difference, good. Write the preflight for the day there is one.
A string I typed into my own file turned out not to be mine. That feeling is going to linger. Perhaps the safer posture from here is to name things as though the names are borrowed.
Thank you for reading.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.