Repository navigation
mcp: an execution failure is a tool error (isError), not an answer (#2006) - #2007
G6KlayWang wants to merge 1 commit into
Conversation
…xiomCodeAI#2006) A call the CLI could not carry out — bash missing, the verb crashed or was killed, an exit no verb uses, silence, a timeout with no build running, a refusal (exit 2) — came back as an ordinary result on both server paths; only schema-invalid arguments were flagged. run() now raises the server's own ToolError with the same words, which the SDK and the fallback both mark isError, so the model still reads the correction. An answer, path's "no chain" (exit 1 in words) and the note that a graph is still being built (exit 2 while the build lock is held) stay ordinary results. AXIOMCODE_MCP_TIMEOUT (seconds) exists so a test can see the timeout. tests/mcp.py checks nine cases per launch mode, on the fallback and, with AXIOMCODE_SDK_PYTHON, on the SDK.
|
Running tests and validating #2006. Can you please check it on Windows and validate it? In the mean time I'm going to run e2e tests to ensure no regressions. |
Windows: worksTested on Windows 11 with Git Bash 5.2.32, Python 3.11, mcp 2.3.0 and the released engine 0.2.2 ( Synthetic, 44 of 44 holdThis is
Real engine, 13 of 13 holdI ran 0.2.2 with this PR's two files on a small Python repo through
Both modes were served by the SDK and gave the same results. The "being built" control is covered only by the synthetic run, because the real build finished in about 12 s. Separate, and already true before this PROn Windows a timeout doesn't return on time. Killing |
Fixes #2006
What changed
A call the CLI could not carry out came back as an ordinary result on both server paths; only schema-invalid arguments carried
isError.run()inplugins/axiomcode/mcp/server.pynow raises the server's ownToolErrorwith the same words, which the SDK and the fallback both markisError: true, so the client sees the failure and the model still reads the correction.plugins/axiomcode/mcp/_fallback.py: aToolErrorclass._handlereturns it as{"content": [...], "isError": true}with the words alone; any other exception keeps theTypeName:prefix, as before.plugins/axiomcode/mcp/server.py:ToolErroris resolved from_fallbackwhen the fallback serves and from the SDK'sexceptionsmodule otherwise.repository(args, cwd)names the repository a call asks about;failed(r, repo)decides what a failure is (table below);run()raises on a failure and returns the text otherwise;answer()passes both outcomes throughplain(), so a flag the tool cannot take is named in neither.AXIOMCODE_MCP_TIMEOUT(seconds, default 900) exists so a test can see the timeout.buildingbelow isax_fresh.building(realpath(repo)), the build-lock proberun()already used on a timeout.OSError)graph refresh:lines) or "(no output, exit 0)"path's "no chain of resolved calls connects …"Why a refusal is an error: a wrong value ("nothing named X") is the same class as a wrong parameter name, which the server already refuses with
isError: true(#1567,tests/mcp.pycheck_arguments). The words, the nearest names and the fix, still reach the model as content; on the SDK path they carry the SDK's own "Error executing tool : " prefix.The orphaned-install note (#1973), which
run()returns before starting bash, is left as an ordinary result.Why the goldens moved, or why they did not
No golden moved: no rule changed. The engine and the parser are untouched.
Evidence
Synthetic only.
tests/mcp.pycheck_failuresdrives the server with a fake bash per case, in every launch mode, on the fallback and, withAXIOMCODE_SDK_PYTHON, on the SDK. The four cases that must NOT be errors are the controls.Runs on macOS arm64, Node 22.23.3, Python 3.13.10, mcp 2.3.0 in a venv:
tests/mcp.py:okon v0.2.0 + this patch andokon0.2.3+ this patch, failures checked in all seven launch modes (bin/axiomcode mcp,server.pyfallback,server.pySDK,.codex-plugin/mcp.json,.cursor-plugin/plugin.json,.mcp.json,.mcp.jsonwith the SDK python); each mode prints which half served it. Without the venv the SDK modes are skipped out loud.tests/latency.py --no-engine: the timeout check passes as a tool error; 17 of 18 held. The one failure,verbs:, splits a logged command on whitespace and fails under a checkout path containing a space; it fails the same on pristine v0.2.0 and passes in a path without one. Harness defect, separate issue.tests/repo_arg.pyok;tests/front_door.py21 of 21;tests/corrupt_graph.py0 failures over 26 checks, including "mcp: the tool answer says the graph is corrupt, no traceback" and "mcp: once rebuilt the tool answers again".AXIOMCODE_BASH=/nonexistent/bash, fallback and SDK:isError: true, words unchanged.What to watch:
ax_contract._corruptstarts a detached rebuild and exits 2 "a rebuild is running" before that build may hold the lock;failed()can then mark that one callisErrorwith the correct words. The next call answers normally.tests/corrupt_graph.pytolerates it.check_failuresis POSIX-only (fake/bin/shscripts,fcntl); theAXIOMCODE_ARGVbranch ofrun()is untouched. Worth a run on the Windows gate.isError. Any such client already stopped on the unknown-argument refusal; Claude Code, Cursor and Codex hand the content to the model.tests/mcp.pyis not run by.github/workflows/ci.yml; the runs above are local.Checklist
bug,plugin,mcp(I cannot set them; please add)