Uh oh!
There was an error while loading. Please reload this page.
feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (#700) - #702
Merged
Conversation
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Team Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…rness-neutral core (#700 A3, 1/3) src/amicode_tools_core.ts now holds the ONE implementation of the amicode_* tool surface: the tool table (17 tools, descriptions + args + execute) over filesystem + explicit parameters. The opencode plugin becomes a thin adapter that projects the table and supplies the harness plumbing (engine SDK client, per-call session ctx) — its startup ritual (stderr load line, one-shot legacy migration) stays plugin-owned, and its behavior is byte-identical (pinned by the existing entity-layer tests plus the new adapter-projection test). amicode_session — the one harness-coupled verb — takes the engine client and session identity as explicit AmicodeToolContext params; without them (the MCP transport's shape) it refuses honestly with the same 'Cannot spawn' shape. Type-level only, no behavior change: the moved bodies (and the sibling modules they import) came under tsc for the first time — fixed the latent narrowing/ widening artifacts that surfaced (EventInput.diff accepts the flat recommend/ veloce diffs it already writes; appendStanza takes object; dead locals removed). @modelcontextprotocol/sdk rides this commit (the MCP transport is next).
…e core tool table (#700 A3, 2/3) src/mcp_amico_server.ts serves the amicode_* tools over MCP stdio via the official @modelcontextprotocol/sdk (low-level Server + StdioServerTransport — the raw JSON-Schema args project 1:1, no zod re-shape). esbuild gains the 4th target: a self-contained ESM bundle at bin/dist/mcp-amico.mjs (the core + SDK ride in the bundle), spawned by plain 'node'. tools/list names + descriptions + schemas are pinned ≡ the plugin's registrations in test/mcp_amico_parity.test.ts — both projections read the one core table, so the two transports cannot drift. The server startup ritual mirrors the plugin's (stderr-only diagnostics — stdout is the protocol channel — plus the same one-shot legacy migration with the same env-skip guard). The main-script guard keeps a module import side-effect-free, which is what lets the parity test import the server in-process; 'node mcp-amico.mjs' serves.
…lly to the plugin path (#700 A3, 3/3a) test/mcp_amico_roundtrip.test.ts spawns bin/dist/mcp-amico.mjs (built on demand with the same esbuild config when absent) as a real subprocess, drives initialize → tools/list → tools/call amicode_pick_system through the official SDK client against a temp AMICODE_PROBLEMS_DIR, and pins the recording against the opencode plugin path: the tool's LLM-facing return verbatim, the system.toml/system.json sidecars, the events.jsonl spine, and the score-guard pass-through — byte-equal modulo the ISO timestamps.
…gin retires from the config (#700 A3, 3/3b) buildOpencodeConfigContent injects mcp.amicode = { type: 'local', command: ['node', <bin/dist/mcp-amico.mjs>], enabled: true } — the fork v1.18's McpLocalConfig shape (verified against harmoniqs/opencode@3779ab82). The server's environment carries AMICODE_PROBLEMS_DIR pinned to problemsRoot() (the same derivation the external_directory grant uses — the #402 ladder — explicit, never ambient), plus AMICODE_ENTITIES_DIR when the host set one, so the MCP transport resolves problem slugs against exactly the root the plugin did. The plugin list carries only extraPluginPaths now (amicode_context.ts, the prompt-time context splice, stays a plugin); the pluginPath parameter is retired-but-positional (underscore-documented, ignored). Config tests rewritten to the post-retirement shape: plugin = [] by default, the retired param registers nothing, the mcp.amicode declaration + its env threading are pinned, and the real-binary integration test asserts the declaration survives resolution (skipIf no vendored binary, as before).
…on formatting intact (#700) pnpm add had rewritten the file's unicode escapes wholesale; the dependency is the only intended change, so it is re-applied by hand and verified against the frozen lockfile.
…pacing renders the product-identical view (#700 A3 director decision) The core table stays keyed by each tool's canonical PRODUCT name (amicode_pick_system, …) — stored exactly once — and now owns the naming contract: mcpBareName/mcpProductName derive the two transport views. The MCP server serves the BARE wire name (pick_system, …) — the MCP-native pattern where the client namespaces by server — and resolves tools/call back through the product name. opencode's fork (McpCatalog.toolName = server + '_' + name) with the server registered as 'amicode' renders amicode_pick_system: the model-visible surface is unchanged; non-opencode clients see clean bare names. Parity test now pins the product-identical view (plugin name ≡ 'amicode' + '_' + bare wire name ≡ canonical, bijectively, 17/17) plus descriptions/schemas verbatim; the round-trip drives the wire under the bare names; the core test pins the naming contract (round-trip + refusal of non-prefixed keys).
…e follow-through on #701) origin/main advanced mid-slice with #701 (SEAM 2 — regime priors): the plugin's amicode_recommend gained the composed query (ledger priors ∪ regime priors via serveRecommendations, platform family scoped from the recorded system), the new audit action (auditRegimePriorApplications — the off-profile sensor with the census-staleness check), the current_census arg, and the four-action description. That body is now in the core VERBATIM (byte-identical to main's plugin text; the two sibling specifiers re-based to ../opencode-plugin/), so both transports serve it: the plugin adapter projects it (behavioral reference — main's own scripts/plugin_exercise.ts passes against the shim), and the MCP server serves it under the bare wire name (recommend). The conflict discipline: rebase resolved opencode-plugin/amicode_tools.ts as the retirement adapter (ours) — main's plugin content lives on through the core; regime_priors.ts/.json, their tests, plugin_exercise.ts, and the packaging list arrived from main untouched (diff vs main on the plugin dir = exactly the four type-fix files from the extraction).
aarontrowbridgeforce-pushed
the
700-feat-extension-a3-the-mcp-tool-floor
branch
from
September 1, 2026 06:16
acc0a21 to
faccb77CompareUh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes#700.
The first detachment lands. The
amicode_*interview-rail surface stops being an opencode plugin and becomes product-owned: ONE implementation (the core), two transports (the opencode plugin adapter + an MCP stdio server). Any harness that speaks MCP gets the whole rail.What (7 commits)
src/amicode_tools_core.ts— the 17-tool table, harness-neutral (filesystem + explicit parameters; the one harness-coupled tool,amicode_session, takes anAmicodeToolContextand refuses honestly when uncarried — plugin wording preserved byte-identical). The plugin is now a thin adapter; its tests pass unmodified.bin/dist/mcp-amico.mjs(731 KB self-contained, no runtime node_modules), built on the official@modelcontextprotocol/sdk(the fork itself speaks MCP through this same SDK; our schemas ride verbatim; hand-rolled JSON-RPC fallback not needed).pick_system…); opencode'sMcpCatalognamespacing renders the product-identicalamicode_pick_system— asserted bijectively 17/17 in the parity test (double-prefix asserted absent; round-trips under the bare name byte-identical to the plugin path).mcp.amicode(local, the dist bundle, environment carrying the workspace roots); the tool plugin retires;amicode_context.ts(the prompt-time splice — harness-coupled by nature) stays.pick_system→ the entity recording matches the plugin path byte-for-byte on disk.Honest trail
tsc --noEmitfor the first time — 31 latent type errors surfaced and were fixed type-level only (no runtime edits; enumerated in the diff).amicode_session's engine SDK client — parameterized, honest refusal on MCP.bin/dist(the plugin's src-import is .vscodeignore-dead, header-documented).Director-cast in the harness-agnostic campaign, loop 24 (AFK execution per the standing directive).