Add MCP server for Claude Code memory integration

- Create stdio MCP server wrapping core memory functions (query, show, list, children, add, link)
- Add CLAUDE.md with memory-querying instructions for Claude
- Register MCP server in .mcp.json
- Document MCP setup and tools in USAGE.md
This commit is contained in:
2026-02-02 23:10:38 +01:00
parent c87f97899d
commit d64a80281c
6 changed files with 477 additions and 4 deletions

8
.mcp.json Normal file
View File

@@ -0,0 +1,8 @@
{
"mcpServers": {
"memory": {
"command": "node",
"args": ["./dist/mcp/index.js"]
}
}
}

23
CLAUDE.md Normal file
View File

@@ -0,0 +1,23 @@
# Project Memory Guidelines
## Before Making Changes
- **Always query memory** before modifying code or making architectural decisions: use `memory_query` to search for relevant context.
- Use `memory_show` to check existing context on a specific topic before acting.
- Check for existing decisions, gotchas, and component info that may affect your changes.
## After Making Changes
- **Store new decisions** as memory nodes (kind: `decision`) when architectural choices are made.
- **Store gotchas** as memory nodes (kind: `memory`) when you discover non-obvious behavior.
- **Store component info** (kind: `component`) when creating or significantly modifying a module.
- Tag nodes appropriately for future retrieval.
## Memory Tools (MCP)
- `memory_query` — search memory by text
- `memory_show` — show a node by ID or prefix
- `memory_list` — list nodes filtered by kind/status/tags
- `memory_children` — list children of a node
- `memory_add` — add a new memory node
- `memory_link` — create a relationship between nodes
## Git Preferences
- Do not add `Co-Authored-By` lines to commits.

View File

@@ -260,3 +260,44 @@ The web server exposes these endpoints under `/api`:
| `DELETE` | `/api/edges/:id` | Delete edge | | `DELETE` | `/api/edges/:id` | Delete edge |
| `GET` | `/api/graph` | Get full graph (nodes + edges) for visualization | | `GET` | `/api/graph` | Get full graph (nodes + edges) for visualization |
| `POST` | `/api/search` | Search. Body: `{ text, options?: { kind?, tags?, limit?, includeStale? } }` | | `POST` | `/api/search` | Search. Body: `{ text, options?: { kind?, tags?, limit?, includeStale? } }` |
---
## MCP Server (Claude Code Integration)
Cortex includes an MCP (Model Context Protocol) server that exposes memory tools directly to Claude Code.
### Setup
1. Build the project: `npm run build`
2. Ensure `.mcp.json` exists in the project root:
```json
{
"mcpServers": {
"memory": {
"command": "node",
"args": ["./dist/mcp/index.js"]
}
}
}
```
3. Restart Claude Code — the memory tools will appear automatically.
### MCP Tools
| Tool | Description | Parameters |
|---|---|---|
| `memory_query` | Hybrid search (BM25 + vector + freshness) | `text`, `kind?`, `limit?` |
| `memory_show` | Show node by ID or prefix with connections | `id` |
| `memory_list` | List nodes with filters | `kind?`, `status?`, `tags?`, `limit?` |
| `memory_children` | List children of a node | `id`, `kind?` |
| `memory_add` | Add a new node | `kind`, `title`, `content?`, `tags?`, `status?`, `sections?` |
| `memory_link` | Create an edge between nodes | `fromId`, `toId`, `type` |
### Manual Testing
```bash
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"0.1"}}}' | node dist/mcp/index.js
```

281
package-lock.json generated
View File

