Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

RFD-0027 — Agent-facing tooling: universal CLI + JSON contract, MCP as transport, registration as data

Committed Opened 2026-05-03 · Committed 2026-05-03

Question

How should Argon integrate with AI coding agents — Claude Code, Cursor (IDE + SDK), Codex, opencode, Continue, Aider, and whatever ships next year — such that the integration is general, stable, and additive rather than per-agent code in oxup?

Context

Argon ships a language server (ox-lsp) and a project-manager CLI (ox). The VS Code / Cursor extension wraps the LSP for human-in-IDE use. Today, agents reach Argon through whatever path the IDE happens to surface — when the agent runs in IDE mode with the Argon extension active, diagnostics flow through the editor’s extension-to-context glue. When the agent runs outside an IDE (Cursor SDK harness, cursor-agent CLI, opencode plugin loop, a custom automation harness), there is no Argon integration at all.

That gap is the load-bearing problem. Three things have to be true at once for an integration to be durable across the agent ecosystem:

  1. It works without an IDE in the loop.
  2. It works for any current and future MCP-supporting agent without per-agent code in oxup.
  3. The agent-facing data contract is stable enough that downstream tools can pin to it.

Configuration-blind harnesses (an SDK that doesn’t read external config files, a CI loop that boots an agent with no shell config, etc.) compound the problem: anything that requires a config file to land in the right place fails for them. The floor has to be reachable from a Bash tool with no setup beyond the toolchain being on PATH.

Options

Option A — IDE extension only. Lean on the existing VS Code / Cursor extension; rely on each editor’s extension-to-context integration. Rejected: fails immediately for headless harnesses, SDK-based agents, and CI-mode integrations.

Option B — MCP-only. Ship ox mcp and call it done. Rejected: configuration-blind agents and Bash-only loops still get nothing. The floor needs to be lower than MCP.

Option C — Per-agent native integrations. Implement Cursor SDK plugin, Claude Code plugin, opencode plugin, etc., each natively. Rejected: scales badly (every new agent is a code change in oxup), creates surface drift, and reimplements the same data flow N times.

Option D — Universal CLI + JSON schemas at the floor; MCP as an opt-in transport; both backed by a single in-process surface library; per-agent registration as data not code. The position taken in this RFD.

Decision

Argon’s agent-facing tooling is layered, with all tiers backed by a single in-process surface library so the operation a transport carries and the operation a transport implements are the same code. No tier reimplements another’s payloads; transports differ only in how they reach the surface.

Implementation locus — oxc-agent-surface library. Each agent-facing operation is a typed Rust function returning a Serialize + schemars::JsonSchema payload. The library lives in argon/oxc-agent-surface/ (or as an extension of oxc-runtime::OxcContext if module-internal scope wins out during implementation). CLI, MCP, and any future LSP-extended request are thin transports over these function calls — argument parsing, transport-specific envelope, then a single function invocation that returns the typed payload. JSON Schemas under share/argon/schemas/ are generated from the surface’s return types via schemars, the same pattern oxc-codegen already uses to keep oxc-protocol types in sync with their TypeScript bindings and the diagnostic registry. The Rust types are the source of truth; the schemas are their wire-format projection.

Tier 0 — universal CLI + JSON schema contract. Every agent-relevant ox subcommand has a --json flag. The flag selects a serializer; the operation is a call into oxc-agent-surface. Schemas (generated): diagnostics.schema.json, hover.schema.json, query-result.schema.json, package-tree.schema.json, provenance.schema.json, plus version.json carrying the schema-set semver. Any harness that can run ox check --json <path> and parse JSON is integrated. This is the floor.

Tier 1 — MCP transport (ox mcp). Each MCP tool delegates to the same oxc-agent-surface call as the equivalent CLI path; tool payloads are bytewise identical to CLI JSON because they pass through the same serializer. Tool surface: argon_check, argon_check_file, argon_explain, argon_hover, argon_query, argon_packages, plus argon_why once provenance walks ship. MCP-aware agents wire it via a single config entry pointing at ox mcp.

ox mcp lifecycle — two scheduled phases within one release cycle. v0 is a thin spawning transport: each MCP tool call invokes the corresponding ox CLI subcommand as a child process and streams its JSON output back. v1 holds a persistent OxcContext across tool calls, reusing Salsa caches for warm incremental analysis. v0 ships the contract and surface library before the lifecycle complexity; v1 is the production target. The promotion is scheduled, not gated on later telemetry — heavy agent traffic is the assumed workload, not a hypothetical.

