feat(mcp): MCP server, seven memory tools, and stdio integration (T10)#16
Conversation
- deps: @modelcontextprotocol/sdk + zod (for future tool inputSchemas) - src/mcp/server.ts: createMcpServer (name "dennoh" + package version) and startStdioServer, which connects a transport (stdio by default, injectable for tests) and resolves only on close so `serve` stays alive for the session's lifetime - src/mcp/types.ts: StatusResult (forward type for the T10.10 status tool) - src/mcp/index.ts: real exports replacing the stub - src/cli/commands/serve.ts: reads config, opens the index, starts the stdio server; diagnostics go to stderr only (stdout is the JSON-RPC stream) - wire `serve` into the router/help; drop the "not implemented" stub - tests: identity handshake + connect/close lifecycle via in-memory transport; serve validation/config-error paths assert nothing is written to stdout Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each tool lives in its own file under src/mcp/tools/ with a hardcoded English
description, a zod inputSchema matched to the exact core/memory signature, and a
handler that reports failures as `isError` results (never a protocol throw):
- save_memory / update_memory -> return the note metadata (read back via the row)
- delete_memory -> { id, deleted: true }
- search_memory -> { results } with project/tag/date/source filters
- list_recent -> { notes } (metadata only)
- get_note -> { note } or { note: null }
- status -> { indexedCount, latestError } (queueDepth only with a watcher)
Supporting changes:
- db: getIndexStats(db) aggregate for the status tool
- mcp: McpContext (db + vaultPath); createMcpServer(context) registers all tools
via registerAllTools; serve passes { db, vaultPath }
- shared tools/result.ts (toolOk/toolError) and tools/metadata.ts (NoteRow DTO)
- tests: full client<->server tool round-trip over an in-memory transport, plus
tool-registration and isError-not-throw assertions
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- tests/mcp/harness.ts: shared per-test setup (temp vault: git + SQLite, linked in-memory client/server) + result helpers; replaces the monolithic tools.test - per-tool files (save/update/delete/search/list-recent/get-note/status): happy paths plus abnormal cases (unknown id, schema-validation failures, NULL-byte content) — all asserting failures arrive as isError results - tests/mcp/integration.test.ts: spawns the real `dennoh serve` over stdio via StdioClientTransport — initialize handshake, multi-tool scenario writing an actual .md file, and logs-on-stderr / JSON-RPC-only-on-stdout checks - docs/claude-desktop-setup.md: claude_desktop_config.json examples (source + built), path guidance, and save_memory verification steps fix(serve): run migrations on startup — the stdio integration test caught that serve opened the DB without creating the schema, so save_memory failed with "no such table: notes" on a fresh vault. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthrough
ChangesMCP サーバー全体実装
Sequence Diagram(s)sequenceDiagram
participant Claude as Claude Desktop
participant Stdio as StdioServerTransport
participant serveCmd as serveCommand
participant McpServer as McpServer
participant ToolHandler as Tool Handler
participant DB as SQLite DB
Claude->>Stdio: JSON-RPC initialize (stdin)
Stdio->>serveCmd: プロセス起動 (dennoh serve)
serveCmd->>McpServer: createMcpServer({ db, vaultPath })
McpServer->>McpServer: registerAllTools (7 ツール登録)
serveCmd->>Stdio: startStdioServer → server.connect(transport)
Stdio-->>Claude: initialize レスポンス (stdout)
Claude->>Stdio: tools/list
Stdio-->>Claude: 7 つのツール名を返却
Claude->>Stdio: tools/call (save_memory)
Stdio->>McpServer: ディスパッチ
McpServer->>ToolHandler: Zod バリデーション + 実行
ToolHandler->>DB: saveMemory(content, source)
DB-->>ToolHandler: NoteRow
ToolHandler-->>McpServer: toolOk(NoteMetadataDto)
McpServer-->>Stdio: CallToolResult (JSON テキスト)
Stdio-->>Claude: JSON-RPC レスポンス (stdout)
Claude->>Stdio: tools/call (search_memory)
Stdio->>McpServer: ディスパッチ
McpServer->>ToolHandler: Zod バリデーション + 実行
ToolHandler->>DB: searchMemory(query, filters, limit)
DB-->>ToolHandler: NoteRow[]
ToolHandler-->>McpServer: toolOk({ results })
McpServer-->>Stdio: CallToolResult
Stdio-->>Claude: JSON-RPC レスポンス
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 3❌ Failed checks (3 warnings)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Comment |
There was a problem hiding this comment.
Actionable comments posted: 13
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/claude-desktop-setup.md`:
- Around line 31-33: The fenced code block containing the file path on lines
31-33 is missing a language specifier. Add `plaintext` or `text` as the language
identifier immediately after the opening triple backticks to properly format the
code block in markdown, making it more readable and semantically correct.
In `@src/cli/commands/serve.ts`:
- Around line 24-40: The database initialization calls openDatabase and
runMigrations are outside the try/catch block, so exceptions in these functions
propagate without proper error handling and resource cleanup. The finally block
only applies to the inner try block that starts the server, leaving the database
open if initialization fails. Restructure the code to move openDatabase and
runMigrations inside the outer try block, and ensure the finally block that
calls closeDatabase applies to the entire initialization and server startup
sequence, so that database resources are properly cleaned up regardless of where
exceptions occur.
In `@src/db/repository.ts`:
- Around line 136-143: The getIndexStats function uses implicit fallbacks with
the nullish coalescing operator (row?.noteCount ?? 0 and row?.lastUpdatedAt ??
null) instead of throwing explicit errors when the query returns unexpected
results. Replace these implicit fallbacks with an explicit null check that
throws an error if row is null or undefined, since the comment correctly notes
that an aggregate SELECT always returns exactly one row and a null result
indicates an unexpected condition (such as database corruption) that should fail
explicitly rather than silently returning default values.
In `@src/mcp/server.ts`:
- Around line 1-8: Reorganize the import statements in the file to follow proper
grouping guidelines. Third-party imports (McpServer, StdioServerTransport,
Transport) should be separated from local project imports by a single blank
line. The package.json import should be grouped with the other project imports
(registerAllTools and McpContext) rather than treated as a separate group, since
it is a local project file. Move the package.json import line to be adjacent
with the other local imports and remove the extra blank line so there is only
one blank line total between the third-party imports and the consolidated
project imports group.
- Around line 28-36: The code in the startStdioServer function is accessing the
internal `.server` property and its `onclose` callback, which is not part of the
public API of McpServer. Replace this private implementation detail access with
either signal handlers (SIGINT and SIGTERM) that call a cleanup/close method, or
directly use the public API methods available on the McpServer instance such as
server.close() to manage the lifecycle. Avoid accessing internal properties like
server.server.onclose and instead rely on the documented public interface of the
McpServer class.
In `@src/mcp/tools/delete-memory.ts`:
- Around line 1-7: The project imports in the delete-memory.ts file are not
sorted in alphabetical order as required by the coding guidelines. Reorder the
three project imports (the ones starting with "`@/`" and "../") so they are
alphabetically sorted by their import paths. The import of McpContext from
"../types" should come first, followed by the import of toolError and toolOk
from "./result", and finally the import of deleteMemory from "`@/core/memory`".
In `@src/mcp/tools/get-note.ts`:
- Around line 1-7: The project imports (from `@/core/memory`, `../types`, and
`./result`) violate the import order guideline by having an empty line between
them on line 5, and they are not sorted alphabetically. Remove the empty line
between the project imports to make them consecutive, and then reorder all three
project import statements so they are sorted in alphabetical order by their
import path. This applies to the imports of getNote, McpContext, and the result
utilities in the file.
In `@src/mcp/tools/result.ts`:
- Around line 8-10: The toolOk function is missing exception handling for
JSON.stringify, which can throw errors when encountering circular references,
BigInt values, or custom toJSON errors. Wrap the JSON.stringify call in a
try-catch block and return a proper tool error result (with isError: true) when
an exception occurs, ensuring that serialization errors are handled as
tool-level errors rather than propagating as MCP protocol errors.
In `@src/mcp/tools/save-memory.ts`:
- Around line 1-9: The import statements violate the project's import ordering
guidelines by including a blank line between project imports. Remove the blank
line (currently between the `@/db` import and the ../types import) to consolidate
all project imports from `@/core/memory`, `@/db`, ../types, ./metadata, and ./result
into a single consecutive group. Then sort these project imports alphabetically
within that group to comply with the coding standards.
In `@src/mcp/tools/search-memory.ts`:
- Around line 1-7: The project imports in the search-memory.ts file are not
sorted in alphabetical order according to the coding guidelines. Reorder the
three project import statements (the imports from "`@/core/memory`", "../types",
and "./result") so they follow alphabetical order. The correct order should be:
import from "../types" first, then "./result", then "`@/core/memory`" last. This
groups and sorts all project imports alphabetically as required by the coding
standards.
In `@src/mcp/tools/status.ts`:
- Around line 1-6: The project internal imports (those starting with "`@/`" and
"../" and "./") are not sorted in alphabetical order according to project
guidelines. Reorder the three project imports in the file to be alphabetically
sorted: the import from "../types" (containing McpContext and StatusResult
types) should come first, followed by the import from "./result" (containing
toolError and toolOk), and finally the import from "`@/db`" (containing
getIndexStats). Group all project imports together after the blank line
following the third-party import, maintaining the blank line between third-party
and project imports.
In `@tests/cli/serve.test.ts`:
- Around line 1-7: The imports in this file violate the import ordering
guideline. Both bun:test and node:fs, node:os, node:path belong to the standard
library group and must be sorted alphabetically. Since 'bun' comes before 'node'
alphabetically, reorder the imports so that the bun:test import appears before
all the node:* imports in the standard library group, while maintaining the
blank line separation between the standard library imports and the local imports
from `@/cli`.
In `@tests/mcp/harness.ts`:
- Around line 1-14: The import statements in the harness.ts file violate
TypeScript coding guidelines by having an unnecessary blank line that splits
third-party library imports into separate groups. Reorganize all imports to
follow the correct grouping: standard library imports (node: and bun: modules)
first, then third-party imports (`@modelcontextprotocol/sdk` and isomorphic-git)
without blank lines between them in alphabetical order, then project imports (@
paths) last. Remove the blank line after the CallToolResult import and the blank
line before the isomorphic-git import, ensuring all third-party imports are
grouped together and sorted alphabetically, with project imports in their own
group below.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: efc9a0f5-8369-4632-9f0a-87a2f8066dae
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock,!bun.lock
📒 Files selected for processing (32)
.tmp/tasks.mddocs/claude-desktop-setup.mdpackage.jsonsrc/cli/commands/serve.tssrc/cli/index.tssrc/cli/main.tssrc/db/index.tssrc/db/repository.tssrc/mcp/index.tssrc/mcp/server.tssrc/mcp/tools/delete-memory.tssrc/mcp/tools/get-note.tssrc/mcp/tools/index.tssrc/mcp/tools/list-recent.tssrc/mcp/tools/metadata.tssrc/mcp/tools/result.tssrc/mcp/tools/save-memory.tssrc/mcp/tools/search-memory.tssrc/mcp/tools/status.tssrc/mcp/tools/update-memory.tssrc/mcp/types.tstests/cli/serve.test.tstests/mcp/delete-memory.test.tstests/mcp/get-note.test.tstests/mcp/harness.tstests/mcp/integration.test.tstests/mcp/list-recent.test.tstests/mcp/save-memory.test.tstests/mcp/search-memory.test.tstests/mcp/server.test.tstests/mcp/status.test.tstests/mcp/update-memory.test.ts
AI Agents prompts に基づく自動修正: - serve.ts: openDatabase/runMigrations をエラーハンドリング配下へ移動し、 DB ハンドルが必ず close されるよう再構成(history/restore と同じパターンで 未オープン db への closeDatabase を回避) - db/repository.ts: getIndexStats の暗黙フォールバック (?? 0) を、行が無い場合の 明示的エラーに置換(フォールバック禁止ポリシー準拠) - docs/claude-desktop-setup.md: fenced code block に言語指定 (text) を追加 スキップ: インポート順の指摘8件(biome の organizeImports 正準順が既に適用済みで 変更すると lint が壊れる)、server.ts の .server アクセス(McpServer.server は 公開 API)、result.ts の JSON.stringify try/catch(呼び出し側 try/catch で既に toolError 化される)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Refs #15
概要
dennoh serveで stdio transport の MCP サーバーを起動し、core/memory にブリッジする7ツールを実装。ツール単体テスト・実プロセスの stdio 統合テスト・Claude Desktop 設定ドキュメントまで揃えました。タスク表 T10.1〜T10.10 を完了。変更内容(3フェーズ)
Phase 1: サーバー基盤 +
dennoh serve@modelcontextprotocol/sdk+zodsrc/mcp/server.ts:createMcpServer(context)(name=dennoh/version=package.json)、startStdioServer(接続が閉じるまで解決せずプロセスを生存)src/cli/commands/serve.ts: config→vault→DB→stdio接続。診断は stderr のみPhase 2: 7ツール
src/mcp/tools/*.ts(個別ファイル): save/update/delete/search/list_recent/get_note/statusisError結果で報告(プロトコル throw しない)db.getIndexStats()を status 用に追加Phase 3: テスト + ドキュメント
tests/mcp/: 共有ハーネス + ツール別単体テスト(正常/異常系)tests/mcp/integration.test.ts: 実dennoh serveプロセスを spawn し initialize ハンドシェイク・複数ツール往復・実 .md 生成・stderr ログ/stdout JSON-RPC専用を検証docs/claude-desktop-setup.md:claude_desktop_config.json設定例・パス指定・動作確認手順統合テストが実バグを検出 🐛
serveが DB を open するだけでrunMigrationsを呼んでおらず、新規 vault でsave_memoryが「no such table: notes」で失敗していたのを修正(冪等な migration を追加)。in-memory テストでは隠れていた問題を実プロセステストが発見。残課題(このPRの範囲外)
Refs #15でリンクのみ(自動クローズせず)。手順は docs に記載latestError(常にnull)/queueDepth(省略)は watcher/エラー追跡サブシステム未配線のためプレースホルダ検証
🤖 Generated with Claude Code
Summary by CodeRabbit
リリースノート
dennoh serveでstdio経由のMCPサーバを起動できるようになりましたstatusでインデックス状況(最新更新状況含む)を確認できるようになりました