V3 Issue #233 — Structured Tool Arguments and MCP Implementation Plan
Issue-specific plan for #233, stacked on the #261 security-boundary PR.
Goal: Make JSON objects the native tool-call contract from provider/MCP entry through authorization, registry validation, executor dispatch, receipts, and observers, while keeping text APIs explicitly compatibility-only.
Architecture: Extend the existing ToolRegistry and AgentRuntime.execute_call() boundary. Built-in executors live in openkyrozen/tools; MCP and other surfaces adapt only at their public compatibility edges. Security policy remains in runtime/security. No parallel catalog or second dispatcher.
Tech stack: Python 3.12–3.13, standard library and current dependencies, unittest.
Spec: V3 issue #233 (R014–R018), with related issue #226.
Constraints and compatibility
- Add no dependency, paid provider call, provider continuation, or execution retry.
- JSON serialization is allowed for validation, fingerprints, and persistence only; never use serialized or pipe-joined strings to transport native executor arguments.
- All 48 built-in
ToolSpecexecutors accept object arguments and returnToolResult; retain public text APIs andAVAILABLE_TOOLSthrough explicit adapters. - Legacy custom string executors remain compatibility-only. Native custom registration requires a declared object schema and typed result contract.
- Keep approvals, capabilities, workspace and child scope enforcement from #261 at the same shared runtime boundary.
- Preserve canonical names, aliases, argument aliases, snapshots, replacement/removal, MCP output content, and registry-derived documentation.
Caller and contract map
openkyrozen/tools/manifest.py: current schema validation,ToolSpeclegacy converters, registry invoke/result,ToolMappingcompatibility views, descriptors and snapshots.openkyrozen/tools/: built-in typed executor definitions and adapter bindings (48 registered specs, including memory, discovery, graph, orchestration and integration tools).openkyrozen/agent/runtime.pyandopenkyrozen/agent/executor.py:execute_call, canonical policy validation, mutation/effect fingerprints, receipts and observer arguments.openkyrozen/interfaces/mcp/server.pyandopenkyrozen/interfaces/web/service.py: MCP currently translates arguments through_mcp_string_arguments; remove this path from native MCP calls.- CLI/TUI/web/task/subagent/learning callers and tests: preserve old text behavior via compatibility adapters while routing structured callers through the typed boundary.
- Generated
docs/tool-inventory.mdand its drift check: regenerate from the final catalog.
Task 1 — Prove object execution and shared invocation
- [ ] Add failing tests that patch legacy converters to fail while native
execute_calland MCP object arguments succeed. - [ ] Add regressions for malformed search argument validation, pipe characters in paths/content/selectors, Unicode and independent file fields.
- [ ] Verify registry executor invocation currently receives the conversion output rather than the original object; run focused tests and record expected failures.
Task 2 — Enforce canonical JSON-object schemas
- [ ] Exercise required and unknown fields, nested objects/arrays, combinators, bounds, enums, nonfinite numbers, wrong primitive types, and coercion attempts.
- [ ] Extend the existing registry validator only where cases fail; reject invalid arguments before executor invocation with typed
invalid_argumentsoutcomes. - [ ] Keep schema evaluation aligned with the catalog's already supported vocabulary and reject unsupported schema keywords rather than silently ignoring them.
Task 3 — Migrate built-ins with their compatibility edges
- [ ] Inventory every built-in ToolSpec and its executor, adapter, MCP declaration, security/effect caller, and compatibility converter.
- [ ] Convert all 48 executor contracts to object input. Extract each parameter directly; preserve limits, defaults, paging, stale-file checks, and side-effect boundaries.
- [ ] Replace generic string schemas with meaningful object schemas: files, shell, Git/GitHub, browser, graph, memory, calculation, discovery, and orchestration.
- [ ] Keep text/public
AVAILABLE_TOOLScallers working through explicit edge adapters; resolve aliases there and reject conflicting alias values. - [ ] Preserve legacy custom executors as compatibility-only; require object schema and typed executor for native custom registration.
Task 4 — Route runtime and MCP through the typed boundary
- [ ] Validate the complete object before authorization and execution; pass the same canonical snapshot through approval fingerprint, scope checks, executor, receipt, and observers.
- [ ] Update mutation fingerprints, effect verification, plugin/learning observers, subagent execution, registry snapshots and restoration to consume structured values.
- [ ] Remove
_mcp_string_arguments()from native MCP calls; invoke viaexecute_call()and preserve MCP text response and typedisErrorsemantics. - [ ] Confirm execution result remains distinct from effect and task acceptance evidence; do not introduce retry or replay.
- [ ] Update link for #226 and regenerate registry-derived inventory.
Task 5 — Verify and deliver #233
- [ ] Test every registered built-in receives a dictionary and returns
ToolResult; compare MCP and runtime outcomes for equivalent calls. - [ ] Run Python 3.12 and 3.13 focused tests, complete test suite,
make check,make lint,make docs-check, Go TUI tests, and offline agent/subagent/TUI acceptance. Install/use existing browser test support before acceptance where available. - [ ] Review the full stacked diff independently; reproduce actionable feedback and fix material defects with regression tests.
- [ ] GPG-sign commits, verify signatures locally and on GitHub, push
Evan/v3-233-typed-invocation, open PR #233 againstEvan/v3-261-security-boundary, attach it, then wait for final-commit CI and reviews. Leave unmerged.
Acceptance and known limits
PASS means native provider/MCP object arguments reach their typed executor unchanged and schema violations are rejected before side effects. Compatibility text adapters may translate legacy calls. The malformed search argument is independently covered; provider continuation/model repair and unimplemented platform sandboxing are outside this issue. Live providers are not called.