feat(mcp): serve the library over read-only stdio MCP - #171
Merged
Merged
Conversation
Implements ADR 0001 / #80 with the v2 SDK: serveStdio from @modelcontextprotocol/server/stdio, dual-era by default (legacy stays 'serve'), so a 2025-era initialize and a 2026-07-28 per-request envelope are served by one factory. No hand-written handshake, and no Server.connect(new StdioServerTransport()). - Five read-only tools: list_notebooks, search_notebook, get_source, read_document, search_notes. search_notebook returns evidence WITH provenance (document id, page, character offsets), not bare text. No write tool, no model call, and no result carries a filesystem path — that is what makes the surface safe to grant to an agent reading untrusted documents. server/discover is not registered as a tool. - stdio IS the protocol, so the entry redirects every console method to stderr before the database logs on init. Verified end to end: the only bytes on stdout are JSON-RPC frames. - The factory closes over an already-built runtime and opens nothing. serveStdio may call it twice for one connection (optimistic modern probe, then legacy fallback), so anything expensive inside it would run twice. - McpRuntime is the seam: the tools talk to it, never to KnowledgeService, Electron or the database, so the protocol surface is testable with a stub. - KNOWNOTE_DATA_DIR lets the server use a profile other than Electron's default, and the directory is created if it does not exist. Verified: npm run typecheck; npm test (404 pass, 6 new protocol tests driven over the SDK's in-memory transport); npm run check:design; npm run build; eval baseline unchanged; electron . --smoke-test PASS; and an end-to-end run of `electron . --mcp` driven by the v2 client over real stdio (tools/list + tools/call). Co-authored-by: agx7993 <105156832+agx7993@users.noreply.github.com>
mrsibe
force-pushed
the
feat/80-knowledge-mcp
branch
from
September 28, 2026 10:53
5766a10 to
b12c994
Compare
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.
What does this PR do?
Implements the Knowledge MCP server (#80) exactly as ADR 0001 specifies: KnowNote as a read-only MCP server over local stdio, using the TypeScript SDK v2
serveStdio(factory)entry.Why?
#38 is a real user request — someone reached KnowNote from an external agent and published a fork. The right response is not to become an agent platform; it is to make MCP KnowNote's export surface over the retrieval path the app already has.
Related issue
Fixes #80
Related to #79 (ADR), #38 (prior art), #154
SDK note (the ADR is unchanged)
The implementation targets
@modelcontextprotocol/server@2.1.0— the v2 split package, not the v1@modelcontextprotocol/sdk.serveStdiois imported from@modelcontextprotocol/server/stdio.Dual-era is left at its default (
legacy: 'serve'), so one factory serves both a 2025-erainitializeand a 2026-07-28 per-request envelope. There is no hand-written handshake and noServer.connect(new StdioServerTransport()). No ADR amendment is needed.Dependencies:
@modelcontextprotocol/server(runtime) and@modelcontextprotocol/client(dev, for protocol tests).What changed
src/main/mcp/runtime.ts—McpRuntime: the query seam the tools talk to, neverKnowledgeService/Electron/the DB directly. Read-only by construction.src/main/mcp/server.ts—buildMcpServer(runtime), registering exactly the five application tools.server/discoveris not registered as a tool (it is a protocol RPC the SDK entry owns).src/main/mcp/entry.ts—runMcpServer(): redirects every console method to stderr before the DB logs on init (stdio is the protocol), builds the runtime once, thenserveStdio(() => buildMcpServer(runtime)).src/main/index.ts— a--mcpheadless entry alongside--smoke-test/--eval-harness.src/main/db/index.ts—KNOWNOTE_DATA_DIRlets the server use a profile other than Electron'sapp.getPath('userData'), and creates it if missing.Tool surface
list_notebookssearch_notebooknotebook_id,query,top_k(≤50, default 5)get_sourcedocument_idread_documentdocument_id,page?search_notesnotebook_id,queryNo write path, no model call, and no result reports a filesystem path.
How was this tested?
npm run typecheck— passes.npm test— 404 pass, including 6 new protocol tests that drive the real server over the SDK's in-memory transport: the tool list is exactly the five read-only tools (server/discoverabsent, every toolreadOnlyHint), provenance is returned,pagepasses through, a missing source returnsnullrather than an error, andtop_k > 50is rejected at the schema boundary.npm run check:design— no violations.npm run build— passes.docs/eval/baseline-v1.5.json).electron . --smoke-test— PASS.electron . --mcpand drove it with the v2 client (StdioClientTransport):initializeanswered withserverInfo: knownote, no log lines leaked into the stream;tools/listreturned exactly["list_notebooks","search_notebook","get_source","read_document","search_notes"];list_notebooks→[],get_sourcefor a missing id →null.Not verified
initialize) era; the modern 2026-07-28 path is served by the SDK entry but was not driven by a modern client here.Checklist
npm run typecheckpasses.npm run buildpasses.Desktop / build changes
@modelcontextprotocol/server); electron-vite externalizesdependenciesautomatically and electron-builder packs them.