Summary
When an MCP tool call fails — for example project_path pointing at a directory with no
graphify-out/graph.json — the server returns a result with isError unset and delivers the
failure as ordinary text content:
Error executing graph_stats: graph.json not found: C:\some\path\graphify-out\graph.json
An LLM reading that will understand it perfectly, so the interactive experience is fine. But
anything that branches programmatically on the MCP error flag reads a missing graph as a
successful call. That rules the flag out for health checks, CI gates, and rollout scripts — which
is a shame, because a multi-project server addressed by project_path is exactly the setup where a
script wants to ask "is this project's graph actually there?".
The MCP spec's isError field exists for precisely this distinction: a tool-level failure that the
model should see, as opposed to a protocol error. Setting it costs nothing and does not change what
the model reads.
Repro
Any MCP client. Minimal version using the Python SDK:
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
params = StdioServerParameters(command="graphify-mcp", args=["/path/to/a/real/graph.json"])
async with stdio_client(params) as (r, w):
async with ClientSession(r, w) as s:
await s.initialize()
res = await s.call_tool("graph_stats", {"project_path": "/a/dir/with/no/graph"})
print("is_error:", getattr(res, "is_error", None))
print("content :", "\n".join(c.text for c in res.content))
asyncio.run(main())
Actual
is_error: False
content : Error executing graph_stats: graph.json not found: /a/dir/with/no/graph/graphify-out/graph.json
Expected
is_error: True
content : Error executing graph_stats: graph.json not found: ...
Same text, flag set.
Notes
- The rest of the behaviour here is good and worth saying so: a bad
project_path is handled as a
tool-level failure that leaves the server alive and still serving every other graph. An error
call between two good ones changed nothing about either. The docstring in serve.py says this is
deliberate — "A client-supplied project_path must instead become a tool error, so the shared MCP
server can continue serving other graphs" — so the intent is clearly there; it just is not
reflected in the result flag.
- Verified on a server started with a valid default graph and on one started without, so it
is not specific to the pure multi-project mode.
Environment
- graphify 0.9.39,
graphify-mcp over stdio
- Python 3.14.2, Windows 11
- MCP Python SDK exposing
CallToolResult.is_error
Summary
When an MCP tool call fails — for example
project_pathpointing at a directory with nographify-out/graph.json— the server returns a result withisErrorunset and delivers thefailure as ordinary text content:
An LLM reading that will understand it perfectly, so the interactive experience is fine. But
anything that branches programmatically on the MCP error flag reads a missing graph as a
successful call. That rules the flag out for health checks, CI gates, and rollout scripts — which
is a shame, because a multi-project server addressed by
project_pathis exactly the setup where ascript wants to ask "is this project's graph actually there?".
The MCP spec's
isErrorfield exists for precisely this distinction: a tool-level failure that themodel should see, as opposed to a protocol error. Setting it costs nothing and does not change what
the model reads.
Repro
Any MCP client. Minimal version using the Python SDK:
Actual
Expected
Same text, flag set.
Notes
project_pathis handled as atool-level failure that leaves the server alive and still serving every other graph. An error
call between two good ones changed nothing about either. The docstring in
serve.pysays this isdeliberate — "A client-supplied
project_pathmust instead become a tool error, so the shared MCPserver can continue serving other graphs" — so the intent is clearly there; it just is not
reflected in the result flag.
is not specific to the pure multi-project mode.
Environment
graphify-mcpover stdioCallToolResult.is_error