Skip to content

graphify-mcp: tool failures return isError unset, so a missing project_path graph is indistinguishable from success to any programmatic caller #2714

Description

@YNFM

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions