feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (#700) - #702

Merged
aarontrowbridge merged 8 commits into
mainfrom
700-feat-extension-a3-the-mcp-tool-floor
Sep 1, 2026
Merged

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (#700)#702
aarontrowbridge merged 8 commits into
mainfrom
700-feat-extension-a3-the-mcp-tool-floor

Conversation

@aarontrowbridge

Copy link
Copy Markdown
Member

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 an AmicodeToolContext and refuses honestly when uncarried — plugin wording preserved byte-identical). The plugin is now a thin adapter; its tests pass unmodified.
  • The MCP stdio server — 4th esbuild entry → 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).
  • The naming contract (director decision): the MCP server serves bare names (pick_system…); opencode's McpCatalog namespacing 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).
  • The opencode wiring: the injected config carries 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.
  • Round-trip proof: spawn the built server against a temp problem dir → initialize → tools/list → tools/call pick_system → the entity recording matches the plugin path byte-for-byte on disk.

Honest trail

  • The moved bodies came under tsc --noEmit for the first time — 31 latent type errors surfaced and were fixed type-level only (no runtime edits; enumerated in the diff).
  • Exactly one harness dependency found in 1796 lines: amicode_session's engine SDK client — parameterized, honest refusal on MCP.
  • Follow-ups flagged, not hidden: the slow e2e suite pins the retired transport (needs an MCP-aware rewrite against the vendored binary — live-phase); per-model schema transforms ride MCP verbatim (runtime unverified); the packaged-runtime path ships the tools via bin/dist (the plugin's src-import is .vscodeignore-dead, header-documented).
  • Base 9d2141a; origin/main advanced mid-slice (feat: SEAM 2 — regime rules as recommendations, the five-knob priors table (#699) #701) — rebase/integration on the walk.

Director-cast in the harness-agnostic campaign, loop 24 (AFK execution per the standing directive).

@coderabbitai

coderabbitaiBot commented Sep 1, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: ac0089de-705e-446a-be73-8b960c65fe16


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…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).
@aarontrowbridge
aarontrowbridgeforce-pushed the 700-feat-extension-a3-the-mcp-tool-floor branch from acc0a21 to faccb77CompareSeptember 1, 2026 06:16
@aarontrowbridge
aarontrowbridge merged commit 14012fe into mainSep 1, 2026
9 checks passed
Sign up for freeto 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.

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (plugin + MCP stdio)

1 participant

@aarontrowbridge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (#700) - #702

Merged
aarontrowbridge merged 8 commits into
mainfrom
700-feat-extension-a3-the-mcp-tool-floor
Sep 1, 2026
Merged

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (#700)#702
aarontrowbridge merged 8 commits into
mainfrom
700-feat-extension-a3-the-mcp-tool-floor

Conversation

@aarontrowbridge

Copy link
Copy Markdown
Member

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 an AmicodeToolContext and refuses honestly when uncarried — plugin wording preserved byte-identical). The plugin is now a thin adapter; its tests pass unmodified.
  • The MCP stdio server — 4th esbuild entry → 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).
  • The naming contract (director decision): the MCP server serves bare names (pick_system…); opencode's McpCatalog namespacing 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).
  • The opencode wiring: the injected config carries 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.
  • Round-trip proof: spawn the built server against a temp problem dir → initialize → tools/list → tools/call pick_system → the entity recording matches the plugin path byte-for-byte on disk.

Honest trail

  • The moved bodies came under tsc --noEmit for the first time — 31 latent type errors surfaced and were fixed type-level only (no runtime edits; enumerated in the diff).
  • Exactly one harness dependency found in 1796 lines: amicode_session's engine SDK client — parameterized, honest refusal on MCP.
  • Follow-ups flagged, not hidden: the slow e2e suite pins the retired transport (needs an MCP-aware rewrite against the vendored binary — live-phase); per-model schema transforms ride MCP verbatim (runtime unverified); the packaged-runtime path ships the tools via bin/dist (the plugin's src-import is .vscodeignore-dead, header-documented).
  • Base 9d2141a; origin/main advanced mid-slice (feat: SEAM 2 — regime rules as recommendations, the five-knob priors table (#699) #701) — rebase/integration on the walk.

Director-cast in the harness-agnostic campaign, loop 24 (AFK execution per the standing directive).

@coderabbitai

coderabbitaiBot commented Sep 1, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: ac0089de-705e-446a-be73-8b960c65fe16


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…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).
@aarontrowbridge
aarontrowbridgeforce-pushed the 700-feat-extension-a3-the-mcp-tool-floor branch from acc0a21 to faccb77CompareSeptember 1, 2026 06:16
@aarontrowbridge
aarontrowbridge merged commit 14012fe into mainSep 1, 2026
9 checks passed
Sign up for freeto 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.

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (plugin + MCP stdio)

1 participant

@aarontrowbridge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (#700) - #702

Merged
aarontrowbridge merged 8 commits into
mainfrom
700-feat-extension-a3-the-mcp-tool-floor
Sep 1, 2026
Merged

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (#700)#702
aarontrowbridge merged 8 commits into
mainfrom
700-feat-extension-a3-the-mcp-tool-floor

Conversation

@aarontrowbridge

Copy link
Copy Markdown
Member

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 an AmicodeToolContext and refuses honestly when uncarried — plugin wording preserved byte-identical). The plugin is now a thin adapter; its tests pass unmodified.
  • The MCP stdio server — 4th esbuild entry → 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).
  • The naming contract (director decision): the MCP server serves bare names (pick_system…); opencode's McpCatalog namespacing 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).
  • The opencode wiring: the injected config carries 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.
  • Round-trip proof: spawn the built server against a temp problem dir → initialize → tools/list → tools/call pick_system → the entity recording matches the plugin path byte-for-byte on disk.

Honest trail

  • The moved bodies came under tsc --noEmit for the first time — 31 latent type errors surfaced and were fixed type-level only (no runtime edits; enumerated in the diff).
  • Exactly one harness dependency found in 1796 lines: amicode_session's engine SDK client — parameterized, honest refusal on MCP.
  • Follow-ups flagged, not hidden: the slow e2e suite pins the retired transport (needs an MCP-aware rewrite against the vendored binary — live-phase); per-model schema transforms ride MCP verbatim (runtime unverified); the packaged-runtime path ships the tools via bin/dist (the plugin's src-import is .vscodeignore-dead, header-documented).
  • Base 9d2141a; origin/main advanced mid-slice (feat: SEAM 2 — regime rules as recommendations, the five-knob priors table (#699) #701) — rebase/integration on the walk.

Director-cast in the harness-agnostic campaign, loop 24 (AFK execution per the standing directive).

@coderabbitai

coderabbitaiBot commented Sep 1, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: ac0089de-705e-446a-be73-8b960c65fe16


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…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).
@aarontrowbridge
aarontrowbridgeforce-pushed the 700-feat-extension-a3-the-mcp-tool-floor branch from acc0a21 to faccb77CompareSeptember 1, 2026 06:16
@aarontrowbridge
aarontrowbridge merged commit 14012fe into mainSep 1, 2026
9 checks passed
Sign up for freeto 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.

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (plugin + MCP stdio)

1 participant

@aarontrowbridge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (#700) - #702

Merged
aarontrowbridge merged 8 commits into
mainfrom
700-feat-extension-a3-the-mcp-tool-floor
Sep 1, 2026
Merged

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (#700)#702
aarontrowbridge merged 8 commits into
mainfrom
700-feat-extension-a3-the-mcp-tool-floor

Conversation

@aarontrowbridge

Copy link
Copy Markdown
Member

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 an AmicodeToolContext and refuses honestly when uncarried — plugin wording preserved byte-identical). The plugin is now a thin adapter; its tests pass unmodified.
  • The MCP stdio server — 4th esbuild entry → 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).
  • The naming contract (director decision): the MCP server serves bare names (pick_system…); opencode's McpCatalog namespacing 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).
  • The opencode wiring: the injected config carries 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.
  • Round-trip proof: spawn the built server against a temp problem dir → initialize → tools/list → tools/call pick_system → the entity recording matches the plugin path byte-for-byte on disk.

Honest trail

  • The moved bodies came under tsc --noEmit for the first time — 31 latent type errors surfaced and were fixed type-level only (no runtime edits; enumerated in the diff).
  • Exactly one harness dependency found in 1796 lines: amicode_session's engine SDK client — parameterized, honest refusal on MCP.
  • Follow-ups flagged, not hidden: the slow e2e suite pins the retired transport (needs an MCP-aware rewrite against the vendored binary — live-phase); per-model schema transforms ride MCP verbatim (runtime unverified); the packaged-runtime path ships the tools via bin/dist (the plugin's src-import is .vscodeignore-dead, header-documented).
  • Base 9d2141a; origin/main advanced mid-slice (feat: SEAM 2 — regime rules as recommendations, the five-knob priors table (#699) #701) — rebase/integration on the walk.

Director-cast in the harness-agnostic campaign, loop 24 (AFK execution per the standing directive).

@coderabbitai

coderabbitaiBot commented Sep 1, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: ac0089de-705e-446a-be73-8b960c65fe16


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…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).
@aarontrowbridge
aarontrowbridgeforce-pushed the 700-feat-extension-a3-the-mcp-tool-floor branch from acc0a21 to faccb77CompareSeptember 1, 2026 06:16
@aarontrowbridge
aarontrowbridge merged commit 14012fe into mainSep 1, 2026
9 checks passed
Sign up for freeto 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.

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (plugin + MCP stdio)

1 participant

@aarontrowbridge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (#700) - #702

Merged
aarontrowbridge merged 8 commits into
mainfrom
700-feat-extension-a3-the-mcp-tool-floor
Sep 1, 2026
Merged

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (#700)#702
aarontrowbridge merged 8 commits into
mainfrom
700-feat-extension-a3-the-mcp-tool-floor

Conversation

@aarontrowbridge

Copy link
Copy Markdown
Member

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 an AmicodeToolContext and refuses honestly when uncarried — plugin wording preserved byte-identical). The plugin is now a thin adapter; its tests pass unmodified.
  • The MCP stdio server — 4th esbuild entry → 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).
  • The naming contract (director decision): the MCP server serves bare names (pick_system…); opencode's McpCatalog namespacing 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).
  • The opencode wiring: the injected config carries 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.
  • Round-trip proof: spawn the built server against a temp problem dir → initialize → tools/list → tools/call pick_system → the entity recording matches the plugin path byte-for-byte on disk.

Honest trail

  • The moved bodies came under tsc --noEmit for the first time — 31 latent type errors surfaced and were fixed type-level only (no runtime edits; enumerated in the diff).
  • Exactly one harness dependency found in 1796 lines: amicode_session's engine SDK client — parameterized, honest refusal on MCP.
  • Follow-ups flagged, not hidden: the slow e2e suite pins the retired transport (needs an MCP-aware rewrite against the vendored binary — live-phase); per-model schema transforms ride MCP verbatim (runtime unverified); the packaged-runtime path ships the tools via bin/dist (the plugin's src-import is .vscodeignore-dead, header-documented).
  • Base 9d2141a; origin/main advanced mid-slice (feat: SEAM 2 — regime rules as recommendations, the five-knob priors table (#699) #701) — rebase/integration on the walk.

Director-cast in the harness-agnostic campaign, loop 24 (AFK execution per the standing directive).

@coderabbitai

coderabbitaiBot commented Sep 1, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: ac0089de-705e-446a-be73-8b960c65fe16


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…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).
@aarontrowbridge
aarontrowbridgeforce-pushed the 700-feat-extension-a3-the-mcp-tool-floor branch from acc0a21 to faccb77CompareSeptember 1, 2026 06:16
@aarontrowbridge
aarontrowbridge merged commit 14012fe into mainSep 1, 2026
9 checks passed
Sign up for freeto 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.

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (plugin + MCP stdio)

1 participant

@aarontrowbridge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (#700) - #702

Merged
aarontrowbridge merged 8 commits into
mainfrom
700-feat-extension-a3-the-mcp-tool-floor
Sep 1, 2026
Merged

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (#700)#702
aarontrowbridge merged 8 commits into
mainfrom
700-feat-extension-a3-the-mcp-tool-floor

Conversation

@aarontrowbridge

Copy link
Copy Markdown
Member

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 an AmicodeToolContext and refuses honestly when uncarried — plugin wording preserved byte-identical). The plugin is now a thin adapter; its tests pass unmodified.
  • The MCP stdio server — 4th esbuild entry → 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).
  • The naming contract (director decision): the MCP server serves bare names (pick_system…); opencode's McpCatalog namespacing 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).
  • The opencode wiring: the injected config carries 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.
  • Round-trip proof: spawn the built server against a temp problem dir → initialize → tools/list → tools/call pick_system → the entity recording matches the plugin path byte-for-byte on disk.

Honest trail

  • The moved bodies came under tsc --noEmit for the first time — 31 latent type errors surfaced and were fixed type-level only (no runtime edits; enumerated in the diff).
  • Exactly one harness dependency found in 1796 lines: amicode_session's engine SDK client — parameterized, honest refusal on MCP.
  • Follow-ups flagged, not hidden: the slow e2e suite pins the retired transport (needs an MCP-aware rewrite against the vendored binary — live-phase); per-model schema transforms ride MCP verbatim (runtime unverified); the packaged-runtime path ships the tools via bin/dist (the plugin's src-import is .vscodeignore-dead, header-documented).
  • Base 9d2141a; origin/main advanced mid-slice (feat: SEAM 2 — regime rules as recommendations, the five-knob priors table (#699) #701) — rebase/integration on the walk.

Director-cast in the harness-agnostic campaign, loop 24 (AFK execution per the standing directive).

@coderabbitai

coderabbitaiBot commented Sep 1, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: ac0089de-705e-446a-be73-8b960c65fe16


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…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).
@aarontrowbridge
aarontrowbridgeforce-pushed the 700-feat-extension-a3-the-mcp-tool-floor branch from acc0a21 to faccb77CompareSeptember 1, 2026 06:16
@aarontrowbridge
aarontrowbridge merged commit 14012fe into mainSep 1, 2026
9 checks passed
Sign up for freeto 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.

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (plugin + MCP stdio)

1 participant

@aarontrowbridge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (#700) - #702

Merged
aarontrowbridge merged 8 commits into
mainfrom
700-feat-extension-a3-the-mcp-tool-floor
Sep 1, 2026
Merged

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (#700)#702
aarontrowbridge merged 8 commits into
mainfrom
700-feat-extension-a3-the-mcp-tool-floor

Conversation

@aarontrowbridge

Copy link
Copy Markdown
Member

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 an AmicodeToolContext and refuses honestly when uncarried — plugin wording preserved byte-identical). The plugin is now a thin adapter; its tests pass unmodified.
  • The MCP stdio server — 4th esbuild entry → 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).
  • The naming contract (director decision): the MCP server serves bare names (pick_system…); opencode's McpCatalog namespacing 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).
  • The opencode wiring: the injected config carries 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.
  • Round-trip proof: spawn the built server against a temp problem dir → initialize → tools/list → tools/call pick_system → the entity recording matches the plugin path byte-for-byte on disk.

Honest trail

  • The moved bodies came under tsc --noEmit for the first time — 31 latent type errors surfaced and were fixed type-level only (no runtime edits; enumerated in the diff).
  • Exactly one harness dependency found in 1796 lines: amicode_session's engine SDK client — parameterized, honest refusal on MCP.
  • Follow-ups flagged, not hidden: the slow e2e suite pins the retired transport (needs an MCP-aware rewrite against the vendored binary — live-phase); per-model schema transforms ride MCP verbatim (runtime unverified); the packaged-runtime path ships the tools via bin/dist (the plugin's src-import is .vscodeignore-dead, header-documented).
  • Base 9d2141a; origin/main advanced mid-slice (feat: SEAM 2 — regime rules as recommendations, the five-knob priors table (#699) #701) — rebase/integration on the walk.

Director-cast in the harness-agnostic campaign, loop 24 (AFK execution per the standing directive).

@coderabbitai

coderabbitaiBot commented Sep 1, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: ac0089de-705e-446a-be73-8b960c65fe16


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…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).
@aarontrowbridge
aarontrowbridgeforce-pushed the 700-feat-extension-a3-the-mcp-tool-floor branch from acc0a21 to faccb77CompareSeptember 1, 2026 06:16
@aarontrowbridge
aarontrowbridge merged commit 14012fe into mainSep 1, 2026
9 checks passed
Sign up for freeto 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.

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (plugin + MCP stdio)

1 participant

@aarontrowbridge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (#700) - #702

Merged
aarontrowbridge merged 8 commits into
mainfrom
700-feat-extension-a3-the-mcp-tool-floor
Sep 1, 2026
Merged

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (#700)#702
aarontrowbridge merged 8 commits into
mainfrom
700-feat-extension-a3-the-mcp-tool-floor

Conversation

@aarontrowbridge

Copy link
Copy Markdown
Member

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 an AmicodeToolContext and refuses honestly when uncarried — plugin wording preserved byte-identical). The plugin is now a thin adapter; its tests pass unmodified.
  • The MCP stdio server — 4th esbuild entry → 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).
  • The naming contract (director decision): the MCP server serves bare names (pick_system…); opencode's McpCatalog namespacing 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).
  • The opencode wiring: the injected config carries 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.
  • Round-trip proof: spawn the built server against a temp problem dir → initialize → tools/list → tools/call pick_system → the entity recording matches the plugin path byte-for-byte on disk.

Honest trail

  • The moved bodies came under tsc --noEmit for the first time — 31 latent type errors surfaced and were fixed type-level only (no runtime edits; enumerated in the diff).
  • Exactly one harness dependency found in 1796 lines: amicode_session's engine SDK client — parameterized, honest refusal on MCP.
  • Follow-ups flagged, not hidden: the slow e2e suite pins the retired transport (needs an MCP-aware rewrite against the vendored binary — live-phase); per-model schema transforms ride MCP verbatim (runtime unverified); the packaged-runtime path ships the tools via bin/dist (the plugin's src-import is .vscodeignore-dead, header-documented).
  • Base 9d2141a; origin/main advanced mid-slice (feat: SEAM 2 — regime rules as recommendations, the five-knob priors table (#699) #701) — rebase/integration on the walk.

Director-cast in the harness-agnostic campaign, loop 24 (AFK execution per the standing directive).

@coderabbitai

coderabbitaiBot commented Sep 1, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: ac0089de-705e-446a-be73-8b960c65fe16


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…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).
@aarontrowbridge
aarontrowbridgeforce-pushed the 700-feat-extension-a3-the-mcp-tool-floor branch from acc0a21 to faccb77CompareSeptember 1, 2026 06:16
@aarontrowbridge
aarontrowbridge merged commit 14012fe into mainSep 1, 2026
9 checks passed
Sign up for freeto 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.

feat(extension): A3 — the MCP tool floor: one amicode_* implementation, two transports (plugin + MCP stdio)

1 participant

@aarontrowbridge