@@ -9,15 +9,18 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.25.3",
"better-sqlite3": "^12.6.2", "better-sqlite3": "^12.6.2",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"commander": "^14.0.3", "commander": "^14.0.3",
"cors": "^2.8.6", "cors": "^2.8.6",
"express": "^5.2.1", "express": "^5.2.1",
"uuid": "^13.0.0" "uuid": "^13.0.0",
"zod": "^4.3.6"
}, },
"bin": { "bin": {
"memory": "dist/cli/index.js" "memory": "dist/cli/index.js",
"memory-mcp": "dist/mcp/index.js"
}, },
"devDependencies": { "devDependencies": {
"@types/better-sqlite3": "^7.6.13", "@types/better-sqlite3": "^7.6.13",
@@ -28,6 +31,57 @@
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
}, },
"node_modules/@hono/node-server": {
"version": "1.19.9",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz",
"integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==",
"license": "MIT",
"engines": {
"node": ">=18.14.1"
},
"peerDependencies": {
"hono": "^4"
}
},
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.25.3",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.3.tgz",
"integrity": "sha512-vsAMBMERybvYgKbg/l4L1rhS7VXV1c0CtyJg72vwxONVX0l4ZfKVAnZEWTQixJGTzKnELjQ59e4NbdFDALRiAQ==",
"license": "MIT",
"dependencies": {
"@hono/node-server": "^1.19.9",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"content-type": "^1.0.5",
"cors": "^2.8.5",
"cross-spawn": "^7.0.5",
"eventsource": "^3.0.2",
"eventsource-parser": "^3.0.0",
"express": "^5.0.1",
"express-rate-limit": "^7.5.0",
"jose": "^6.1.1",
"json-schema-typed": "^8.0.2",
"pkce-challenge": "^5.0.0",
"raw-body": "^3.0.0",
"zod": "^3.25 || ^4.0",
"zod-to-json-schema": "^3.25.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@cfworker/json-schema": "^4.1.1",
"zod": "^3.25 || ^4.0"
},
"peerDependenciesMeta": {
"@cfworker/json-schema": {
"optional": true
},
"zod": {
"optional": false
}
}
},
"node_modules/@types/better-sqlite3": { "node_modules/@types/better-sqlite3": {
"version": "7.6.13", "version": "7.6.13",
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
@@ -166,6 +220,39 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-formats": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
"integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
"license": "MIT",
"dependencies": {
"ajv": "^8.0.0"
},
"peerDependencies": {
"ajv": "^8.0.0"
},
"peerDependenciesMeta": {
"ajv": {
"optional": true
}
}
},
"node_modules/ansi-styles": { "node_modules/ansi-styles": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -427,6 +514,20 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.3", "version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -569,6 +670,27 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/eventsource": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
"integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
"license": "MIT",
"dependencies": {
"eventsource-parser": "^3.0.1"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/eventsource-parser": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz",
"integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/expand-template": { "node_modules/expand-template": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
@@ -621,6 +743,43 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/express-rate-limit": {
"version": "7.5.1",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz",
"integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==",
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/express-rate-limit"
},
"peerDependencies": {
"express": ">= 4.11"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
"node_modules/fast-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "BSD-3-Clause"
},
"node_modules/file-uri-to-path": { "node_modules/file-uri-to-path": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
@@ -769,6 +928,16 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/hono": {
"version": "4.11.7",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz",
"integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=16.9.0"
}
},
"node_modules/http-errors": { "node_modules/http-errors": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
@@ -852,6 +1021,33 @@
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/jose": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz",
"integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT"
},
"node_modules/json-schema-typed": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
"integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
"license": "BSD-2-Clause"
},
"node_modules/math-intrinsics": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -1018,6 +1214,15 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "8.3.0", "version": "8.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
@@ -1028,6 +1233,15 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/pkce-challenge": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
"integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==",
"license": "MIT",
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/prebuild-install": { "node_modules/prebuild-install": {
"version": "7.1.3", "version": "7.1.3",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
@@ -1145,6 +1359,15 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/router": { "node_modules/router": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
@@ -1250,6 +1473,27 @@
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/side-channel": { "node_modules/side-channel": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
@@ -1527,11 +1771,44 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/wrappy": { "node_modules/wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC" "license": "ISC"
},
"node_modules/zod": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zod-to-json-schema": {
"version": "3.25.1",
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz",
"integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==",
"license": "ISC",
"peerDependencies": {
"zod": "^3.25 || ^4"
}
} }
} }
} }

View File

@@ -12,15 +12,18 @@
"license": "ISC", "license": "ISC",
"description": "AI project memory - knowledge graph backed by SQLite + Ollama embeddings", "description": "AI project memory - knowledge graph backed by SQLite + Ollama embeddings",
"bin": { "bin": {
"memory": "./dist/cli/index.js" "memory": "./dist/cli/index.js",
"memory-mcp": "./dist/mcp/index.js"
}, },
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.25.3",
"better-sqlite3": "^12.6.2", "better-sqlite3": "^12.6.2",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"commander": "^14.0.3", "commander": "^14.0.3",
"cors": "^2.8.6", "cors": "^2.8.6",
"express": "^5.2.1", "express": "^5.2.1",
"uuid": "^13.0.0" "uuid": "^13.0.0",
"zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@types/better-sqlite3": "^7.6.13", "@types/better-sqlite3": "^7.6.13",

