b339cbb3b789c06969fc139cf4833eda10aac28a
2 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| db7f1fac7d |
Fase 9 (2/N): daemon-hosted MCP server, retire electron/mcp_server.cljs
Completes Fase 9's "consolidar MCP" half: the daemon now serves its own MCP endpoint at /mcp, exposing every semantic-routes operation as an MCP tool against the daemon's authoritative graph, and the old Electron-local MCP server is retired since it's now redundant. New: src/main/frontend/worker/mcp_server.cljs The CF-Worker's own MCP endpoint (deps/db-sync/worker/entry.mjs) generates its tools from the OpenAPI document via @cloudflare/codemode's openApiMcpServer, a package that only exists inside Cloudflare Workers, plus a pile of ChatGPT-App-specific tooling (asset image resources, upload_asset, Cognito protected-resource metadata) this daemon has no use for -- none of that ports to Node. So this registers one tool per operation by hand instead, using @modelcontextprotocol/sdk directly, the same pattern electron/mcp_server.cljs already used for its own 6 tools (registerTool with an explicit title/description/inputSchema/handler) -- just generated from semantic-routes' operation table and operation-docs (now public) instead of writing 35 one-off registrations by hand, so the MCP tool surface can't drift from the HTTP routes or the OpenAPI document. Each tool's Zod input shape is assembled from path params (auto-derived from the route's :param segments) plus a per-operation-id map of the extra query/body fields; a handful of shared fragments (pagination, time filters, the recursive block-tree shape, property-value/task-status selectors) cover the repetition across list/create/update operations. Each tool's handler makes a loopback HTTP call to this same daemon's own /v1/semantic/* route, forwarding the /mcp request's own Authorization header -- reusing the already-tested HTTP-layer logic (repo-error validation, lock-owner assertion, response shaping) instead of a second request path directly against the thread-api. Session/transport plumbing (handle-post/get/delete-request, the transports-by-session-id map) is adapted from electron/mcp_server.cljs's Fastify-based version: that one gets req/res already unwrapped-to-Node-primitives via `.raw` with the body pre-parsed by Fastify; this one is called straight from the daemon's own raw http.createServer callback, so req/res already are what StreamableHTTPServerTransport wants, and the body needs parsing here instead. No separate auth check either -- the daemon's existing bearer-token check on every route (request-authorized? in db_worker_node.cljs) already gates /mcp before this is reached. Wires POST/GET/DELETE /mcp into db_worker_node.cljs's make-server cond chain, alongside the existing /v1/semantic/* branch. Dependencies: adds @modelcontextprotocol/sdk and zod (zod/v3 -- the SDK doesn't work with zod 4's default export, same constraint electron/mcp_server.cljs already worked around) to the root package.json; previously only resources/package.json (Electron's own bundle) had them. Retired: electron/mcp_server.cljs and its wiring (electron/server.cljs's initialize-mcp-routes, its 3 POST/GET/DELETE /mcp route registrations, the :mcp-enabled? config field; components/server.cljs's "copy MCP URL" popup link; components/settings.cljs's entire AI settings tab, which had nothing in it besides the MCP-server toggle). Electron already talks to the daemon (Fase 3) rather than driving itself locally, so a second, separately-configured MCP surface bolted onto Electron's own local automation API server (which otherwise stays -- /api is a distinct, still-used concern) was redundant with the daemon's new one. Removed the 4 associated i18n keys (server/mcp-url-copied, settings/ai, settings.ai/enable-mcp-server[-desc]) from all ~26 locale dicts. New: src/test/frontend/worker/mcp_server_test.cljs Connects a real @modelcontextprotocol/sdk client over HTTP to a live daemon's /mcp endpoint: lists all registered tools, round-trips createPage/getPage and a recursive captureToToday block tree, and checks a tool call against a nonexistent page surfaces as MCP error content rather than throwing. 4 tests, 11 assertions. Verification: all 5 shadow-cljs targets compile with 0 warnings. Full `bb dev:test` run: the only failures are the same 7 pre-existing, sandboxed-network-related persist_db_test.cljs failures already documented in the Fase 8 and Fase 9 (1/N) commits -- identical test names, identical ECONNREFUSED/Invalid URL errors. Every other shard, including all three daemon-HTTP worker test files sharing one test.js process (db_worker_node_test.cljs, semantic_test.cljs, mcp_server_test.cljs), passes clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
|||
| 196de21731 |
Fase 9 (1/N): re-host the semantic API onto the daemon
Ports the 38-operation semantic API from deps/db-sync's Cloudflare Worker
handler (handler/semantic.cljs + routes/semantic.cljs) onto the daemon's
own authoritative conn, per the plan's Fase 9. deps/db-sync itself is
untouched -- this only adds the daemon-side equivalent.
New:
- worker/semantic_routes.cljs: route table + OpenAPI 3.1.0 document
generator, adapted from the CF-Worker's routes/semantic.cljs. Drops the
:graph-id URL segment and listGraphs operation entirely -- the daemon is
bound to a single repo per process (see db_worker_node.cljs's
bound-repo), unlike the CF-Worker's per-graph Durable Objects, so every
request implicitly targets the daemon's own bound repo. Security scheme
is a bearer token (the daemon's actual session auth) instead of OAuth2.
- worker/handler/semantic.cljs: the 38 operations themselves, ported from
the CF-Worker's handler/semantic.cljs. Adaptations from the original:
* ensure-conn! (lazy SQLite conn open) -> worker.state/get-datascript-conn
(the daemon already holds the authoritative conn for its bound repo).
* broadcast-change! calls dropped entirely -- any transact! against the
conn is already picked up by the listener worker.db-listener installs,
which broadcasts to every connected client, so nothing here needs to
push manually.
* HTTP body reads (p/let + body-clj) dropped -- bodies arrive already
JSON-parsed from the HTTP layer, and every operation is synchronous
(DataScript itself is synchronous), so none of this needs promises.
* http/bad-request, http/not-found, js/Response error helpers -> ex-info
with :status/:code, matching every other thread-api in this codebase.
* R2 asset storage -> platform/asset-write-bytes! (local filesystem),
using the <uuid>.<type> naming convention components/block/asset.cljs
already establishes. R2 signed URLs -> server-relative
/v1/assets/:repo/:name paths (already served, with ETag caching, by
the existing Fase 5 asset routes) since bearer-token auth already
gates those with the same ?token= fallback semantic GET routes need.
Registered as a single :thread-api/semantic-request thread-api
dispatched by handler keyword, mirroring how the CF-Worker original
dispatched via one big `case` in `handle`.
- db_worker_node.cljs: wires a /v1/semantic/* branch (JSON body parsed
for most operations; raw bytes read directly for assets-create,
mirroring the existing /v1/assets/:repo/:name PUT route) plus
/v1/semantic/openapi.json. Unlike /v1/invoke -- an internal RPC
transport whose transit-encoded results Logseq's own clients already
know how to decode -- /v1/semantic/* is the public HTTP API documented
by the OpenAPI doc and meant for arbitrary HTTP/MCP clients, so results
are decoded server-side into plain JSON, the same way the existing
/v1/assets/:repo/:name GET route already does for the other route with
external HTTP consumers.
- src/test/frontend/worker/semantic_test.cljs: exercises pages,
blocks/properties, capture, tags/tasks, search, the OpenAPI document,
401-without-auth, and 404-on-unknown-route against a live daemon over
real HTTP. 7 tests, 31 assertions.
Verification: all 5 shadow-cljs targets compile with 0 warnings. Full
`bb dev:test` run has zero regressions -- the only failures are the same
7 pre-existing, sandboxed-network-related persist_db_test.cljs failures
(ECONNREFUSED / Invalid URL) already documented as environmental in the
Fase 8 commit.
Found and fixed in-session: semantic_test.cljs originally set its own
LOGSEQ_DAEMON_USERNAME/PASSWORD env vars at namespace load time, colliding
with db_worker_node_test.cljs doing the same (both get bundled into the
same test.js process) -- whichever namespace loaded last won the race and
locked the other file's daemons out of their own bootstrapped credential,
cascading into ~150 unrelated failures. Fixed by reusing the same literal
credential constants instead of introducing a second one.
Known follow-up, not yet done: the plan's "consolidar MCP" half of Fase 9
-- an MCP JSON-RPC endpoint on the daemon (the CF-Worker's equivalent,
deps/db-sync/worker/entry.mjs, leans on @cloudflare/codemode's
openApiMcpServer plus ChatGPT-app-specific tooling that doesn't port to
Node) and the decision on electron/mcp_server.cljs's fate are still open.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|