feat(app): surface AXI tools in MCP status popover#2
Open
davidgut1982 wants to merge 224 commits into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Wire AXI CLI tools (gh-axi, npm-axi, chrome-devtools-axi, cluster-ops-axi) into the opencode status popover MCP section. The label should change from 'MCP' to 'MCP/AXI' and the AXI tools should appear alongside MCP servers with an info-blue indicator (not toggle switches, since they are read-only). The backend's scanAxiTools() already returns AXI resources via the experimental resource endpoint — this connects it to the frontend.
What Changed
scanAxiTools()on the backend scans PATH for AXI CLI tools (gh-axi, npm-axi, chrome-devtools-axi, cluster-ops-axi) and exposes them via the experimental resource endpoint merged alongside MCP resourceschild-store.ts(theenableAxi()call was never reached due to a missingaxi: trueoption), removed deadisSymbolicLink()check thatstat()always returnsfalsefor, corrected AXI fixture shape in tests, and addeddisableAxi()for symmetric test isolationRisk Assessment
✅ Low: All round 1 findings are resolved; the remaining concern is a low-probability key collision that only surfaces if a user happens to have an MCP server literally named "axi".
Testing
Full app unit suite (407 pass / 0 fail / 70 files) exercised including the modified
child-store.test.tswhich adds theaxiquery fixture and verifies the updatedquerySinglescount. Source diff confirms the MCP→MCP/AXI label, info-blue indicator rendering,scanAxiTools()backend, anddisableAxiflag — all user-intent points are satisfied and no regressions were found.Evidence: MCP→MCP/AXI label diff (en.ts)
BASE e97020e: "status.popover.tab.mcp": "MCP" TARGET d799bc6: "status.popover.tab.mcp": "MCP/AXI"Evidence: Full app unit test run (407 pass / 0 fail)
bun test v1.3.14 407 pass 0 fail 1044 expect() calls Ran 407 tests across 70 files. [2.29s]Evidence: scanAxiTools() backend diff
Evidence: App-side AXI diff (status-popover, child-store, directory-sync)
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
⏭️ **Review** - skipped
packages/app/src/context/directory-sync.ts:184- AXI loading is never activated.enableAxi()is only called fromchild()/childReady()whenoptions.axi === true, but the sole call site atdirectory-sync.ts:184only passes{ mcp: true }and notaxi: true. This leavesaxiEnabledpermanentlyfalse, the TanStack Query for AXI is never activated, andsync().data.axiis always{}. The status popover AXI section will silently show nothing regardless of what tools are installed.packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts:195-stat()follows symlinks, sostats.isSymbolicLink()is alwaysfalsewhen usingstat()— symlink detection requireslstat(). The branch!stats.isFile() && !stats.isSymbolicLink()reduces to just!stats.isFile(). Since AXI tools likegh-axiare often symlinks installed by package managers, they are correctly included (symlinks to files returnisFile() === trueviastat()), so behavior is correct. TheisSymbolicLink()check is dead code that should be removed.packages/app/src/context/global-sync/child-store.test.ts:63- The AXI query mock at line 63 returns{ demo: { status: 'disabled' } }— the MCP status shape — copy-pasted from the line above. The actual AXI type is{ [key: string]: { name: string; uri: string; description?: string; mimeType?: string } }. The test only checks query counts so it still passes, but the fixture is misleading for anyone reading or extending this test.packages/app/src/context/global-sync/child-store.ts:324-child-store.tsexportsdisableMcpbut has nodisableAxi. TheaxiTogglesmap is populated but only ever set totrue(viaenableAxi). If a future caller needs to temporarily suspend AXI loading (e.g. for test isolation or dynamic toggling), there is no API. Likely intentional since AXI tools are global/static unlike MCP servers, but worth noting the asymmetry.🔧 Fix: fix axi activation, dead symlink check, test fixture shape, add disableAxi
1 warning still open:
packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts:200- AXI resource keys are generated as"axi:" + sanitize(filename)(e.g.axi:gh-axi). MCP resource keys use the same formatsanitize(clientName) + ":" + sanitize(resourceName). If any MCP server is named"axi", its resources will be silently overwritten by AXI entries in{ ...mcpResources, ...axiResources }. Consider a dedicated prefix that cannot be confused with an MCP client name (e.g.__axi__:or$axi:), or add an assertion/log when the namespaces collide.✅ **Test** - passed
✅ No issues found.
cd packages/app && bun test— full 407-test app unit suite (70 files, 2.29s)git diff e97020efa4b4b9fd48905880c1d5e6cba153da0b d799bc6dbd88d51600e5c6762df95e2571fef618 -- packages/app/src/i18n/en.ts— confirmed MCP→MCP/AXI label changegit diff e97020e d799bc6 -- packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts— confirmed scanAxiTools() implementation and resource() mergegit diff e97020e d799bc6 -- packages/app/src/context/global-sync/child-store.test.ts— confirmed axi fixture and querySingles count bump from 4→5✅ **Document** - passed
✅ No issues found.
packages/app/src/components/status-popover-body.tsx:133- typescript-eslint(unbound-method):platform.getDefaultServerpassed as an unbound method reference touseDefaultServerKey. False positive —getDefaultServeris defined as an arrow function and has nothiscontext to lose.packages/app/src/components/status-popover-body.tsx:281- typescript-eslint(unbound-method): Same false positive on the seconduseDefaultServerKey(platform.getDefaultServer)call inStatusPopoverBody.✅ **Push** - passed
✅ No issues found.