Tier 2 — IDE extension. Unchanged. Continues to serve in-editor humans and IDE-mode agents that consume diagnostics via the editor’s extension-to-context glue.

Tier 3 — per-agent skill / rules files. Documentation pointers at Tier 0 / Tier 1 capabilities, not implementations. Content is single-sourced; per-agent variants differ only in file location and frontmatter wrapping.

Registration is data-driven. oxup agents register reads share/agents/<agent>/registration.toml describing the config-file path, write strategy, and detection predicate for each supported agent. Adding a new agent is a share/ data drop, not an oxup code change. Detection is per-agent presence (config directory exists or binary on PATH); missing agents are skipped silently.

Schema versioning. share/argon/schemas/version.json carries the schema-set semver; each schema also carries its own version. Pre-1.0 schemas are explicitly experimental and may break between minor toolchain releases; 1.0+ schemas commit to SemVer (breaking → major, additive → minor). Consumers pin. The agent contract gets the same stability discipline ConceptDef gets for the kernel — but each schema earns 1.0 through its own ratification RFD, not by default.

Rationale

Why CLI + JSON is the floor. Every agent that integrates with code has exactly two universal capabilities: read files, run shell commands. No agent universally speaks LSP, MCP, IDE-extension, or any other protocol. Picking anything richer than CLI as the floor leaves agents out. Configuration-blind harnesses can integrate at Tier 0 with zero installation magic, by literally calling ox check --json and parsing the output. Their problem reduces to “parse a documented JSON schema” — solvable in any language, by any harness, forever.

Why MCP is the right Tier 1, not the floor. MCP is converging fast as the cross-agent standard for tool exposure. Claude Code, Cursor (IDE and SDK), opencode, Codex, Continue, Aider — all support it or are adding support. Building Tier 1 as MCP means one implementation reaches every MCP-aware agent. Future MCP-adopting agents integrate for free. But MCP is not universal today, and never will be for harnesses that intentionally avoid configuration. Hence Tier 0 below it.

Why a single in-process surface is the implementation locus, not just shared schemas. Sharing JSON Schemas across transports prevents wire-format drift but doesn’t prevent implementation drift: three call sites for the same operation (one per transport) can subtly diverge in argument validation, error mapping, edge-case handling, and which fields get populated when. Pulling the operation into one typed function call that every transport invokes makes drift structurally impossible — the function and its return type are the operation. Schemas generate from the return types via schemars rather than being hand-authored alongside; this is the same drift-prevention pattern oxc-codegen already uses for oxc-protocol types and the LSP/TypeScript bindings, extended to the agent contract.

Why registration is data, not code. Agent ecosystems move faster than oxup releases. Cursor adds an SDK; Codex changes a config path; opencode forks. If every agent change requires a code update, oxup becomes a bottleneck on agent-ecosystem velocity. Data-driven registration (TOML descriptors plus shipped skill files) lets us add an agent in the next release tarball without touching Rust.

Why per-agent skills are pointers, not implementations. The skill/rules layer’s job is to teach the agent which capabilities exist and when to use them. The capabilities themselves live in Tier 0 / Tier 1. If skill content forks per agent, every fix requires N edits. Single-source content with per-agent wrapping (frontmatter, file location) means one update propagates everywhere.

Why ox mcp and ox-lsp stay distinct binaries. LSP and MCP have incompatible lifecycle semantics: LSP is a long-lived process talking JSON-RPC over stdio with bidirectional notifications; MCP is tool/resource semantics with retry-friendly one-shots. Conflating them inherits the worst of both. Each protocol gets its own binary that delegates into the same oxc-agent-surface library — and through it, into the same underlying oxc Salsa-tracked queries. Distinct binaries, single implementation locus.

Why ox mcp v0 ships before v1, even though v1 is the destination. v1 (persistent OxcContext + warm Salsa across tool calls) is the production-traffic shape: agents iterating on the same files across dozens of tool calls per session benefit substantially from cache reuse. But v1 inherits a class of bugs v0 doesn’t — process lifecycle, crash recovery, IPC robustness, cache-invalidation correctness against file changes between calls. Shipping v1 before the contract and surface library are stable means debugging persistence at the same time as debugging operations. v0 (thin spawning transport) lands the contract; v1 follows in the same release cycle as the next phase. Scheduled, not conditional — heavy warm-cache traffic is the assumed workload, not a maybe-later case.

