ANTIGRAVITY LABJP
Articles/Integrations
Integrations/2026-09-18Intermediate

When a session starts with MCP tools missing, where do you stop?

When MCP initialization fails at startup, the agent quietly falls back to other means and keeps going. Here is a small script that asks your servers directly which tools they expose, plus the three branches I decided on in advance for when something is missing.

MCP25Antigravity372Operations12Decision makingNode.js7

I opened Antigravity first thing in the morning and picked up where I had left off the day before. The summary that came back read well, and nothing in it made me pause.

What stopped me was scrolling back through the log a while later. There was no call to the MCP server I had connected — not a single one. Initialization had failed at startup, and I had been working for a good stretch without noticing.

The same state shows up on the forum. In a report of MCP servers failing to initialize on every launch, the connection is only created after a manual refresh, and the server itself runs fine when started on its own.

The hard part isn't the fix. It's noticing — and noticing early — that the session began without the tools you assumed were there.

The agent will not tell you something is missing

When MCP tools fail to load, nothing halts. The agent shifts to whatever else it has — web search, a file scan, general knowledge it already carries — and hands you something plausible.

The output looks fine, so you don't question it. For a while I treated "an answer came back" as proof that the connection was live. That didn't serve me well. I later found that half a day of work had been built on sources I had never actually read.

A gap is visible only when a tool fails. When the agent quietly substitutes something else, no failure event ever occurs. The absence of errors in the log didn't mean things were healthy; it meant nothing had been attempted.

Opening the config file at this point usually turns up nothing. The syntax is valid and the server starts by hand. A static check of the config takes about forty lines before startup, but this symptom lives one step further along — in whether the handshake actually completed.

Ask for tools/list yourself and read the names

There is one reliable way to check: don't trust the editor's display. Connect to the MCP server yourself and read the tool names that come back.

MCP is JSON-RPC over stdio, so you send initialize, reply with notifications/initialized, then send tools/list — and the tools that are genuinely visible come back as an array of names. The script below does this for every server in your config file and exits with code 1 if a required tool is absent.

// mcp-readiness.mjs — connect to the configured servers and print the tools they actually expose
// Usage: node mcp-readiness.mjs <mcp_config.json> [required tool names ...]
import { spawn } from 'node:child_process';
import { readFileSync } from 'node:fs';
 
const [configPath, ...required] = process.argv.slice(2);
const config = JSON.parse(readFileSync(configPath, 'utf8'));
const servers = config.mcpServers ?? config.servers ?? {};
 
const PROTOCOL_VERSION = '2025-06-18';
const TIMEOUT_MS = 8000;
 
function listTools(name, spec) {
  return new Promise((resolve) => {
    const child = spawn(spec.command, spec.args ?? [], {
      env: { ...process.env, ...(spec.env ?? {}) },
      stdio: ['pipe', 'pipe', 'pipe'],
    });
 
    let buffer = '';
    let settled = false;
    const done = (result) => {
      if (settled) return;
      settled = true;
      child.kill();
      resolve({ name, ...result });
    };
 
    const timer = setTimeout(
      () => done({ ok: false, reason: `no response within ${TIMEOUT_MS}ms` }),
      TIMEOUT_MS,
    );
    child.on('error', (e) => { clearTimeout(timer); done({ ok: false, reason: e.message }); });
 
    const send = (msg) => child.stdin.write(JSON.stringify(msg) + '\n');
 
    child.stdout.on('data', (chunk) => {
      buffer += chunk;
      let index;
      while ((index = buffer.indexOf('\n')) >= 0) {
        const line = buffer.slice(0, index).trim();
        buffer = buffer.slice(index + 1);
        if (!line) continue;
        let msg;
        try { msg = JSON.parse(line); } catch { continue; } // some servers interleave plain log lines
        if (msg.id === 1) {
          send({ jsonrpc: '2.0', method: 'notifications/initialized' });
          send({ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} });
        }
        if (msg.id === 2) {
          clearTimeout(timer);
          done({ ok: true, tools: (msg.result?.tools ?? []).map((t) => t.name) });
        }
      }
    });
 
    send({
      jsonrpc: '2.0',
      id: 1,
      method: 'initialize',
      params: {
        protocolVersion: PROTOCOL_VERSION,
        capabilities: {},
        clientInfo: { name: 'mcp-readiness', version: '1.0.0' },
      },
    });
  });
}
 
