Skip to content

fix(mcp): Align tool ergonomics with real agent call patterns - #25

Merged
Xuepoo merged 1 commit into
mainfrom
carryctx/ctx-0047
Aug 24, 2026
Merged

fix(mcp): Align tool ergonomics with real agent call patterns#25
Xuepoo merged 1 commit into
mainfrom
carryctx/ctx-0047

Conversation

@Xuepoo

@Xuepoo Xuepoo commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Summary

Implements all six commander decisions from task CTX-0047, closing the ranked friction inventory established by forensics (CTX-0048, note PX-0079). Only error paths, validation, and schema descriptions change; JSON-RPC framing and success-path payloads remain byte-compatible. The CLI keeps unrestricted local paths by design (local-trusted vs MCP remote-distrusted).

Forensics evidence

  • 83/83 historical MCP calls used absolute paths — the old pinned_path rejected every one of them outright (absolute paths are not allowed), making it the single biggest recurring breaker.
  • Benchmark logs showed repeated failures from: required lines on whole-file reads, numeric lines: 100 arguments rejected by validation, unhelpful unknown tool: X / bare missing-argument errors forcing schema round-trips, and dead-end symbol not found: X typos.

Decisions implemented

  1. Absolute paths under root are legal (pinned_path): absolute input is lexically normalized (. dropped, .. applied without touching the filesystem); existing targets are additionally symlink-resolved against the canonical root exactly like before. Accepted paths are returned normalized-but-not-canonicalized so handler not-found diagnostics are preserved. Escapes (lexical or symlinked) are still rejected with the same path escapes workspace root text. Relative behavior unchanged.
  2. lines optional on read: omitted or empty string now yields the whole file (open-ended range semantics). inputSchema.required drops to ["file"]; the description documents "N", "N-M", "N-", comma lists, and omitted/empty = whole file. Note: -M (from start of file) is not documented because the shared range parser does not support it and the CLI must stay untouched.
  3. Benign numeric coercion in validate_arguments: a JSON number for a string-typed argument coerces to its decimal string form (lines: 100"100", keeping single-line meaning per P03). Bool/object/array/null are still rejected, now naming key, expected type, and got type (argument \lines` must be a string, got boolean`). Explicit null stays absent-as-before.
  4. Unknown tool errors enumerate the registry: unknown tool `X`; available: ctxctl_deps, ctxctl_exec, ctxctl_outline, ctxctl_read, ctxctl_symbol — derived from a single alphabetical TOOL_TABLE that also feeds validation, hints, and suggestions, so it cannot drift.
  5. Missing-arg usage hints: missing/empty required args append an example built from that tool's argument table, e.g. expected: {"file": "<workspace-relative or absolute path under workspace>", "no_doc": <boolean>, "no_lines": <boolean>}.
  6. Unknown-arg errors list valid keys: unknown argument `encoding`; valid arguments: file, lines.

Plus: ctxctl_symbol not-found errors append up to 3 case-insensitive prefix-then-substring suggestions from the file's actual symbols (; similar: NodeHandle, NodeState), silently skipped when there are none.

Test coverage

Unit (mcp.rs tests module) + integration (tests/mcp_test.rs), mirroring the adapted P00-P10 matrix:

  • P00/P01: absolute path + omitted/empty lines → success, whole file
  • P02: lines: 100-style numeric argument reads that line (was a rejection test; expectations updated to new design)
  • Absolute in-root acceptance across outline/deps/symbol; deterministic rejection of nonexistent absolute escapes and lexical .. escapes with no content leak
  • Unknown-tool enumeration (exact string), missing-arg usage hints (exact strings), unknown-arg valid-key listing, scalar mistype diagnostics
  • Symbol suggestions: prefix ranking, substring fallback, cap at 3, silent when none
  • Schema contract: read required == ["file"], documented formats

All existing tests kept green; only assertions pinning the removed "absolute paths are not allowed" message were minimally rewritten to the new message content.

Gates: cargo fmt --check ✓ · cargo clippy --all-features -- -D warnings ✓ · cargo test --workspace ✓ (278 tests)

Forensics (CTX-0048, note PX-0079) ranked recurring agent call failures
on the MCP surface; this implements all six commander decisions:

- pinned_path: absolute paths are now legal when they normalize+resolve
  inside the pinned workspace root (existing targets symlink-resolved as
  before); only escapes are rejected. Relative behavior unchanged.
  (83/83 historical calls used absolute paths.)
- ctxctl_read: lines is optional — omitted or empty yields the whole
  file, mirroring CLI open-ended-range semantics; inputSchema updated.
- validate_arguments: benign numeric coercion — a JSON number for a
  string-typed argument becomes its decimal string form (lines:100 ->
  "100", single-line meaning preserved). bool/object/array/null still
  rejected naming key, expected type, and got type.
- unknown tool errors enumerate the registry: 'unknown tool `X`;
  available: ...' from a single alphabetical TOOL_TABLE source.
- missing-argument errors append a usage example built from that tool's
  argument table so it can never drift from the schema.
- unknown-argument errors list the valid keys.

Also: ctxctl_symbol not-found errors gain up to 3 case-insensitive
prefix-then-substring suggestions ('; similar: A, B') from the file's
actual symbols; silent when none.

JSON-RPC framing and success-path payloads stay byte-compatible; only
error paths, validation, and schema descriptions change. The CLI keeps
unrestricted local paths by design.
@Xuepoo
Xuepoo merged commit 9e4c26b into main Aug 24, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant