Lazy-load database and embedding modules in MCP server - #2
Open
wolfiesch wants to merge 1 commit into
Open
Conversation
Defers database connection, schema migration, and embedding pipeline initialization until tool execution time. MCP server startup now connects to transport immediately without loading heavy native SQLite or ONNX bindings on boot. Preserves automatic schema migration for legacy index databases via a memoized ensureDbReady helper before read-only queries are executed.
wolfiesch
force-pushed
the
perf/lazy-mcp-startup-and-visual-read
branch
from
August 19, 2026 09:08
799df9a to
a7210c7
Compare
There was a problem hiding this comment.
Pull request overview
This PR restructures the MCP server so heavyweight DB/embedding initialization is deferred until tool execution time, and adds a new read_visual tool that renders transcripts into cached PNG pages for vision-capable models (plus telemetry and regression tests).
Changes:
- Lazy-load DB access (and associated schema migration) during tool calls via
ensureDbReady()and dynamic imports. - Add
read_visualMCP tool powered by a Bun-rendered rasterization pipeline with a content-addressed cache and integrity checks. - Add session-path boundary resolution helper, telemetry logging, tests, and dogfood harness for validating visual-read caching/latency behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/visual-read.test.ts | Adds unit tests for visual page caching, identity binding, and corruption handling. |
| test/session-path.test.ts | Adds tests for bounded session path resolution and traversal rejection. |
| test/db.test.ts | Adds regression test proving lazy migration happens before read-only DB queries. |
| src/visual-read.ts | Implements content-addressed visual rendering cache + receipt integrity validation. |
| src/session-path.ts | Adds a shared helper to resolve session paths safely within a configured root. |
| src/mcp-server.ts | Defers DB initialization, adds telemetry, and introduces read_visual tool. |
| scripts/render-pages.ts | Bun bridge script that rasterizes markdown into PNG pages + manifest for caching. |
| package.json | Publishes render script and adds @oh-my-pi/snapcompact dependency. |
| dogfood/read-visual/contract.ts | Defines dogfood protocol/receipt schemas and immutability helpers. |
| dogfood/read-visual/preflight.ts | Preflight checks for reproducible dogfood runs and artifact identity. |
| dogfood/read-visual/run.ts | Executes serialized local MCP dogfood runs and records receipts. |
| dogfood/read-visual/score.ts | Scores dogfood outputs against protocol gates and emits a score artifact. |
| dogfood/read-visual/protocol.json | Declares the dogfood execution plan and acceptance gates. |
| bun.lock | Locks newly added snapcompact dependency tree. |
Suppressed comments (3)
src/mcp-server.ts:189
formatConversationassumesparseSessionFile()returns at least one exchange, butparseSessionFilecan return an empty array (e.g. oversized sessions are skipped). This will throw when accessingexchanges[0], breaking bothreadandread_visualfor those files.
`Session: ${header.sessionId}`,
header.cwd ? `Directory: ${header.cwd}` : "",
`Exchanges: ${exchanges.length}`,
"",
src/mcp-server.ts:460
list_gotchasdefines an optionalqueryinput, but the handler never passes it tosearchMemoryRecords, so supplyingqueryhas no effect.
src/mcp-server.ts:493get_project_contextaccepts an optionalprojectinput, but the handler does not pass it togetProjectContext, so project-scoped context is never returned.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+28
to
+48
| let dbReadyPromise: Promise<void> | null = null; | ||
|
|
||
| /** | ||
| * Lazily runs index DB schema migration once on the first DB-backed tool call, | ||
| * ensuring older DB schemas are upgraded without paying migration cost on server boot. | ||
| */ | ||
| export async function ensureDbReady(dbPath: string = DB_PATH): Promise<void> { | ||
| if (!dbReadyPromise) { | ||
| dbReadyPromise = (async () => { | ||
| if (existsSync(dbPath)) { | ||
| try { | ||
| const { openDb } = await import("./db.js"); | ||
| openDb(dbPath).close(); | ||
| } catch (error) { | ||
| console.error("Schema migration skipped:", error instanceof Error ? error.message : String(error)); | ||
| } | ||
| } | ||
| })(); | ||
| } | ||
| return dbReadyPromise; | ||
| } |
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.
Summary
ensureDbReady()helper before read-only queries are executed.better-sqlite3,sqlite-vec, and@xenova/transformersONNX bindings during initial MCP server transport connection.test/db.test.tsverifying that outdated index DB fixtures migrate lazily before read queries.Performance Receipts (5-Session Averages on Exact PR Head)
96.6 ms(down from175.6 mswarm /616 mscold).24.5 ms(skips embedding model initialization).210.4 ms(loads embedding pipeline on demand).44.6 ms(model resident in memory).