You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
P1 architecture/agent-UX defect: the same correctable input mistake can surface through incompatible error channels, and the public MCP boundary often removes the exact constraint an agent needs to repair the call.
Jacobian currently exposes several materially different failure contracts:
JSON-RPC/MCP exceptions raised before or during tool dispatch;
CallToolResult(is_error=true) with a JSON string in text content;
a structured math.find response with kind="error";
a syntactically successful math.run result whose CapabilityResult.execution failed and whose diagnostics explain why;
expected no-match/weak-match discovery results that are correctly non-errors.
The distinctions are meaningful internally, but they are not represented through one finite agent-facing taxonomy. Hosts and generated connectors may preserve, flatten, or discard different parts of each channel.
Worse, the public MCP error mapper turns every ValueError into the same generic response:
{
"error": {
"code": "INVALID_INPUT",
"stage": "<tool>",
"message": "The tool input is not valid for this operation.",
"hint": "Check the tool input schema or call math.find, then retry."
}
}
This discards precise safe details already known by the server, such as:
unknown top-level argument names;
capability_id cannot be combined with search filters;
view is exact-inspection-only;
artifact_type requires input_kind=TYPED_ARTIFACT;
a specific nested JSON pointer is missing or malformed;
a field exceeds a published maximum;
input and candidate must be siblings inside payload.
The result is fail-closed but not reliably recoverable by an agent.
Audit baseline: current main as inspected on 2026-08-10.
Current evidence
The public boundary erases the original ValueError
src/jacobian/adapters/mcp/context.py classifies any ValueError as generic INVALID_INPUT and does not preserve str(tool_error), a field path, expected shape, or allowed arguments.
src/jacobian/adapters/mcp/server.py constructs a precise error for unknown arguments:
unknown tool arguments: ...
but then passes it through the same generic public mapper.
src/jacobian/adapters/mcp/tools.py raises precise ValueErrors for invalid browse/search/inspect combinations, but those details are similarly lost at the public tool boundary.
Discovery and execution use different error shapes
An unknown exact capability in math.find is converted into a typed discovery error containing nearby IDs and recovery paths.
An unknown capability in math.run is represented as a failed CapabilityResult diagnostic and then bounded separately.
A malformed math.find mode combination can become a tool error with only text content.
A no-match query is correctly a successful discovery result rather than an error.
These are all defensible local choices, but there is no common object that tells a client:
which layer rejected the request
whether mathematical execution started
whether anything was persisted
whether retry is safe
which exact field/constraint failed
which next actions are callable in this host
Model-in-the-loop failures are dominated by repairable shape errors
Domain validation behaved safely in these cases. The missing layer is a uniform machine-actionable recovery contract across the transport and capability boundaries.
Root cause
Jacobian has separate error models for separate implementation layers, but no canonical projection between them.
The public boundary currently treats safe validation detail and potentially sensitive implementation detail as the same category, so it redacts both.
At the same time, MCP/host semantics (is_error, JSON-RPC failure, structured content) are allowed to determine the agent-visible shape rather than projecting one stable Jacobian diagnostic into every supported channel.
This creates two anti-patterns:
precise internal validation
-> generic public prose
and:
same recoverable mistake
-> different shape depending on rejection layer
Proposed architecture
1. Define one closed AgentToolDiagnostic
Introduce a bounded typed model shared by MCP errors, math.find errors, and capability diagnostics. A possible shape:
{
"diagnostic_version": "1",
"code": "INVALID_ARGUMENT_COMBINATION",
"layer": "TOOL_ENVELOPE",
"stage": "math.find",
"message": "capability_id cannot be combined with query",
"path": "/query",
"expected": "omit discovery fields when capability_id is present",
"actual_type": "string",
"execution_started": false,
"state_changed": false,
"retryability": "SAFE_AFTER_CORRECTION",
"recovery_actions": []
}
Exact names may differ. The model should distinguish at least:
their error data/message carries the canonical bounded diagnostic;
a valid math.find call with invalid mode arguments returns is_error=true plus the same diagnostic in structured and text channels;
no-match and weak-match discovery remain successful kind="discovery" results;
unknown exact capability uses one explicit capability-resolution diagnostic and bounded nearby matches;
math.run validation/provider/adapter outcomes remain CapabilityResult values, but their diagnostics use or embed the same canonical diagnostic model;
the client can always determine whether execution began and whether retry is safe.
Do not force every mathematical non-conclusion into is_error=true. The objective is semantic consistency, not flattening all outcomes into one boolean.
4. Make recovery actions typed and host-resolvable
A recovery action should identify:
canonical tool/resource
local callable binding when different
arguments or argument patch
preconditions
whether it is ranked or merely available
#1029's invariant applies: never tell an agent to call a tool absent from its current host surface.
For a payload error, prefer a minimal patch or exact-inspection action over returning the complete catalog.
5. Preserve one diagnostic across channels
Structured content, text projection, connector exception wrappers, telemetry, and logs should carry the same diagnostic ID/code and bounded fields.
The text projection can be concise, but it must not change the code, stage, path, expected constraint, execution-started status, or recovery action.
6. Add a validation-error adapter layer
Translate reviewed exception families explicitly:
Pydantic validation errors;
JSON Schema validation errors;
Jacobian typed domain validation errors;
capability-resolution/policy errors;
resource not found;
timeout/cancellation;
authentication/tenant limits.
Unknown exceptions remain generic OPERATION_FAILED with a log correlation digest. Do not guess a repair for unknown backend failures.
7. Version and test error semantics
Error codes and recovery action shapes are public protocol. Version them, document compatibility, and prevent one host adapter from inventing another code/stage vocabulary.
Acceptance criteria
Unknown top-level tool arguments identify the rejected field names without echoing values.
capability_id + query reports the exact incompatible fields and a safe corrected-call shape.
Discovery-mode view identifies /view as exact-inspection-only.
artifact_type/input_kind dependency failures expose the exact invariant.
A nested verifier wrapper error states that /payload/input and /payload/candidate are siblings, with no candidate contents returned.
math.find and math.run use the same diagnostic code/path/recovery vocabulary for equivalent resolution/input failures.
Clients can determine execution_started, state_changed, and retryability for every public failure class.
No-match/weak-match discovery remains a non-error and cannot be confused with transport failure.
Every recovery action is callable from the same in-process, stdio, HTTP, packaged Codex, and installed connector surface.
Structured and text channels agree on all decision-relevant fields.
Oversized validation sets are deterministically bounded with an explicit omitted-error count.
Adversarial tests prove payload values, secrets, paths, stack traces, and backend stderr are never returned.
Suggested regression matrix
Test the same failures through every supported surface:
Priority
P1 architecture/agent-UX defect: the same correctable input mistake can surface through incompatible error channels, and the public MCP boundary often removes the exact constraint an agent needs to repair the call.
Area
MCP adapter, SDK validation,
math.find,math.run, capability dispatch, diagnostics, host connectors, recovery actions.Summary
Jacobian currently exposes several materially different failure contracts:
CallToolResult(is_error=true)with a JSON string in text content;math.findresponse withkind="error";math.runresult whoseCapabilityResult.executionfailed and whosediagnosticsexplain why;The distinctions are meaningful internally, but they are not represented through one finite agent-facing taxonomy. Hosts and generated connectors may preserve, flatten, or discard different parts of each channel.
Worse, the public MCP error mapper turns every
ValueErrorinto the same generic response:{ "error": { "code": "INVALID_INPUT", "stage": "<tool>", "message": "The tool input is not valid for this operation.", "hint": "Check the tool input schema or call math.find, then retry." } }This discards precise safe details already known by the server, such as:
capability_idcannot be combined with search filters;viewis exact-inspection-only;artifact_typerequiresinput_kind=TYPED_ARTIFACT;inputandcandidatemust be siblings insidepayload.The result is fail-closed but not reliably recoverable by an agent.
Audit baseline: current
mainas inspected on 2026-08-10.Current evidence
The public boundary erases the original
ValueErrorsrc/jacobian/adapters/mcp/context.pyclassifies anyValueErroras genericINVALID_INPUTand does not preservestr(tool_error), a field path, expected shape, or allowed arguments.src/jacobian/adapters/mcp/server.pyconstructs a precise error for unknown arguments:but then passes it through the same generic public mapper.
src/jacobian/adapters/mcp/tools.pyraises preciseValueErrors for invalid browse/search/inspect combinations, but those details are similarly lost at the public tool boundary.Discovery and execution use different error shapes
An unknown exact capability in
math.findis converted into a typed discovery error containing nearby IDs and recovery paths.An unknown capability in
math.runis represented as a failedCapabilityResultdiagnostic and then bounded separately.A malformed
math.findmode combination can become a tool error with only text content.A no-match query is correctly a successful discovery result rather than an error.
These are all defensible local choices, but there is no common object that tells a client:
Model-in-the-loop failures are dominated by repairable shape errors
input/candidatesibling-shape failure for Smith normal form.Domain validation behaved safely in these cases. The missing layer is a uniform machine-actionable recovery contract across the transport and capability boundaries.
Root cause
Jacobian has separate error models for separate implementation layers, but no canonical projection between them.
The public boundary currently treats safe validation detail and potentially sensitive implementation detail as the same category, so it redacts both.
At the same time, MCP/host semantics (
is_error, JSON-RPC failure, structured content) are allowed to determine the agent-visible shape rather than projecting one stable Jacobian diagnostic into every supported channel.This creates two anti-patterns:
and:
Proposed architecture
1. Define one closed
AgentToolDiagnosticIntroduce a bounded typed model shared by MCP errors,
math.finderrors, and capability diagnostics. A possible shape:{ "diagnostic_version": "1", "code": "INVALID_ARGUMENT_COMBINATION", "layer": "TOOL_ENVELOPE", "stage": "math.find", "message": "capability_id cannot be combined with query", "path": "/query", "expected": "omit discovery fields when capability_id is present", "actual_type": "string", "execution_started": false, "state_changed": false, "retryability": "SAFE_AFTER_CORRECTION", "recovery_actions": [] }Exact names may differ. The model should distinguish at least:
2. Preserve safe validation facts, never rejected values
Allowlist bounded details that are safe and useful:
Do not return:
Use typed validation exceptions for public-safe detail rather than exposing generic
ValueErrormessages indiscriminately.3. Define the channel mapping explicitly
Recommended boundary:
math.findcall with invalid mode arguments returnsis_error=trueplus the same diagnostic in structured and text channels;kind="discovery"results;math.runvalidation/provider/adapter outcomes remainCapabilityResultvalues, but their diagnostics use or embed the same canonical diagnostic model;Do not force every mathematical non-conclusion into
is_error=true. The objective is semantic consistency, not flattening all outcomes into one boolean.4. Make recovery actions typed and host-resolvable
A recovery action should identify:
#1029's invariant applies: never tell an agent to call a tool absent from its current host surface.
For a payload error, prefer a minimal patch or exact-inspection action over returning the complete catalog.
5. Preserve one diagnostic across channels
Structured content, text projection, connector exception wrappers, telemetry, and logs should carry the same diagnostic ID/code and bounded fields.
The text projection can be concise, but it must not change the code, stage, path, expected constraint, execution-started status, or recovery action.
6. Add a validation-error adapter layer
Translate reviewed exception families explicitly:
Unknown exceptions remain generic
OPERATION_FAILEDwith a log correlation digest. Do not guess a repair for unknown backend failures.7. Version and test error semantics
Error codes and recovery action shapes are public protocol. Version them, document compatibility, and prevent one host adapter from inventing another code/stage vocabulary.
Acceptance criteria
capability_id + queryreports the exact incompatible fields and a safe corrected-call shape.viewidentifies/viewas exact-inspection-only.artifact_type/input_kinddependency failures expose the exact invariant./payload/inputand/payload/candidateare siblings, with no candidate contents returned.math.findandmath.runuse the same diagnostic code/path/recovery vocabulary for equivalent resolution/input failures.execution_started,state_changed, and retryability for every public failure class.Suggested regression matrix
Test the same failures through every supported surface:
math.findfields;Canonicalize and compare:
is_error/transport mapping;Related work
math.findmodes; this issue owns actionable runtime diagnostics when a client still sends an invalid call.Non-goals
is_errorvalue without preserving semantics.