fix(plugins): honour schemaId in xnet_query and xnet_create - #637
Merged
Conversation
Both MCP tools read only `schema`, so a client passing `schemaId` — the field name every node carries — had its filter silently dropped. `store.list` treats an absent `schemaId` as "every schema", so `xnet_query` answered "my pages" with nodes of any type; `xnet_create` could mint a node with no schema at all. Read `schemaId` first, keep `schema` as a deprecated alias, and fail loudly when neither is supplied rather than widening to every node. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
Contributor
|
Preview removed for PR #637. |
crs48
added a commit
that referenced
this pull request
Jul 28, 2026
…638) ## What was broken The desktop app already ran an agent bridge that drives your own `claude` CLI as the chat model. But the agent had **no path to your data**. The MCP wiring sat behind `XNET_BRIDGE_MCP` + `XNET_BRIDGE_MCP_CLI`, and the CLI entry pointed at `xnet mcp serve` — except `@xnetjs/cli` is not an Electron dependency, so that path could never resolve in a packaged app. Even had it resolved, it passed no token, so the local API would have returned 401. Chat could talk about your workspace but not read or write it. ## What this does Hosts the MCP server **in the Electron main process**, over the renderer store proxy, and points the agent at it over Streamable HTTP on an ephemeral loopback port. No CLI to spawn, no local-API hop, no per-session token to plumb across processes. Withhold the tools with `XNET_BRIDGE_MCP=0`. Also replaces the local API's hardcoded four-schema stub (`Schema`/`Task`/`Project`/`Note`) with the renderer's real `schemaRegistry`, served over the same IPC channel that already carries store operations. Anything reading that stub was getting a fiction. ### A subtlety worth reviewing `getAllIRIs()` is synchronous in `SchemaRegistryAPI`, but the registry lives an IPC hop away in the renderer — so the proxy caches it. Both subscribers start **before `createWindow()`**, so priming must be background-with-retry and must never be awaited at boot; awaiting it deadlocks startup (hit and backed out during development). While unprimed it **throws** rather than returning `[]`, so "renderer not ready" cannot read as "this workspace has no schemas". ## Verification Verified in the live desktop app over CDP, not just unit-tested. Bridge status from inside the renderer: ``` { running: true, agent: "claude", url: "http://127.0.0.1:31416", workspaceTools: true } ``` The spawned process, wired as designed: ``` claude -p "..." --output-format stream-json --include-partial-messages --verbose --mcp-config "~/Library/Application Support/xnet-desktop/agent-bridge-mcp.json" --allowedTools mcp__xnet__* ``` A real chat turn through the bridge returned *"Schema count: 194 · New page ID: `M6_jJ2XVxc5aKIx45SX3V`"*, and the page was really in the store: ``` { id: "M6_jJ2XVxc5aKIx45SX3V", schemaId: "xnet://xnet.fyi/Page@1.0.0", title: "Agent Smoke Test 0406" } pageCount: 29 -> 30 ``` **194 schemas is the proof the registry fix works** — the old stub would have reported 4. The test page was deleted afterwards. Also confirmed empirically: `createMcpHttpServer` answers POST with plain `application/json` (no SSE, no `mcp-session-id`), and Claude Code 2.1.220 accepts it as a `{"type":"http"}` MCP server. That was the load-bearing unknown in this design, so it was probed against the real CLI before building on it. Checks: `turbo typecheck` clean, 146 electron + 166 devkit tests green (6 new covering the priming logic), lint 0 errors. ## Notes - The `xnet_query` schemaId bug found while verifying this landed separately in #637. - Changeset included (`@xnetjs/devkit` minor — new `mcpHttpConfigFor` export) plus a changelog fragment. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.
The bug
xnet_queryignored aschemaIdfilter and returned nodes of every schema.Verified against the live desktop app's in-process MCP server: calling
xnet_querywith{"schemaId":"xnet://xnet.fyi/Page@1.0.0","limit":3}cameback with a
Canvasamong the results. TheNodeStorewas not at fault —store.list({ schemaId: 'xnet://xnet.fyi/Page@1.0.0' })returns only Pages.Root cause
The tool was declared with
schemaand the handler readtoolArgs.schema.An MCP client that passes
schemaId— the field name every node the toolshand back actually carries — therefore left
schemaundefined, andstore.listtreats an absentschemaIdas "every schema". The filter was notmis-applied; it was dropped, and the widened result was indistinguishable from
a correct one.
xnet_createhad the same mismatch, where dropping the argument mints a nodewith
schemaId: undefined.The fix
schemaIdfirst and keepschemaas a deprecated alias.a missing filter must fail, not widen.
schemaId(required) alongside the alias, and theexample IRI is a real registered one (
xnet://xnet.fyi/Task@1.0.0).Tests
Three cases added to
mcp-server.test.ts, all failing before the fix:alongside a Project);
schemaIdis accepted as an alias and still filters — this one returned bothnodes before the fix;
pnpm exec vitest run --project integration packages/plugins→ 788 passed.turbo run typecheck --filter=@xnetjs/pluginsand eslint clean.Full
pnpm test: 11258 passed, 3 files failed to load from this worktree'sincomplete
node_modules(smol-tomlunlinked forpackages/cli, electronbinary not installed) — pre-existing and unrelated; CI is the real gate.
🤖 Generated with Claude Code