Why the agent contract gets first-class stability discipline. Agents will be primary authors of Argon code; the agent surface is therefore upstream of language productivity itself. Diagnostic JSON quality, hover payload richness, query primitives — these shape what Argon is in practice for the bulk of users (other software), not just how its tooling feels. Schemas accordingly graduate at deliberate pace: each schema gets its own follow-up RFD as it stabilizes, not a single bulk-author-and-ship pass. The diagnostics schema is the highest-traffic surface and lands first; hover, query-result, package-tree, and provenance follow as their underlying surfaces do. Pre-1.0 schemas are explicitly experimental in version.json; 1.0+ schemas commit to SemVer.

Why “Ayush’s harness can’t read config” is not our problem to solve. A configuration-blind SDK harness sits on top of the universal floor (Tier 0) by definition: it can run ox check --json and parse output, and that’s all the contract requires. Anything more — auto-discovery of MCP servers, automatic skill installation — assumes a harness that reads config files. We don’t owe that to harnesses that intentionally don’t. The floor is what we owe; the rest is convenience for harnesses that opt in.

Consequences

  • New crate: oxc-agent-surface (or extension on oxc-runtime::OxcContext) defining each agent-facing operation as a typed Rust function. CLI / MCP / future LSP-extended requests all become thin transports over this surface. The crate is the implementation locus; transports are wire-format adapters.
  • Codegen extension. oxc-codegen grows an emitter that writes share/argon/schemas/*.schema.json from the surface’s return types via schemars. Drift between Rust types and schemas becomes a CI failure, same gate that already protects oxc-protocol.
  • New subcommand: ox mcp ships in two scheduled phases within one release cycle. v0 is a thin spawning transport; v1 holds a persistent OxcContext. The phasing is to land the contract before the lifecycle complexity, not to defer v1 indefinitely.
  • oxup changes. oxup agents register reads share/agents/<agent>/registration.toml and writes per-agent config. The existing Claude Code marketplace pointer migrates to this mechanism. oxup install invokes registration after toolchain laydown; users opt out via oxup agents register --none or per-agent flags.
  • share/ layout grows. New directories under the toolchain tarball:
    • share/argon/schemas/ — generated JSON Schemas + version meta
    • share/agents/_common/argon-skill.md — single-sourced skill content
    • share/agents/<agent>/registration.toml + share/agents/<agent>/<skill-file> per supported agent (cursor, claude-code, codex, opencode at v0)
  • Documentation. Argon book gains a “For Agents — Tooling” page covering JSON schema shapes, MCP tool names, and integration recipes per tier. Hosted in the existing “For Agents” part.
  • Contract stability. Schemas under share/argon/schemas/ follow SemVer once they hit 1.0. Pre-1.0 schemas are explicitly experimental in version.json. Each schema graduates via its own follow-up RFD as the underlying surface stabilizes; this RFD ratifies the architecture, not the per-schema shapes.
  • Cluster shape. Sized like a coherent multi-PR body of work — surface-library scaffolding, --json audit, ox mcp v0 + v1, oxup agents register data-drive, skill content authoring, book docs. Patch-line bumps absorb it; the substrate is purely additive (no breaking changes to existing surface).

Implementation issues that fall out

To be opened post-commit, each citing this RFD:

  1. Land oxc-agent-surface crate scaffolding. Enumerate operations as typed functions; derive return-type schemas via schemars; wire oxc-codegen to emit share/argon/schemas/*.schema.json from the surface types with a CI drift gate.
  2. Audit ox subcommand --json coverage. Route every --json path through oxc-agent-surface rather than ad-hoc serialization; remove direct serde_json calls in CLI handlers where the surface library covers them.
  3. Implement ox mcp v0 — thin spawning transport. Each MCP tool call invokes the corresponding ox CLI subcommand as a child process and streams JSON output back. Lands the MCP contract.
  4. Implement ox mcp v1 — persistent OxcContext. Hold one OxcContext across tool calls; warm Salsa caches across requests; correct cache invalidation against file changes between calls. This is the production target.
  5. Make oxup agents register data-driven. Read share/agents/<agent>/registration.toml; migrate the existing Claude marketplace pointer onto this mechanism; detect agents per descriptor; idempotent.
  6. Author single-source skill content. share/agents/_common/argon-skill.md carries the substantive content; per-agent variants in share/agents/<agent>/ carry frontmatter and any agent-specific guidance.
  7. Land “For Agents — Tooling” page in the Argon book. Tier-by-tier integration recipes; example diagnostic JSON, MCP tool invocations, registration commands.
  8. Open follow-up RFDs per schema as it stabilizes. Diagnostics first (highest traffic), then hover, query-result, package-tree, provenance. Each ratifies its schema for SemVer 1.0.