Files
projax/mcp
mAi d7438ba89e refactor(mcp): widen ToolHandler signature to return *ToolError with .data support
Phase 5d slice A. ToolHandler was `func(ctx, params) (any, error)` and
errors surfaced through the MCP `result.isError=true` content envelope with
no place to put structured payloads. Widen to `(any, *ToolError)` so
handlers return a typed `{Code, Msg, Data}` that the server marshals into
the JSON-RPC `error` envelope (`{code, message, data}`) — `data` is omitted
when nil so today's untyped errors stay clean.

Handler.go:
- ToolError gains `Code int`; Msg+Data unchanged. Error() preserved.
- Drop `AsToolError` — `errors.As` indirection is no longer needed now that
  handlers return *ToolError directly.
- Add `InternalError(err)` (-32603, wraps a plain error) and
  `InvalidParamsError(msg)` (-32602, declared for slice B's validation
  promotion — no callers in slice A).
- `handleToolsCall` switches from the `result.isError` envelope to the
  JSON-RPC `error` envelope via new `writeToolErr` helper. Transport-level
  errors (`writeErr`) are unchanged.

Tools.go:
- `itemWriteError` now returns `*ToolError` with the legacy
  `validation <kind>: <detail> [{json-blob}]` Msg text and no Data. Slice B
  replaces this with `ValidationToolError` (typed .data + -32602).
- All ten tool handlers migrated to the new signature. Non-validation
  paths default to `Code: codeInternalError (-32603)` via `InternalError(err)`
  for semantic preservation; "field is required" guards keep the same
  message string under -32603.
- Helper functions (`resolveItem`, `resolveParentPaths`,
  `resolveTimelineWindow`, `resolveTimelineItems`, `applyHasLinkFilters`,
  `parseInput`) keep returning plain `error`; their tool-handler callers
  wrap with `InternalError`.

Test source edits (per the 5c rule):
- `mcp_test.go` TestToolsCallSuccessAndError: error path now asserts on
  the JSON-RPC `.error.code == -32603` and `.error.message == "kaboom"`
  envelope instead of `result.isError=true` + content text. The success
  path is unchanged (`isError:false` and content[].text stay). Also
  refreshed two handler-literal signatures in the same test file from
  `(any, error)` → `(any, *ToolError)` so the test compiles against the
  widened signature.

All other MCP tests stay behaviour-preserving — they exercise success
paths through the unchanged result envelope, or hit error paths via
`Handler(...) (any, *ToolError)` directly (timeline_test.go) and still see
a non-nil error.
2026-05-22 11:46:19 +02:00
..