121
src/mcp/index.ts Normal file
View File

@@ -0,0 +1,121 @@
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod/v3';
import { query, listNodes, getNode, findNodeByPrefix, addNode, addEdge } from '../core/store';
import { getConnections } from '../core/graph';
import { NodeKind, EdgeType } from '../types';
const server = new McpServer({
name: 'memory',
version: '1.0.0',
});
server.tool(
'memory_query',
'Search memory nodes by text (hybrid BM25 + vector search)',
{
text: z.string().describe('Search query text'),
kind: z.enum(['memory', 'component', 'task', 'decision']).optional().describe('Filter by node kind'),
limit: z.number().optional().describe('Max results (default 10)'),
},
async ({ text, kind, limit }) => {
const results = await query(text, { kind: kind as NodeKind, limit });
return { content: [{ type: 'text' as const, text: JSON.stringify(results, null, 2) }] };
}
);
server.tool(
'memory_show',
'Show a memory node by ID or ID prefix',
{
id: z.string().describe('Full node ID or prefix'),
},
async ({ id }) => {
const node = getNode(id) ?? findNodeByPrefix(id);
if (!node) {
return { content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Node not found' }) }], isError: true };
}
const connections = getConnections(node.id);
return { content: [{ type: 'text' as const, text: JSON.stringify({ node, connections }, null, 2) }] };
}
);
server.tool(
'memory_list',
'List memory nodes with optional filters',
{
kind: z.enum(['memory', 'component', 'task', 'decision']).optional().describe('Filter by node kind'),
status: z.string().optional().describe('Filter by status'),
tags: z.array(z.string()).optional().describe('Filter by tags'),
limit: z.number().optional().describe('Max results'),
},
async ({ kind, status, tags, limit }) => {
const nodes = listNodes({ kind: kind as NodeKind, status, tags, limit });
return { content: [{ type: 'text' as const, text: JSON.stringify(nodes, null, 2) }] };
}
);
server.tool(
'memory_children',
'List children of a memory node (outgoing "contains" edges)',
{
id: z.string().describe('Parent node ID or prefix'),
kind: z.enum(['memory', 'component', 'task', 'decision']).optional().describe('Filter children by kind'),
},
async ({ id, kind }) => {
const node = getNode(id) ?? findNodeByPrefix(id);
if (!node) {
return { content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Node not found' }) }], isError: true };
}
const { outgoing } = getConnections(node.id);
let children = outgoing.filter(e => e.type === 'contains').map(e => (e as any).node);
if (kind) {
children = children.filter((n: any) => n.kind === kind);
}
return { content: [{ type: 'text' as const, text: JSON.stringify(children, null, 2) }] };
}
);
server.tool(
'memory_add',
'Add a new memory node',
{
kind: z.enum(['memory', 'component', 'task', 'decision']).describe('Node kind'),
title: z.string().describe('Node title'),
content: z.string().optional().describe('Node content/body'),
tags: z.array(z.string()).optional().describe('Tags'),
status: z.string().optional().describe('Status (e.g. active, todo, done)'),
sections: z.array(z.object({ label: z.string(), body: z.string() })).optional().describe('Structured sections'),
},
async ({ kind, title, content, tags, status, sections }) => {
const metadata: Record<string, any> = {};
if (sections) metadata.sections = sections;
const node = await addNode({ kind: kind as NodeKind, title, content, tags, status, metadata });
return { content: [{ type: 'text' as const, text: JSON.stringify(node, null, 2) }] };
}
);
server.tool(
'memory_link',
'Create an edge between two memory nodes',
{
fromId: z.string().describe('Source node ID'),
toId: z.string().describe('Target node ID'),
type: z.enum(['depends_on', 'contains', 'implements', 'blocked_by', 'subtask_of', 'relates_to', 'supersedes', 'about']).describe('Edge type'),
},
async ({ fromId, toId, type }) => {
const edge = addEdge(fromId, toId, type as EdgeType);
return { content: [{ type: 'text' as const, text: JSON.stringify(edge, null, 2) }] };
}
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Memory MCP server running on stdio');
}
main().catch((err) => {
console.error('Fatal error:', err);
process.exit(1);
});