const results = await Promise.all(
  Object.entries(servers).map(([name, spec]) => listTools(name, spec)),
);
 
const seen = new Set();
for (const r of results) {
  if (r.ok) {
    r.tools.forEach((t) => seen.add(t));
    console.log(`OK   ${r.name}  ${r.tools.length} tools: ${r.tools.join(', ')}`);
  } else {
    console.log(`NG   ${r.name}  ${r.reason}`);
  }
}
 
const missing = required.filter((t) => !seen.has(t));
if (missing.length > 0) {
  console.log(`\nMissing required tools: ${missing.join(', ')}`);
  process.exit(1);
}
console.log('\nAll required tools are present');

A few of those choices came from being bitten. Printing names rather than a count matters because a server can be up while exposing fewer tools than it did on an earlier version. "Three are visible" isn't enough; you need to know whether the one you depend on is among those three.

The catch { continue; } around JSON parsing is there for the same practical reason. Some servers interleave human-readable log lines on startup, and if you can't skip a line, the very first one ends the check.

I settled on an eight second timeout because three seconds wasn't enough for servers that fetch dependencies on first run, while thirty seconds meant I learned far too late that something wasn't coming up at all.

Three branches, decided in advance

Having a way to check doesn't help much if you reason it out from scratch each time. I decided the branches ahead of time.

SituationWhat I doWhat it costs
A manual reload brings the names backCarry on — after confirming by name a second time, not by the fact that a reload happenedA few dozen seconds
One server still won't come up after a reloadStop phrasing requests around that one tool; fetch what I need myself and paste it inMore manual work, but a source I can point at
A write-capable or permissioned server won't come upI don't start that work in this session. I fix the configuration firstOne item slides to another day

The axis I split on is whether the tool fetches or writes.

Against my expectations, the fetching side turned out to be the dangerous one. A missing write tool announces itself: nothing changes, and you notice immediately. A missing read tool gets replaced by something plausible, and that substitute flows straight into the deliverable. Tracing provenance backwards afterwards cost far more than the work itself.

So my line now is that if even one read-side tool is absent, I stop that task. It isn't a judgment about the agent's ability. I simply have no grounds to trust the result.

Put the stop signal on your side of the process

What I settled on after that morning was this: don't delegate the check to the agent. Asking "can you see the MCP tools?" returns an answer bounded by what it can see. What it can't see stays invisible.

Whether the tools are there is a question for my own process, not for the agent. That is the one line I try not to cross even on a rushed day.

The practice itself is plain. I run the Lab sites on a daily automated update, and the rule there has the same shape — if the data a step depends on comes back empty, it writes one line to the log and stops. Continuing in silence always finds its way back to me later. MCP is no different: deciding in advance that a non-zero exit from mcp-readiness.mjs means "don't rely on that server today" removed the hesitation entirely.

Narrowing what you hand the agent in the first place also shortens what you have to verify. Paired with scoping the MCP tools an agent receives, the morning check takes about thirty seconds.

What to do tomorrow morning

Run the script once against your own config file. You don't need to pass any required tool names yet — just read the list that comes back.

If the names you expected are there, that's your grounds for trusting the session. If they aren't, today was a day to postpone that particular task — and knowing that alone protects the next several hours.

Thank you for reading. If it makes someone else's first thirty seconds of the morning a little lighter, I'm glad.

Share

Thank You for Reading

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

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Integrations2026-06-01
Fixing spawn npx ENOENT When an Antigravity MCP Server Won't Start
Your MCP server config JSON looks correct, but Antigravity logs spawn npx ENOENT and the server stays gray. This is almost always a PATH inheritance problem, not a broken server. Here is how to diagnose and fix it.
Integrations2026-08-20
Find the one broken MCP entry before Antigravity starts, with 40 lines of Node
A single typo in your MCP settings can take every server down with it. Here is a small preflight script that catches the mistakes statically, the real output from a deliberately broken config, and the duplicate-key trap that JSON hides from you.
Integrations2026-08-03
I Measured Before Writing a Number: MCP Connect and Tool Calls Differed by 486x
Antigravity 2.4.3 lets you set a timeout per MCP server. To find a defensible number I built a stdio server, measured each boundary separately, and found why a single value cannot cover both.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links