@vheins/local-memory-mcp 0.5.32 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -6
- package/dist/chunk-XIJO63UU.js +3618 -0
- package/dist/dashboard/public/assets/{index-C8FB4maW.css → index-Bd7v94SO.css} +1 -1
- package/dist/dashboard/public/assets/index-Df97JpLg.js +84 -0
- package/dist/dashboard/public/index.html +2 -2
- package/dist/dashboard/server.js +790 -451
- package/dist/mcp/server.js +2121 -305
- package/dist/{mcp/prompts/definitions → prompts}/create-task.md +4 -3
- package/dist/{mcp/prompts/definitions → prompts}/memory-agent-core.md +2 -0
- package/dist/{mcp/prompts/definitions → prompts}/memory-index-policy.md +2 -0
- package/dist/{mcp/prompts/definitions → prompts}/review-and-audit.md +3 -1
- package/dist/{mcp/prompts/definitions → prompts}/review-and-post-issue.md +3 -1
- package/dist/prompts/task-management-guidelines.md +28 -0
- package/dist/{mcp/prompts/definitions → prompts}/task-memory-executor.md +6 -5
- package/package.json +22 -6
- package/dist/capabilities.d.ts +0 -22
- package/dist/capabilities.d.ts.map +0 -1
- package/dist/capabilities.js +0 -38
- package/dist/capabilities.js.map +0 -1
- package/dist/completion.d.ts +0 -25
- package/dist/completion.d.ts.map +0 -1
- package/dist/completion.js +0 -127
- package/dist/completion.js.map +0 -1
- package/dist/dashboard/dashboard.test.d.ts +0 -2
- package/dist/dashboard/dashboard.test.d.ts.map +0 -1
- package/dist/dashboard/dashboard.test.js +0 -370
- package/dist/dashboard/dashboard.test.js.map +0 -1
- package/dist/dashboard/public/assets/index-DIhCu9qA.js +0 -78
- package/dist/dashboard/server.d.ts +0 -3
- package/dist/dashboard/server.d.ts.map +0 -1
- package/dist/dashboard/server.js.map +0 -1
- package/dist/e2e.test.d.ts +0 -2
- package/dist/e2e.test.d.ts.map +0 -1
- package/dist/e2e.test.js +0 -250
- package/dist/e2e.test.js.map +0 -1
- package/dist/mcp/capabilities.d.ts +0 -22
- package/dist/mcp/capabilities.d.ts.map +0 -1
- package/dist/mcp/capabilities.js +0 -38
- package/dist/mcp/capabilities.js.map +0 -1
- package/dist/mcp/client.d.ts +0 -34
- package/dist/mcp/client.d.ts.map +0 -1
- package/dist/mcp/client.js +0 -188
- package/dist/mcp/client.js.map +0 -1
- package/dist/mcp/client.test.d.ts +0 -2
- package/dist/mcp/client.test.d.ts.map +0 -1
- package/dist/mcp/client.test.js +0 -130
- package/dist/mcp/client.test.js.map +0 -1
- package/dist/mcp/completion.d.ts +0 -25
- package/dist/mcp/completion.d.ts.map +0 -1
- package/dist/mcp/completion.js +0 -127
- package/dist/mcp/completion.js.map +0 -1
- package/dist/mcp/elicitation.d.ts +0 -24
- package/dist/mcp/elicitation.d.ts.map +0 -1
- package/dist/mcp/elicitation.js +0 -13
- package/dist/mcp/elicitation.js.map +0 -1
- package/dist/mcp/prompts/definitions/task-management-guidelines.md +0 -30
- package/dist/mcp/prompts/loader.d.ts +0 -10
- package/dist/mcp/prompts/loader.d.ts.map +0 -1
- package/dist/mcp/prompts/loader.js +0 -31
- package/dist/mcp/prompts/loader.js.map +0 -1
- package/dist/mcp/prompts/registry.d.ts +0 -35
- package/dist/mcp/prompts/registry.d.ts.map +0 -1
- package/dist/mcp/prompts/registry.js +0 -95
- package/dist/mcp/prompts/registry.js.map +0 -1
- package/dist/mcp/resources/index.d.ts +0 -68
- package/dist/mcp/resources/index.d.ts.map +0 -1
- package/dist/mcp/resources/index.js +0 -359
- package/dist/mcp/resources/index.js.map +0 -1
- package/dist/mcp/router.d.ts +0 -14
- package/dist/mcp/router.d.ts.map +0 -1
- package/dist/mcp/router.js +0 -255
- package/dist/mcp/router.js.map +0 -1
- package/dist/mcp/sampling.d.ts +0 -69
- package/dist/mcp/sampling.d.ts.map +0 -1
- package/dist/mcp/sampling.js +0 -13
- package/dist/mcp/sampling.js.map +0 -1
- package/dist/mcp/server.d.ts +0 -3
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/mcp/server.js.map +0 -1
- package/dist/mcp/session.d.ts +0 -28
- package/dist/mcp/session.d.ts.map +0 -1
- package/dist/mcp/session.js +0 -106
- package/dist/mcp/session.js.map +0 -1
- package/dist/mcp/storage/sqlite.d.ts +0 -87
- package/dist/mcp/storage/sqlite.d.ts.map +0 -1
- package/dist/mcp/storage/sqlite.js +0 -1327
- package/dist/mcp/storage/sqlite.js.map +0 -1
- package/dist/mcp/storage/vectors.d.ts +0 -19
- package/dist/mcp/storage/vectors.d.ts.map +0 -1
- package/dist/mcp/storage/vectors.js +0 -74
- package/dist/mcp/storage/vectors.js.map +0 -1
- package/dist/mcp/storage/vectors.stub.d.ts +0 -12
- package/dist/mcp/storage/vectors.stub.d.ts.map +0 -1
- package/dist/mcp/storage/vectors.stub.js +0 -88
- package/dist/mcp/storage/vectors.stub.js.map +0 -1
- package/dist/mcp/tests/client.test.d.ts +0 -2
- package/dist/mcp/tests/client.test.d.ts.map +0 -1
- package/dist/mcp/tests/client.test.js +0 -130
- package/dist/mcp/tests/client.test.js.map +0 -1
- package/dist/mcp/tests/dashboard.test.d.ts +0 -2
- package/dist/mcp/tests/dashboard.test.d.ts.map +0 -1
- package/dist/mcp/tests/dashboard.test.js +0 -370
- package/dist/mcp/tests/dashboard.test.js.map +0 -1
- package/dist/mcp/tests/detail-tools.test.d.ts +0 -2
- package/dist/mcp/tests/detail-tools.test.d.ts.map +0 -1
- package/dist/mcp/tests/detail-tools.test.js +0 -109
- package/dist/mcp/tests/detail-tools.test.js.map +0 -1
- package/dist/mcp/tests/e2e.test.d.ts +0 -2
- package/dist/mcp/tests/e2e.test.d.ts.map +0 -1
- package/dist/mcp/tests/e2e.test.js +0 -255
- package/dist/mcp/tests/e2e.test.js.map +0 -1
- package/dist/mcp/tests/index.test.d.ts +0 -2
- package/dist/mcp/tests/index.test.d.ts.map +0 -1
- package/dist/mcp/tests/index.test.js +0 -185
- package/dist/mcp/tests/index.test.js.map +0 -1
- package/dist/mcp/tests/logger.test.d.ts +0 -2
- package/dist/mcp/tests/logger.test.d.ts.map +0 -1
- package/dist/mcp/tests/logger.test.js +0 -104
- package/dist/mcp/tests/logger.test.js.map +0 -1
- package/dist/mcp/tests/memory.bulk.test.d.ts +0 -2
- package/dist/mcp/tests/memory.bulk.test.d.ts.map +0 -1
- package/dist/mcp/tests/memory.bulk.test.js +0 -53
- package/dist/mcp/tests/memory.bulk.test.js.map +0 -1
- package/dist/mcp/tests/memory.search.test.d.ts +0 -2
- package/dist/mcp/tests/memory.search.test.d.ts.map +0 -1
- package/dist/mcp/tests/memory.search.test.js +0 -181
- package/dist/mcp/tests/memory.search.test.js.map +0 -1
- package/dist/mcp/tests/normalize.test.d.ts +0 -2
- package/dist/mcp/tests/normalize.test.d.ts.map +0 -1
- package/dist/mcp/tests/normalize.test.js +0 -181
- package/dist/mcp/tests/normalize.test.js.map +0 -1
- package/dist/mcp/tests/query-expander.test.d.ts +0 -2
- package/dist/mcp/tests/query-expander.test.d.ts.map +0 -1
- package/dist/mcp/tests/query-expander.test.js +0 -33
- package/dist/mcp/tests/query-expander.test.js.map +0 -1
- package/dist/mcp/tests/router.test.d.ts +0 -2
- package/dist/mcp/tests/router.test.d.ts.map +0 -1
- package/dist/mcp/tests/router.test.js +0 -481
- package/dist/mcp/tests/router.test.js.map +0 -1
- package/dist/mcp/tests/spec_compliance.test.d.ts +0 -2
- package/dist/mcp/tests/spec_compliance.test.d.ts.map +0 -1
- package/dist/mcp/tests/spec_compliance.test.js +0 -61
- package/dist/mcp/tests/spec_compliance.test.js.map +0 -1
- package/dist/mcp/tests/sqlite.test.d.ts +0 -2
- package/dist/mcp/tests/sqlite.test.d.ts.map +0 -1
- package/dist/mcp/tests/sqlite.test.js +0 -367
- package/dist/mcp/tests/sqlite.test.js.map +0 -1
- package/dist/mcp/tests/tasks-search.test.d.ts +0 -2
- package/dist/mcp/tests/tasks-search.test.d.ts.map +0 -1
- package/dist/mcp/tests/tasks-search.test.js +0 -156
- package/dist/mcp/tests/tasks-search.test.js.map +0 -1
- package/dist/mcp/tests/tasks-transition.test.d.ts +0 -2
- package/dist/mcp/tests/tasks-transition.test.d.ts.map +0 -1
- package/dist/mcp/tests/tasks-transition.test.js +0 -174
- package/dist/mcp/tests/tasks-transition.test.js.map +0 -1
- package/dist/mcp/tests/tasks.bulk.test.d.ts +0 -2
- package/dist/mcp/tests/tasks.bulk.test.d.ts.map +0 -1
- package/dist/mcp/tests/tasks.bulk.test.js +0 -410
- package/dist/mcp/tests/tasks.bulk.test.js.map +0 -1
- package/dist/mcp/tests/tasks.e2e.test.d.ts +0 -2
- package/dist/mcp/tests/tasks.e2e.test.d.ts.map +0 -1
- package/dist/mcp/tests/tasks.e2e.test.js +0 -289
- package/dist/mcp/tests/tasks.e2e.test.js.map +0 -1
- package/dist/mcp/tests/tasks.pending-limit-refined.test.d.ts +0 -2
- package/dist/mcp/tests/tasks.pending-limit-refined.test.d.ts.map +0 -1
- package/dist/mcp/tests/tasks.pending-limit-refined.test.js +0 -72
- package/dist/mcp/tests/tasks.pending-limit-refined.test.js.map +0 -1
- package/dist/mcp/tests/v2-features.test.d.ts +0 -2
- package/dist/mcp/tests/v2-features.test.d.ts.map +0 -1
- package/dist/mcp/tests/v2-features.test.js +0 -209
- package/dist/mcp/tests/v2-features.test.js.map +0 -1
- package/dist/mcp/tools/memory.acknowledge.d.ts +0 -4
- package/dist/mcp/tools/memory.acknowledge.d.ts.map +0 -1
- package/dist/mcp/tools/memory.acknowledge.js +0 -32
- package/dist/mcp/tools/memory.acknowledge.js.map +0 -1
- package/dist/mcp/tools/memory.bulk-delete.d.ts +0 -4
- package/dist/mcp/tools/memory.bulk-delete.d.ts.map +0 -1
- package/dist/mcp/tools/memory.bulk-delete.js +0 -40
- package/dist/mcp/tools/memory.bulk-delete.js.map +0 -1
- package/dist/mcp/tools/memory.delete.d.ts +0 -9
- package/dist/mcp/tools/memory.delete.d.ts.map +0 -1
- package/dist/mcp/tools/memory.delete.js +0 -40
- package/dist/mcp/tools/memory.delete.js.map +0 -1
- package/dist/mcp/tools/memory.get.d.ts +0 -3
- package/dist/mcp/tools/memory.get.d.ts.map +0 -1
- package/dist/mcp/tools/memory.get.js +0 -24
- package/dist/mcp/tools/memory.get.js.map +0 -1
- package/dist/mcp/tools/memory.recap.d.ts +0 -4
- package/dist/mcp/tools/memory.recap.d.ts.map +0 -1
- package/dist/mcp/tools/memory.recap.js +0 -52
- package/dist/mcp/tools/memory.recap.js.map +0 -1
- package/dist/mcp/tools/memory.search.d.ts +0 -5
- package/dist/mcp/tools/memory.search.d.ts.map +0 -1
- package/dist/mcp/tools/memory.search.js +0 -144
- package/dist/mcp/tools/memory.search.js.map +0 -1
- package/dist/mcp/tools/memory.store.d.ts +0 -5
- package/dist/mcp/tools/memory.store.d.ts.map +0 -1
- package/dist/mcp/tools/memory.store.js +0 -120
- package/dist/mcp/tools/memory.store.js.map +0 -1
- package/dist/mcp/tools/memory.summarize.d.ts +0 -4
- package/dist/mcp/tools/memory.summarize.d.ts.map +0 -1
- package/dist/mcp/tools/memory.summarize.js +0 -32
- package/dist/mcp/tools/memory.summarize.js.map +0 -1
- package/dist/mcp/tools/memory.synthesize.d.ts +0 -14
- package/dist/mcp/tools/memory.synthesize.d.ts.map +0 -1
- package/dist/mcp/tools/memory.synthesize.js +0 -230
- package/dist/mcp/tools/memory.synthesize.js.map +0 -1
- package/dist/mcp/tools/memory.update.d.ts +0 -5
- package/dist/mcp/tools/memory.update.d.ts.map +0 -1
- package/dist/mcp/tools/memory.update.js +0 -74
- package/dist/mcp/tools/memory.update.js.map +0 -1
- package/dist/mcp/tools/schemas.d.ts +0 -3177
- package/dist/mcp/tools/schemas.d.ts.map +0 -1
- package/dist/mcp/tools/schemas.js +0 -1081
- package/dist/mcp/tools/schemas.js.map +0 -1
- package/dist/mcp/tools/task.bulk-manage.d.ts +0 -4
- package/dist/mcp/tools/task.bulk-manage.d.ts.map +0 -1
- package/dist/mcp/tools/task.bulk-manage.js +0 -190
- package/dist/mcp/tools/task.bulk-manage.js.map +0 -1
- package/dist/mcp/tools/task.get.d.ts +0 -3
- package/dist/mcp/tools/task.get.d.ts.map +0 -1
- package/dist/mcp/tools/task.get.js +0 -32
- package/dist/mcp/tools/task.get.js.map +0 -1
- package/dist/mcp/tools/task.manage.d.ts +0 -18
- package/dist/mcp/tools/task.manage.d.ts.map +0 -1
- package/dist/mcp/tools/task.manage.js +0 -477
- package/dist/mcp/tools/task.manage.js.map +0 -1
- package/dist/mcp/types.d.ts +0 -87
- package/dist/mcp/types.d.ts.map +0 -1
- package/dist/mcp/types.js +0 -3
- package/dist/mcp/types.js.map +0 -1
- package/dist/mcp/utils/completion.d.ts +0 -2
- package/dist/mcp/utils/completion.d.ts.map +0 -1
- package/dist/mcp/utils/completion.js +0 -28
- package/dist/mcp/utils/completion.js.map +0 -1
- package/dist/mcp/utils/git-scope.d.ts +0 -8
- package/dist/mcp/utils/git-scope.d.ts.map +0 -1
- package/dist/mcp/utils/git-scope.js +0 -38
- package/dist/mcp/utils/git-scope.js.map +0 -1
- package/dist/mcp/utils/logger.d.ts +0 -25
- package/dist/mcp/utils/logger.d.ts.map +0 -1
- package/dist/mcp/utils/logger.js +0 -152
- package/dist/mcp/utils/logger.js.map +0 -1
- package/dist/mcp/utils/mcp-response.d.ts +0 -82
- package/dist/mcp/utils/mcp-response.d.ts.map +0 -1
- package/dist/mcp/utils/mcp-response.js +0 -182
- package/dist/mcp/utils/mcp-response.js.map +0 -1
- package/dist/mcp/utils/normalize.d.ts +0 -9
- package/dist/mcp/utils/normalize.d.ts.map +0 -1
- package/dist/mcp/utils/normalize.js +0 -62
- package/dist/mcp/utils/normalize.js.map +0 -1
- package/dist/mcp/utils/pagination.d.ts +0 -6
- package/dist/mcp/utils/pagination.d.ts.map +0 -1
- package/dist/mcp/utils/pagination.js +0 -32
- package/dist/mcp/utils/pagination.js.map +0 -1
- package/dist/mcp/utils/query-expander.d.ts +0 -2
- package/dist/mcp/utils/query-expander.d.ts.map +0 -1
- package/dist/mcp/utils/query-expander.js +0 -29
- package/dist/mcp/utils/query-expander.js.map +0 -1
- package/dist/memory.bulk.test.d.ts +0 -2
- package/dist/memory.bulk.test.d.ts.map +0 -1
- package/dist/memory.bulk.test.js +0 -52
- package/dist/memory.bulk.test.js.map +0 -1
- package/dist/memory.db +0 -0
- package/dist/prompts/registry.d.ts +0 -314
- package/dist/prompts/registry.d.ts.map +0 -1
- package/dist/prompts/registry.js +0 -936
- package/dist/prompts/registry.js.map +0 -1
- package/dist/resources/index.d.ts +0 -68
- package/dist/resources/index.d.ts.map +0 -1
- package/dist/resources/index.js +0 -323
- package/dist/resources/index.js.map +0 -1
- package/dist/resources/index.test.d.ts +0 -2
- package/dist/resources/index.test.d.ts.map +0 -1
- package/dist/resources/index.test.js +0 -105
- package/dist/resources/index.test.js.map +0 -1
- package/dist/router.d.ts +0 -14
- package/dist/router.d.ts.map +0 -1
- package/dist/router.js +0 -242
- package/dist/router.js.map +0 -1
- package/dist/router.test.d.ts +0 -2
- package/dist/router.test.d.ts.map +0 -1
- package/dist/router.test.js +0 -122
- package/dist/router.test.js.map +0 -1
- package/dist/server.d.ts +0 -3
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js +0 -338
- package/dist/server.js.map +0 -1
- package/dist/storage/sqlite.d.ts +0 -79
- package/dist/storage/sqlite.d.ts.map +0 -1
- package/dist/storage/sqlite.js +0 -1148
- package/dist/storage/sqlite.js.map +0 -1
- package/dist/storage/sqlite.test.d.ts +0 -2
- package/dist/storage/sqlite.test.d.ts.map +0 -1
- package/dist/storage/sqlite.test.js +0 -367
- package/dist/storage/sqlite.test.js.map +0 -1
- package/dist/storage/vectors.d.ts +0 -19
- package/dist/storage/vectors.d.ts.map +0 -1
- package/dist/storage/vectors.js +0 -74
- package/dist/storage/vectors.js.map +0 -1
- package/dist/storage/vectors.stub.d.ts +0 -12
- package/dist/storage/vectors.stub.d.ts.map +0 -1
- package/dist/storage/vectors.stub.js +0 -88
- package/dist/storage/vectors.stub.js.map +0 -1
- package/dist/tasks.archive.test.d.ts +0 -2
- package/dist/tasks.archive.test.d.ts.map +0 -1
- package/dist/tasks.archive.test.js +0 -75
- package/dist/tasks.archive.test.js.map +0 -1
- package/dist/tasks.bulk.test.d.ts +0 -2
- package/dist/tasks.bulk.test.d.ts.map +0 -1
- package/dist/tasks.bulk.test.js +0 -250
- package/dist/tasks.bulk.test.js.map +0 -1
- package/dist/tasks.e2e.test.d.ts +0 -2
- package/dist/tasks.e2e.test.d.ts.map +0 -1
- package/dist/tasks.e2e.test.js +0 -289
- package/dist/tasks.e2e.test.js.map +0 -1
- package/dist/tests/client.test.d.ts +0 -2
- package/dist/tests/client.test.d.ts.map +0 -1
- package/dist/tests/client.test.js +0 -130
- package/dist/tests/client.test.js.map +0 -1
- package/dist/tests/dashboard.test.d.ts +0 -2
- package/dist/tests/dashboard.test.d.ts.map +0 -1
- package/dist/tests/dashboard.test.js +0 -370
- package/dist/tests/dashboard.test.js.map +0 -1
- package/dist/tests/e2e.test.d.ts +0 -2
- package/dist/tests/e2e.test.d.ts.map +0 -1
- package/dist/tests/e2e.test.js +0 -250
- package/dist/tests/e2e.test.js.map +0 -1
- package/dist/tests/index.test.d.ts +0 -2
- package/dist/tests/index.test.d.ts.map +0 -1
- package/dist/tests/index.test.js +0 -185
- package/dist/tests/index.test.js.map +0 -1
- package/dist/tests/logger.test.d.ts +0 -2
- package/dist/tests/logger.test.d.ts.map +0 -1
- package/dist/tests/logger.test.js +0 -104
- package/dist/tests/logger.test.js.map +0 -1
- package/dist/tests/memory.bulk.test.d.ts +0 -2
- package/dist/tests/memory.bulk.test.d.ts.map +0 -1
- package/dist/tests/memory.bulk.test.js +0 -52
- package/dist/tests/memory.bulk.test.js.map +0 -1
- package/dist/tests/memory.search.test.d.ts +0 -2
- package/dist/tests/memory.search.test.d.ts.map +0 -1
- package/dist/tests/memory.search.test.js +0 -181
- package/dist/tests/memory.search.test.js.map +0 -1
- package/dist/tests/normalize.test.d.ts +0 -2
- package/dist/tests/normalize.test.d.ts.map +0 -1
- package/dist/tests/normalize.test.js +0 -181
- package/dist/tests/normalize.test.js.map +0 -1
- package/dist/tests/query-expander.test.d.ts +0 -2
- package/dist/tests/query-expander.test.d.ts.map +0 -1
- package/dist/tests/query-expander.test.js +0 -33
- package/dist/tests/query-expander.test.js.map +0 -1
- package/dist/tests/router.test.d.ts +0 -2
- package/dist/tests/router.test.d.ts.map +0 -1
- package/dist/tests/router.test.js +0 -470
- package/dist/tests/router.test.js.map +0 -1
- package/dist/tests/sqlite.test.d.ts +0 -2
- package/dist/tests/sqlite.test.d.ts.map +0 -1
- package/dist/tests/sqlite.test.js +0 -367
- package/dist/tests/sqlite.test.js.map +0 -1
- package/dist/tests/tasks-search.test.d.ts +0 -2
- package/dist/tests/tasks-search.test.d.ts.map +0 -1
- package/dist/tests/tasks-search.test.js +0 -154
- package/dist/tests/tasks-search.test.js.map +0 -1
- package/dist/tests/tasks-transition.test.d.ts +0 -2
- package/dist/tests/tasks-transition.test.d.ts.map +0 -1
- package/dist/tests/tasks-transition.test.js +0 -174
- package/dist/tests/tasks-transition.test.js.map +0 -1
- package/dist/tests/tasks.bulk.test.d.ts +0 -2
- package/dist/tests/tasks.bulk.test.d.ts.map +0 -1
- package/dist/tests/tasks.bulk.test.js +0 -254
- package/dist/tests/tasks.bulk.test.js.map +0 -1
- package/dist/tests/tasks.e2e.test.d.ts +0 -2
- package/dist/tests/tasks.e2e.test.d.ts.map +0 -1
- package/dist/tests/tasks.e2e.test.js +0 -289
- package/dist/tests/tasks.e2e.test.js.map +0 -1
- package/dist/tests/tasks.pending-limit-refined.test.d.ts +0 -2
- package/dist/tests/tasks.pending-limit-refined.test.d.ts.map +0 -1
- package/dist/tests/tasks.pending-limit-refined.test.js +0 -72
- package/dist/tests/tasks.pending-limit-refined.test.js.map +0 -1
- package/dist/tests/v2-features.test.d.ts +0 -2
- package/dist/tests/v2-features.test.d.ts.map +0 -1
- package/dist/tests/v2-features.test.js +0 -209
- package/dist/tests/v2-features.test.js.map +0 -1
- package/dist/tools/memory.acknowledge.d.ts +0 -4
- package/dist/tools/memory.acknowledge.d.ts.map +0 -1
- package/dist/tools/memory.acknowledge.js +0 -30
- package/dist/tools/memory.acknowledge.js.map +0 -1
- package/dist/tools/memory.bulk-delete.d.ts +0 -4
- package/dist/tools/memory.bulk-delete.d.ts.map +0 -1
- package/dist/tools/memory.bulk-delete.js +0 -39
- package/dist/tools/memory.bulk-delete.js.map +0 -1
- package/dist/tools/memory.delete.d.ts +0 -9
- package/dist/tools/memory.delete.d.ts.map +0 -1
- package/dist/tools/memory.delete.js +0 -39
- package/dist/tools/memory.delete.js.map +0 -1
- package/dist/tools/memory.recap.d.ts +0 -4
- package/dist/tools/memory.recap.d.ts.map +0 -1
- package/dist/tools/memory.recap.js +0 -90
- package/dist/tools/memory.recap.js.map +0 -1
- package/dist/tools/memory.search.d.ts +0 -5
- package/dist/tools/memory.search.d.ts.map +0 -1
- package/dist/tools/memory.search.js +0 -134
- package/dist/tools/memory.search.js.map +0 -1
- package/dist/tools/memory.search.test.d.ts +0 -2
- package/dist/tools/memory.search.test.d.ts.map +0 -1
- package/dist/tools/memory.search.test.js +0 -181
- package/dist/tools/memory.search.test.js.map +0 -1
- package/dist/tools/memory.store.d.ts +0 -5
- package/dist/tools/memory.store.d.ts.map +0 -1
- package/dist/tools/memory.store.js +0 -117
- package/dist/tools/memory.store.js.map +0 -1
- package/dist/tools/memory.summarize.d.ts +0 -4
- package/dist/tools/memory.summarize.d.ts.map +0 -1
- package/dist/tools/memory.summarize.js +0 -31
- package/dist/tools/memory.summarize.js.map +0 -1
- package/dist/tools/memory.synthesize.d.ts +0 -14
- package/dist/tools/memory.synthesize.d.ts.map +0 -1
- package/dist/tools/memory.synthesize.js +0 -228
- package/dist/tools/memory.synthesize.js.map +0 -1
- package/dist/tools/memory.update.d.ts +0 -5
- package/dist/tools/memory.update.d.ts.map +0 -1
- package/dist/tools/memory.update.js +0 -73
- package/dist/tools/memory.update.js.map +0 -1
- package/dist/tools/schemas.d.ts +0 -2762
- package/dist/tools/schemas.d.ts.map +0 -1
- package/dist/tools/schemas.js +0 -952
- package/dist/tools/schemas.js.map +0 -1
- package/dist/tools/task.bulk-manage.d.ts +0 -4
- package/dist/tools/task.bulk-manage.d.ts.map +0 -1
- package/dist/tools/task.bulk-manage.js +0 -146
- package/dist/tools/task.bulk-manage.js.map +0 -1
- package/dist/tools/task.manage.d.ts +0 -16
- package/dist/tools/task.manage.d.ts.map +0 -1
- package/dist/tools/task.manage.js +0 -395
- package/dist/tools/task.manage.js.map +0 -1
- package/dist/tools/tasks-transition.test.d.ts +0 -2
- package/dist/tools/tasks-transition.test.d.ts.map +0 -1
- package/dist/tools/tasks-transition.test.js +0 -174
- package/dist/tools/tasks-transition.test.js.map +0 -1
- package/dist/tools/tasks.pending-limit-refined.test.d.ts +0 -2
- package/dist/tools/tasks.pending-limit-refined.test.d.ts.map +0 -1
- package/dist/tools/tasks.pending-limit-refined.test.js +0 -72
- package/dist/tools/tasks.pending-limit-refined.test.js.map +0 -1
- package/dist/types.d.ts +0 -87
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- package/dist/utils/completion.d.ts +0 -2
- package/dist/utils/completion.d.ts.map +0 -1
- package/dist/utils/completion.js +0 -28
- package/dist/utils/completion.js.map +0 -1
- package/dist/utils/git-scope.d.ts +0 -8
- package/dist/utils/git-scope.d.ts.map +0 -1
- package/dist/utils/git-scope.js +0 -38
- package/dist/utils/git-scope.js.map +0 -1
- package/dist/utils/logger.d.ts +0 -25
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js +0 -152
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/logger.test.d.ts +0 -2
- package/dist/utils/logger.test.d.ts.map +0 -1
- package/dist/utils/logger.test.js +0 -76
- package/dist/utils/logger.test.js.map +0 -1
- package/dist/utils/mcp-response.d.ts +0 -96
- package/dist/utils/mcp-response.d.ts.map +0 -1
- package/dist/utils/mcp-response.js +0 -131
- package/dist/utils/mcp-response.js.map +0 -1
- package/dist/utils/normalize.d.ts +0 -9
- package/dist/utils/normalize.d.ts.map +0 -1
- package/dist/utils/normalize.js +0 -62
- package/dist/utils/normalize.js.map +0 -1
- package/dist/utils/normalize.test.d.ts +0 -2
- package/dist/utils/normalize.test.d.ts.map +0 -1
- package/dist/utils/normalize.test.js +0 -159
- package/dist/utils/normalize.test.js.map +0 -1
- package/dist/utils/pagination.d.ts +0 -6
- package/dist/utils/pagination.d.ts.map +0 -1
- package/dist/utils/pagination.js +0 -32
- package/dist/utils/pagination.js.map +0 -1
- package/dist/utils/query-expander.d.ts +0 -2
- package/dist/utils/query-expander.d.ts.map +0 -1
- package/dist/utils/query-expander.js +0 -29
- package/dist/utils/query-expander.js.map +0 -1
- package/dist/utils/query-expander.test.d.ts +0 -2
- package/dist/utils/query-expander.test.d.ts.map +0 -1
- package/dist/utils/query-expander.test.js +0 -33
- package/dist/utils/query-expander.test.js.map +0 -1
- package/dist/v2-features.test.d.ts +0 -2
- package/dist/v2-features.test.d.ts.map +0 -1
- package/dist/v2-features.test.js +0 -209
- package/dist/v2-features.test.js.map +0 -1
- /package/dist/{mcp/prompts/definitions → prompts}/architecture-design.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/documentation-sync.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/fix-suggestion.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/import-github-issues.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/learning-retrospective.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/memory-guided-review.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/project-briefing.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/root-cause-analysis.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/security-triage.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/senior-code-review.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/session-planner.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/tech-affinity-scout.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/technical-planning.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/tool-usage-guidelines.md +0 -0
package/dist/mcp/server.js
CHANGED
|
@@ -1,338 +1,2154 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
import {
|
|
3
|
+
CAPABILITIES,
|
|
4
|
+
LOG_LEVEL_VALUES,
|
|
5
|
+
MCP_PROTOCOL_VERSION,
|
|
6
|
+
MemoryAcknowledgeSchema,
|
|
7
|
+
MemoryDeleteSchema,
|
|
8
|
+
MemoryDetailSchema,
|
|
9
|
+
MemoryRecapSchema,
|
|
10
|
+
MemorySearchSchema,
|
|
11
|
+
MemoryStoreSchema,
|
|
12
|
+
MemorySummarizeSchema,
|
|
13
|
+
MemorySynthesizeSchema,
|
|
14
|
+
MemoryUpdateSchema,
|
|
15
|
+
SQLiteStore,
|
|
16
|
+
TOOL_DEFINITIONS,
|
|
17
|
+
TaskCreateInteractiveSchema,
|
|
18
|
+
TaskCreateSchema,
|
|
19
|
+
TaskDeleteSchema,
|
|
20
|
+
TaskGetSchema,
|
|
21
|
+
TaskListSchema,
|
|
22
|
+
TaskUpdateSchema,
|
|
23
|
+
addLogSink,
|
|
24
|
+
completePromptArgument,
|
|
25
|
+
completeResourceArgument,
|
|
26
|
+
createSessionContext,
|
|
27
|
+
decodeCursor,
|
|
28
|
+
encodeCursor,
|
|
29
|
+
extractRootsFromResult,
|
|
30
|
+
findContainingRoot,
|
|
31
|
+
getFilesystemRoots,
|
|
32
|
+
getLogLevel,
|
|
33
|
+
getPrompt,
|
|
34
|
+
inferRepoFromSession,
|
|
35
|
+
isPathWithinRoots,
|
|
36
|
+
listPrompts,
|
|
37
|
+
listResourceTemplates,
|
|
38
|
+
listResources,
|
|
39
|
+
logger,
|
|
40
|
+
normalizeRepo,
|
|
41
|
+
readResource,
|
|
42
|
+
setLogLevel,
|
|
43
|
+
updateSessionFromInitialize,
|
|
44
|
+
updateSessionRoots
|
|
45
|
+
} from "../chunk-XIJO63UU.js";
|
|
46
|
+
|
|
47
|
+
// src/mcp/server.ts
|
|
48
|
+
import readline from "readline";
|
|
49
|
+
|
|
50
|
+
// src/mcp/router.ts
|
|
51
|
+
import path2 from "path";
|
|
52
|
+
|
|
53
|
+
// src/mcp/completion.ts
|
|
9
54
|
import fs from "fs";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
55
|
+
import path from "path";
|
|
56
|
+
var MAX_COMPLETION_VALUES = 100;
|
|
57
|
+
var MAX_FILE_SCAN_RESULTS = 300;
|
|
58
|
+
async function complete(params, db2, session2) {
|
|
59
|
+
const refType = params?.ref?.type;
|
|
60
|
+
const argumentName = typeof params?.argument?.name === "string" ? params.argument.name : "";
|
|
61
|
+
const argumentValue = typeof params?.argument?.value === "string" ? params.argument.value : "";
|
|
62
|
+
const contextArguments = params?.context?.arguments ?? {};
|
|
63
|
+
if (!refType || !argumentName) {
|
|
64
|
+
throw invalidCompletionParams("completion/complete requires ref.type and argument.name");
|
|
65
|
+
}
|
|
66
|
+
const dataSources = {
|
|
67
|
+
repos: getSuggestedRepos(db2, session2),
|
|
68
|
+
tags: getSuggestedTags(db2),
|
|
69
|
+
filePaths: getSuggestedFilePaths(session2),
|
|
70
|
+
tasks: getSuggestedTasks(db2, session2, contextArguments)
|
|
71
|
+
};
|
|
72
|
+
if (refType === "ref/prompt") {
|
|
73
|
+
const promptName = typeof params?.ref?.name === "string" ? params.ref.name : "";
|
|
74
|
+
if (!promptName) {
|
|
75
|
+
throw invalidCompletionParams("Prompt completion requires ref.name");
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
completion: buildCompletionResult(
|
|
79
|
+
await completePromptArgument(promptName, argumentName, argumentValue, contextArguments, dataSources)
|
|
80
|
+
)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (refType === "ref/resource") {
|
|
84
|
+
const resourceUri = typeof params?.ref?.uri === "string" ? params.ref.uri : "";
|
|
85
|
+
if (!resourceUri) {
|
|
86
|
+
throw invalidCompletionParams("Resource completion requires ref.uri");
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
completion: buildCompletionResult(
|
|
90
|
+
completeResourceArgument(resourceUri, argumentName, argumentValue, contextArguments, dataSources)
|
|
91
|
+
)
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
throw invalidCompletionParams(`Unsupported completion ref type: ${refType}`);
|
|
95
|
+
}
|
|
96
|
+
function buildCompletionResult(values) {
|
|
97
|
+
const capped = values.slice(0, MAX_COMPLETION_VALUES);
|
|
98
|
+
return {
|
|
99
|
+
values: capped,
|
|
100
|
+
total: values.length,
|
|
101
|
+
hasMore: values.length > capped.length
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function getSuggestedRepos(db2, session2) {
|
|
105
|
+
const values = /* @__PURE__ */ new Set();
|
|
106
|
+
const inferredRepo = inferRepoFromSession(session2);
|
|
107
|
+
if (inferredRepo) values.add(inferredRepo);
|
|
108
|
+
for (const rootPath of getFilesystemRoots(session2)) {
|
|
109
|
+
values.add(path.basename(rootPath));
|
|
110
|
+
}
|
|
111
|
+
for (const repo of db2.system.listRepos()) {
|
|
112
|
+
values.add(repo);
|
|
113
|
+
}
|
|
114
|
+
return [...values].sort((a, b) => a.localeCompare(b));
|
|
115
|
+
}
|
|
116
|
+
function getSuggestedTags(db2) {
|
|
117
|
+
const values = /* @__PURE__ */ new Set();
|
|
118
|
+
const memories = db2.memories.getRecentMemories("", 1e3);
|
|
119
|
+
for (const memory of memories) {
|
|
120
|
+
for (const tag of memory.tags || []) {
|
|
121
|
+
if (typeof tag === "string" && tag.trim()) {
|
|
122
|
+
values.add(tag.trim());
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return [...values].sort((a, b) => a.localeCompare(b));
|
|
127
|
+
}
|
|
128
|
+
function getSuggestedTasks(db2, session2, contextArguments) {
|
|
129
|
+
const repo = typeof contextArguments.repo === "string" && contextArguments.repo.trim() ? contextArguments.repo.trim() : inferRepoFromSession(session2);
|
|
130
|
+
if (!repo) return [];
|
|
131
|
+
return db2.tasks.getTasksByRepo(repo, void 0, 100).map((task) => ({
|
|
132
|
+
id: task.id,
|
|
133
|
+
task_code: task.task_code,
|
|
134
|
+
title: task.title
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
function getSuggestedFilePaths(session2) {
|
|
138
|
+
const roots = getFilesystemRoots(session2);
|
|
139
|
+
const results = [];
|
|
140
|
+
for (const rootPath of roots) {
|
|
141
|
+
collectFiles(rootPath, rootPath, results);
|
|
142
|
+
if (results.length >= MAX_FILE_SCAN_RESULTS) break;
|
|
143
|
+
}
|
|
144
|
+
return results.sort((a, b) => a.localeCompare(b));
|
|
145
|
+
}
|
|
146
|
+
function collectFiles(rootPath, currentPath, results) {
|
|
147
|
+
if (results.length >= MAX_FILE_SCAN_RESULTS) return;
|
|
148
|
+
let entries;
|
|
149
|
+
try {
|
|
150
|
+
entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
151
|
+
} catch {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
for (const entry of entries) {
|
|
155
|
+
if (results.length >= MAX_FILE_SCAN_RESULTS) return;
|
|
156
|
+
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist") continue;
|
|
157
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
158
|
+
if (entry.isDirectory()) {
|
|
159
|
+
collectFiles(rootPath, fullPath, results);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (!entry.isFile()) continue;
|
|
163
|
+
results.push(path.relative(rootPath, fullPath) || entry.name);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function invalidCompletionParams(message) {
|
|
167
|
+
const error = new Error(message);
|
|
168
|
+
error.code = -32602;
|
|
169
|
+
return error;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/mcp/tools/memory.store.ts
|
|
173
|
+
import { randomUUID } from "crypto";
|
|
174
|
+
|
|
175
|
+
// src/mcp/utils/mcp-response.ts
|
|
176
|
+
import { z } from "zod";
|
|
177
|
+
var McpAnnotationsSchema = z.object({
|
|
178
|
+
audience: z.array(z.enum(["user", "assistant"])).optional(),
|
|
179
|
+
priority: z.number().min(0).max(1).optional(),
|
|
180
|
+
lastModified: z.string().optional()
|
|
181
|
+
}).strict().optional();
|
|
182
|
+
var McpContentSchema = z.discriminatedUnion("type", [
|
|
183
|
+
z.object({
|
|
184
|
+
type: z.literal("text"),
|
|
185
|
+
text: z.string(),
|
|
186
|
+
annotations: McpAnnotationsSchema
|
|
187
|
+
}),
|
|
188
|
+
z.object({
|
|
189
|
+
type: z.literal("image"),
|
|
190
|
+
data: z.string(),
|
|
191
|
+
mimeType: z.string(),
|
|
192
|
+
annotations: McpAnnotationsSchema
|
|
193
|
+
}),
|
|
194
|
+
z.object({
|
|
195
|
+
type: z.literal("resource_link"),
|
|
196
|
+
uri: z.string(),
|
|
197
|
+
name: z.string(),
|
|
198
|
+
description: z.string().optional(),
|
|
199
|
+
mimeType: z.string().optional(),
|
|
200
|
+
annotations: McpAnnotationsSchema
|
|
201
|
+
}),
|
|
202
|
+
z.object({
|
|
203
|
+
type: z.literal("resource"),
|
|
204
|
+
resource: z.object({
|
|
205
|
+
uri: z.string(),
|
|
206
|
+
mimeType: z.string().optional(),
|
|
207
|
+
text: z.string().optional(),
|
|
208
|
+
annotations: McpAnnotationsSchema
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
]);
|
|
212
|
+
function createMcpResponse(data, summary, options) {
|
|
213
|
+
const {
|
|
214
|
+
resourceLinks,
|
|
215
|
+
structuredContentPathHint,
|
|
216
|
+
contentSummary,
|
|
217
|
+
includeSerializedStructuredContent = "auto"
|
|
218
|
+
} = options || {};
|
|
219
|
+
void includeSerializedStructuredContent;
|
|
220
|
+
let finalData = data;
|
|
221
|
+
if (data && typeof data === "object") {
|
|
222
|
+
const cloned = JSON.parse(JSON.stringify(data));
|
|
223
|
+
finalData = cloned;
|
|
224
|
+
const arrayKeys = ["results", "tasks", "memories", "items"];
|
|
225
|
+
let foundArray = false;
|
|
226
|
+
for (const key of arrayKeys) {
|
|
227
|
+
const value = cloned[key];
|
|
228
|
+
if (Array.isArray(value)) {
|
|
229
|
+
cloned[key] = value.map(
|
|
230
|
+
(item) => pruneMetadata(item)
|
|
231
|
+
);
|
|
232
|
+
foundArray = true;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (Array.isArray(cloned)) {
|
|
236
|
+
finalData = cloned.map((item) => pruneMetadata(item));
|
|
237
|
+
} else if (!foundArray) {
|
|
238
|
+
finalData = pruneMetadata(cloned);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const content = [];
|
|
242
|
+
if (contentSummary?.trim().length) {
|
|
243
|
+
content.push({
|
|
244
|
+
type: "text",
|
|
245
|
+
text: contentSummary.trim()
|
|
246
|
+
});
|
|
247
|
+
} else if (summary.trim().length > 0) {
|
|
248
|
+
const pointerText = structuredContentPathHint ? `Read structuredContent.${structuredContentPathHint} for details.` : `Read structuredContent for machine-readable results.`;
|
|
249
|
+
content.push({
|
|
250
|
+
type: "text",
|
|
251
|
+
text: `${summary.trim()} ${pointerText}`
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
for (const link of resourceLinks || []) {
|
|
255
|
+
content.push({
|
|
256
|
+
type: "resource_link",
|
|
257
|
+
uri: link.uri,
|
|
258
|
+
name: link.name,
|
|
259
|
+
mimeType: link.mimeType,
|
|
260
|
+
annotations: link.annotations
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
const response = {
|
|
264
|
+
structuredContent: finalData,
|
|
265
|
+
isError: false
|
|
266
|
+
};
|
|
267
|
+
response.content = content;
|
|
268
|
+
return response;
|
|
269
|
+
}
|
|
270
|
+
function pruneMetadata(item) {
|
|
271
|
+
if (!item || typeof item !== "object") return item;
|
|
272
|
+
const pruned = { ...item };
|
|
273
|
+
const toRemove = [
|
|
274
|
+
"hit_count",
|
|
275
|
+
"recall_count",
|
|
276
|
+
"last_used_at",
|
|
277
|
+
"expires_at",
|
|
278
|
+
"agent",
|
|
279
|
+
"role",
|
|
280
|
+
"model",
|
|
281
|
+
"recall_rate",
|
|
282
|
+
"vector_version",
|
|
283
|
+
"similarity"
|
|
284
|
+
// Similarity is useful but adds noise if many results
|
|
285
|
+
];
|
|
286
|
+
for (const field of toRemove) {
|
|
287
|
+
delete pruned[field];
|
|
288
|
+
}
|
|
289
|
+
return pruned;
|
|
290
|
+
}
|
|
291
|
+
function getPrimaryTextContent(response) {
|
|
292
|
+
if (!Array.isArray(response.content)) return "";
|
|
293
|
+
const textItem = response.content.find((item) => item.type === "text");
|
|
294
|
+
return textItem?.type === "text" ? textItem.text : "";
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/mcp/tools/memory.store.ts
|
|
298
|
+
function hasMetadataLikeTitle(title) {
|
|
299
|
+
const normalized = title.trim();
|
|
300
|
+
return /^\[[^\]]{0,200}(agent:|role:|model:|\d{4}-\d{2}-\d{2}|source_)[^\]]*\]/i.test(normalized);
|
|
301
|
+
}
|
|
302
|
+
async function handleMemoryStore(params, db2, vectors2) {
|
|
303
|
+
const validated = MemoryStoreSchema.parse(params);
|
|
304
|
+
if (hasMetadataLikeTitle(validated.title)) {
|
|
305
|
+
throw new Error(
|
|
306
|
+
"Title appears to contain metadata. Keep title concise and move agent/role/date details into metadata or dedicated fields."
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
310
|
+
const expires_at = validated.ttlDays != null ? new Date(Date.now() + validated.ttlDays * 864e5).toISOString() : null;
|
|
311
|
+
if (!validated.supersedes && validated.type !== "task_archive") {
|
|
312
|
+
const conflict = await db2.memories.checkConflicts(
|
|
313
|
+
validated.content,
|
|
314
|
+
validated.scope.repo,
|
|
315
|
+
validated.type,
|
|
316
|
+
vectors2,
|
|
317
|
+
0.55
|
|
318
|
+
);
|
|
319
|
+
if (conflict) {
|
|
320
|
+
return createMcpResponse(
|
|
321
|
+
{
|
|
322
|
+
success: false,
|
|
323
|
+
error: "MEMORY_CONFLICT",
|
|
324
|
+
message: `This memory content overlaps significantly with an existing memory (ID: ${conflict.id}).`,
|
|
325
|
+
conflicting_memory: {
|
|
326
|
+
id: conflict.id,
|
|
327
|
+
title: conflict.title,
|
|
328
|
+
content: conflict.content
|
|
329
|
+
},
|
|
330
|
+
instruction: "Use 'memory-update' on the existing ID, or provide 'supersedes' if this new memory replaces it. If the old memory is no longer relevant, you can delete it first."
|
|
331
|
+
},
|
|
332
|
+
`Rejected due to conflict with ${conflict.id}. Hint: Use 'supersedes' if this replaces the old memory, or 'memory-update' if you're updating it. If the old memory is no longer relevant, delete it first using 'memory-delete'.`,
|
|
333
|
+
{
|
|
334
|
+
structuredContentPathHint: "conflicting_memory"
|
|
335
|
+
}
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (validated.supersedes) {
|
|
340
|
+
const oldMemory = db2.memories.getById(validated.supersedes);
|
|
341
|
+
if (oldMemory) {
|
|
342
|
+
db2.memories.update(oldMemory.id, { status: "archived" });
|
|
343
|
+
logger.info("[MCP] memory.store - archived superseded memory", {
|
|
344
|
+
oldId: oldMemory.id,
|
|
345
|
+
newId: validated.supersedes
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
const tags = validated.tags ?? [];
|
|
350
|
+
if (validated.scope.language && !tags.includes(validated.scope.language.toLowerCase())) {
|
|
351
|
+
tags.push(validated.scope.language.toLowerCase());
|
|
352
|
+
}
|
|
353
|
+
const entry = {
|
|
354
|
+
id: randomUUID(),
|
|
355
|
+
type: validated.type,
|
|
356
|
+
title: validated.title,
|
|
357
|
+
content: validated.content,
|
|
358
|
+
importance: validated.importance,
|
|
359
|
+
agent: validated.agent,
|
|
360
|
+
role: validated.role,
|
|
361
|
+
model: validated.model,
|
|
362
|
+
scope: validated.scope,
|
|
363
|
+
created_at: now,
|
|
364
|
+
updated_at: now,
|
|
365
|
+
completed_at: null,
|
|
366
|
+
hit_count: 0,
|
|
367
|
+
recall_count: 0,
|
|
368
|
+
last_used_at: null,
|
|
369
|
+
expires_at,
|
|
370
|
+
supersedes: validated.supersedes ?? null,
|
|
371
|
+
status: "active",
|
|
372
|
+
tags,
|
|
373
|
+
metadata: validated.metadata ?? {},
|
|
374
|
+
is_global: validated.is_global
|
|
375
|
+
};
|
|
376
|
+
db2.memories.insert(entry);
|
|
377
|
+
try {
|
|
378
|
+
await vectors2.upsert(entry.id, entry.content);
|
|
379
|
+
} catch (error) {
|
|
380
|
+
logger.warn("Failed to generate vector embedding", { error: String(error) });
|
|
381
|
+
}
|
|
382
|
+
logger.info("[MCP] memory.store", {
|
|
383
|
+
repo: validated.scope.repo,
|
|
384
|
+
id: entry.id,
|
|
385
|
+
title: entry.title,
|
|
386
|
+
type: entry.type,
|
|
387
|
+
importance: entry.importance
|
|
388
|
+
});
|
|
389
|
+
return createMcpResponse(
|
|
390
|
+
{
|
|
391
|
+
success: true,
|
|
392
|
+
id: entry.id,
|
|
393
|
+
repo: entry.scope.repo,
|
|
394
|
+
type: entry.type,
|
|
395
|
+
title: entry.title
|
|
396
|
+
},
|
|
397
|
+
`Stored memory "${entry.title}" in repo "${entry.scope.repo}".`,
|
|
398
|
+
{
|
|
399
|
+
structuredContentPathHint: "id",
|
|
400
|
+
resourceLinks: [
|
|
401
|
+
{
|
|
402
|
+
uri: `memory://${entry.id}`,
|
|
403
|
+
name: entry.title,
|
|
404
|
+
description: `Stored memory in repo ${entry.scope.repo}`,
|
|
405
|
+
mimeType: "application/json",
|
|
406
|
+
annotations: {
|
|
407
|
+
audience: ["assistant"],
|
|
408
|
+
priority: 0.9,
|
|
409
|
+
lastModified: entry.updated_at
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
uri: `repository://${encodeURIComponent(entry.scope.repo)}/memories`,
|
|
414
|
+
name: `Memory Index (${entry.scope.repo})`,
|
|
415
|
+
description: "Repository memory index",
|
|
416
|
+
mimeType: "application/json",
|
|
417
|
+
annotations: {
|
|
418
|
+
audience: ["assistant"],
|
|
419
|
+
priority: 0.6
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
]
|
|
423
|
+
}
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// src/mcp/tools/memory.update.ts
|
|
428
|
+
function hasMetadataLikeTitle2(title) {
|
|
429
|
+
const normalized = title.trim();
|
|
430
|
+
return /^\[[^\]]{0,200}(agent:|role:|model:|\d{4}-\d{2}-\d{2}|source_)[^\]]*\]/i.test(normalized);
|
|
431
|
+
}
|
|
432
|
+
async function handleMemoryUpdate(params, db2, vectors2) {
|
|
433
|
+
const validated = MemoryUpdateSchema.parse(params);
|
|
434
|
+
const existing = db2.memories.getById(validated.id);
|
|
435
|
+
if (!existing) {
|
|
436
|
+
throw new Error(`Memory not found: ${validated.id}`);
|
|
437
|
+
}
|
|
438
|
+
const repoFilter = params?.repo || params?.scope?.repo;
|
|
439
|
+
if (repoFilter && repoFilter !== existing.scope.repo) {
|
|
440
|
+
throw new Error(
|
|
441
|
+
`Repository mismatch: provided repo "${repoFilter}" does not match memory repo "${existing.scope.repo}"`
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
if (validated.title !== void 0 && hasMetadataLikeTitle2(validated.title)) {
|
|
445
|
+
throw new Error(
|
|
446
|
+
"Title appears to contain metadata. Keep title concise and move agent/role/date details into metadata or dedicated fields."
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
const updates = {};
|
|
450
|
+
if (validated.type !== void 0) updates.type = validated.type;
|
|
451
|
+
if (validated.title !== void 0) updates.title = validated.title;
|
|
452
|
+
if (validated.content !== void 0) updates.content = validated.content;
|
|
453
|
+
if (validated.importance !== void 0) updates.importance = validated.importance;
|
|
454
|
+
if (validated.agent !== void 0) updates.agent = validated.agent;
|
|
455
|
+
if (validated.role !== void 0) updates.role = validated.role;
|
|
456
|
+
if (validated.status !== void 0) updates.status = validated.status;
|
|
457
|
+
if (validated.supersedes !== void 0) updates.supersedes = validated.supersedes;
|
|
458
|
+
if (validated.tags !== void 0) updates.tags = validated.tags;
|
|
459
|
+
if (validated.metadata !== void 0) updates.metadata = validated.metadata;
|
|
460
|
+
if (validated.is_global !== void 0) updates.is_global = validated.is_global;
|
|
461
|
+
if (validated.completed_at !== void 0) updates.completed_at = validated.completed_at;
|
|
462
|
+
db2.memories.update(validated.id, updates);
|
|
463
|
+
if (validated.content !== void 0) {
|
|
464
|
+
await vectors2.upsert(validated.id, validated.content);
|
|
465
|
+
}
|
|
466
|
+
db2.actions.logAction("update", existing.scope.repo, { memoryId: validated.id, resultCount: 1 });
|
|
467
|
+
logger.info("[MCP] memory.update", { repo: existing.scope.repo, id: validated.id, fields: Object.keys(updates) });
|
|
468
|
+
return createMcpResponse(
|
|
469
|
+
{
|
|
470
|
+
success: true,
|
|
471
|
+
id: validated.id,
|
|
472
|
+
repo: existing.scope.repo,
|
|
473
|
+
updatedFields: Object.keys(updates)
|
|
474
|
+
},
|
|
475
|
+
`Updated memory ${validated.id} in repo "${existing.scope.repo}". Fields: ${Object.keys(updates).join(", ") || "none"}.`,
|
|
476
|
+
{
|
|
477
|
+
structuredContentPathHint: "updatedFields",
|
|
478
|
+
resourceLinks: [
|
|
479
|
+
{
|
|
480
|
+
uri: `memory://${validated.id}`,
|
|
481
|
+
name: existing.title || validated.id,
|
|
482
|
+
description: `Updated memory in repo ${existing.scope.repo}`,
|
|
483
|
+
mimeType: "application/json",
|
|
484
|
+
annotations: {
|
|
485
|
+
audience: ["assistant"],
|
|
486
|
+
priority: 0.9
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
]
|
|
490
|
+
}
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// src/mcp/utils/query-expander.ts
|
|
495
|
+
var TECH_SYNONYMS = {
|
|
496
|
+
update: ["migrate", "change", "alter", "modify", "patch", "upgrade"],
|
|
497
|
+
database: ["db", "sql", "schema", "persistence", "storage", "sqlite", "postgres"],
|
|
498
|
+
auth: ["login", "security", "token", "session", "permission", "authorization", "authentication", "system"],
|
|
499
|
+
authentication: ["auth", "login", "security", "token", "session", "permission"],
|
|
500
|
+
system: ["architecture", "structure", "design", "security"],
|
|
501
|
+
error: ["bug", "issue", "failure", "mistake", "fix", "exception"],
|
|
502
|
+
ui: ["frontend", "component", "styling", "css", "layout", "view"],
|
|
503
|
+
deploy: ["publish", "release", "ci", "cd", "pipeline"]
|
|
504
|
+
};
|
|
505
|
+
function expandQuery(query, prompt) {
|
|
506
|
+
const baseText = prompt ? `${query} ${prompt}` : query;
|
|
507
|
+
const words = baseText.toLowerCase().split(/\s+/);
|
|
508
|
+
const expansions = new Set(words);
|
|
509
|
+
for (const word of words) {
|
|
510
|
+
const cleanWord = word.replace(/[?.!,]/g, "");
|
|
511
|
+
if (TECH_SYNONYMS[cleanWord]) {
|
|
512
|
+
TECH_SYNONYMS[cleanWord].forEach((syn) => expansions.add(syn));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return Array.from(expansions).join(" ");
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// src/mcp/tools/memory.search.ts
|
|
519
|
+
var HYBRID_WEIGHTS_VECTOR = {
|
|
520
|
+
similarity: 0.4,
|
|
521
|
+
vector: 0.4,
|
|
522
|
+
importance: 0.2
|
|
523
|
+
};
|
|
524
|
+
async function handleMemorySearch(params, db2, vectors2) {
|
|
525
|
+
const validated = MemorySearchSchema.parse(params);
|
|
526
|
+
const searchQuery = expandQuery(validated.query, validated.prompt);
|
|
527
|
+
const fetchLimit = (validated.offset + validated.limit) * 3;
|
|
528
|
+
const similarityResults = db2.memories.searchBySimilarity(
|
|
529
|
+
searchQuery,
|
|
530
|
+
validated.repo,
|
|
531
|
+
fetchLimit,
|
|
532
|
+
validated.include_archived,
|
|
533
|
+
validated.current_tags ?? []
|
|
534
|
+
);
|
|
535
|
+
let candidates = similarityResults.map((r) => ({
|
|
536
|
+
memory: r,
|
|
537
|
+
similarityScore: r.similarity
|
|
538
|
+
}));
|
|
539
|
+
if (candidates.length > 0) {
|
|
540
|
+
const currentPath = validated.current_file_path?.toLowerCase();
|
|
541
|
+
const currentTags = (validated.current_tags || []).map((t) => t.toLowerCase());
|
|
542
|
+
const currentBranch = validated.scope?.branch;
|
|
543
|
+
candidates = candidates.map((c) => {
|
|
544
|
+
let boost = 0;
|
|
545
|
+
if (currentBranch && c.memory.scope.branch === currentBranch) {
|
|
546
|
+
boost += 0.1;
|
|
547
|
+
}
|
|
548
|
+
if (currentPath && c.memory.scope.folder && currentPath.includes(c.memory.scope.folder.toLowerCase())) {
|
|
549
|
+
boost += 0.15;
|
|
550
|
+
}
|
|
551
|
+
if (currentPath && c.memory.scope.language) {
|
|
552
|
+
const ext = currentPath.split(".").pop();
|
|
553
|
+
if (ext && ext.includes(c.memory.scope.language.toLowerCase())) boost += 0.1;
|
|
554
|
+
}
|
|
555
|
+
if (currentTags.length > 0 && c.memory.tags.some((t) => currentTags.includes(t.toLowerCase()))) {
|
|
556
|
+
boost += 0.2;
|
|
557
|
+
}
|
|
558
|
+
return { ...c, similarityScore: Math.min(1, c.similarityScore + boost) };
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
let scoredMemories = [];
|
|
562
|
+
try {
|
|
563
|
+
const vectorResults = await vectors2.search(searchQuery, candidates.length || 10, validated.repo);
|
|
564
|
+
const vectorScoreMap = new Map(vectorResults.map((vr) => [vr.id, vr.score]));
|
|
565
|
+
if (candidates.length > 0) {
|
|
566
|
+
scoredMemories = candidates.map((c) => {
|
|
567
|
+
const vScore = vectorScoreMap.get(c.memory.id) ?? 0;
|
|
568
|
+
const impBoost = c.memory.importance / 5;
|
|
569
|
+
const finalScore = c.similarityScore * HYBRID_WEIGHTS_VECTOR.similarity + vScore * HYBRID_WEIGHTS_VECTOR.vector + impBoost * HYBRID_WEIGHTS_VECTOR.importance;
|
|
570
|
+
return { ...c, vectorScore: vScore, finalScore };
|
|
571
|
+
});
|
|
572
|
+
} else if (vectorResults.length > 0) {
|
|
573
|
+
const vectorIds = vectorResults.map((vr) => vr.id);
|
|
574
|
+
const fetchedMemories = db2.memories.getByIds(vectorIds);
|
|
575
|
+
const memoryMap = new Map(fetchedMemories.map((m) => [m.id, m]));
|
|
576
|
+
for (const vr of vectorResults) {
|
|
577
|
+
const mem = memoryMap.get(vr.id);
|
|
578
|
+
if (mem) {
|
|
579
|
+
const impBoost = mem.importance / 5;
|
|
580
|
+
scoredMemories.push({
|
|
581
|
+
memory: mem,
|
|
582
|
+
similarityScore: 0,
|
|
583
|
+
vectorScore: vr.score,
|
|
584
|
+
finalScore: vr.score * 0.8 + impBoost * 0.2
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
} catch (error) {
|
|
590
|
+
logger.warn("Vector search failed, using similarity only", { error: String(error) });
|
|
591
|
+
scoredMemories = candidates.map((c) => ({
|
|
592
|
+
...c,
|
|
593
|
+
vectorScore: 0,
|
|
594
|
+
finalScore: c.similarityScore * 0.8 + c.memory.importance / 5 * 0.2
|
|
595
|
+
}));
|
|
596
|
+
}
|
|
597
|
+
scoredMemories.sort((a, b) => b.finalScore - a.finalScore);
|
|
598
|
+
const threshold = scoredMemories.length <= 5 ? 0.1 : 0.4;
|
|
599
|
+
let allMatches = scoredMemories.filter((sm) => sm.finalScore >= threshold).map((sm) => sm.memory);
|
|
600
|
+
if (allMatches.length === 0 && scoredMemories.length > 0) {
|
|
601
|
+
allMatches = [scoredMemories[0].memory];
|
|
602
|
+
}
|
|
603
|
+
const total = allMatches.length;
|
|
604
|
+
const paginatedResults = allMatches.slice(validated.offset, validated.offset + validated.limit);
|
|
605
|
+
db2.memories.incrementHitCounts(paginatedResults.map((m) => m.id));
|
|
606
|
+
logger.info("[MCP] memory.search", {
|
|
607
|
+
repo: validated.repo,
|
|
608
|
+
query: validated.query,
|
|
609
|
+
total,
|
|
610
|
+
offset: validated.offset,
|
|
611
|
+
returned: paginatedResults.length
|
|
612
|
+
});
|
|
613
|
+
const COLUMNS = ["id", "title", "type", "importance"];
|
|
614
|
+
const rows = paginatedResults.map((m) => [m.id, m.title ?? "Untitled", m.type, m.importance]);
|
|
615
|
+
const structuredContent = {
|
|
616
|
+
schema: "memory-search",
|
|
617
|
+
query: validated.query,
|
|
618
|
+
count: paginatedResults.length,
|
|
619
|
+
total,
|
|
620
|
+
offset: validated.offset,
|
|
621
|
+
limit: validated.limit,
|
|
622
|
+
results: {
|
|
623
|
+
columns: [...COLUMNS],
|
|
624
|
+
rows
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
const memoryList = paginatedResults.map((m) => `"${m.title}" (ID: ${m.id})`).join(", ");
|
|
628
|
+
const contentSummary = paginatedResults.length > 0 ? `Found ${total} memories for "${validated.query}" (showing ${paginatedResults.length} at offset ${validated.offset}): ${memoryList}. Use memory-detail to read full content.` : `No memories found for "${validated.query}" in repo "${validated.repo}".`;
|
|
629
|
+
return createMcpResponse(structuredContent, contentSummary, {
|
|
630
|
+
contentSummary,
|
|
631
|
+
includeSerializedStructuredContent: false
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// src/mcp/tools/memory.summarize.ts
|
|
636
|
+
async function handleMemorySummarize(params, db2) {
|
|
637
|
+
const validated = MemorySummarizeSchema.parse(params);
|
|
638
|
+
const summary = validated.signals.join("\n- ");
|
|
639
|
+
const fullSummary = `Project summary:
|
|
640
|
+
- ${summary}`;
|
|
641
|
+
db2.summaries.upsertSummary(validated.repo, fullSummary);
|
|
642
|
+
return createMcpResponse(
|
|
643
|
+
{
|
|
644
|
+
success: true,
|
|
645
|
+
repo: validated.repo,
|
|
646
|
+
summary: fullSummary,
|
|
647
|
+
signalCount: validated.signals.length
|
|
648
|
+
},
|
|
649
|
+
`Updated summary for repo "${validated.repo}" with ${validated.signals.length} signals.`,
|
|
650
|
+
{
|
|
651
|
+
structuredContentPathHint: "summary",
|
|
652
|
+
resourceLinks: [
|
|
653
|
+
{
|
|
654
|
+
uri: `repository://${encodeURIComponent(validated.repo)}/summary`,
|
|
655
|
+
name: `Repository Summary (${validated.repo})`,
|
|
656
|
+
description: "Repository summary resource",
|
|
657
|
+
mimeType: "text/plain",
|
|
658
|
+
annotations: {
|
|
659
|
+
audience: ["assistant"],
|
|
660
|
+
priority: 0.9
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
]
|
|
664
|
+
}
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// src/mcp/sampling.ts
|
|
669
|
+
function asArray(value) {
|
|
670
|
+
return Array.isArray(value) ? value : [value];
|
|
671
|
+
}
|
|
672
|
+
function extractTextFromContent(content) {
|
|
673
|
+
return asArray(content).filter((entry) => entry.type === "text").map((entry) => entry.text).join("\n");
|
|
674
|
+
}
|
|
675
|
+
function extractToolUses(content) {
|
|
676
|
+
return asArray(content).filter(
|
|
677
|
+
(entry) => entry.type === "tool_use"
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// src/mcp/elicitation.ts
|
|
682
|
+
function extractAcceptedElicitationContent(result) {
|
|
683
|
+
if (result?.action === "accept" && result.content && typeof result.content === "object") {
|
|
684
|
+
return result.content;
|
|
685
|
+
}
|
|
686
|
+
if (result?.action === "decline") {
|
|
687
|
+
throw new Error("User declined the elicitation request");
|
|
688
|
+
}
|
|
689
|
+
if (result?.action === "cancel") {
|
|
690
|
+
throw new Error("User canceled the elicitation request");
|
|
691
|
+
}
|
|
692
|
+
throw new Error("Elicitation did not return accepted content");
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// src/mcp/tools/memory.recap.ts
|
|
696
|
+
async function handleMemoryRecap(params, db2) {
|
|
697
|
+
const validated = MemoryRecapSchema.parse(params);
|
|
698
|
+
logger.info("[MCP] memory.recap", { repo: validated.repo, limit: validated.limit, offset: validated.offset });
|
|
699
|
+
const stats = db2.memories.getStats(validated.repo);
|
|
700
|
+
const total = db2.memories.getTotalCount(validated.repo, false, ["task_archive"]);
|
|
701
|
+
const rows = db2.memories.getRecentMemories(validated.repo, validated.limit, validated.offset, false, [
|
|
702
|
+
"task_archive"
|
|
703
|
+
]);
|
|
704
|
+
const COLUMNS = ["id", "title", "type", "importance"];
|
|
705
|
+
const topRows = rows.map((row) => [row.id, row.title ?? "Untitled", row.type, row.importance]);
|
|
706
|
+
const byType = {};
|
|
707
|
+
for (const [type, count] of Object.entries(stats.byType)) {
|
|
708
|
+
if (type !== "task_archive") {
|
|
709
|
+
byType[type] = count;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
const structuredContent = {
|
|
713
|
+
schema: "memory-recap",
|
|
714
|
+
repo: validated.repo,
|
|
715
|
+
count: rows.length,
|
|
716
|
+
total,
|
|
717
|
+
offset: validated.offset,
|
|
718
|
+
limit: validated.limit,
|
|
719
|
+
stats: {
|
|
720
|
+
by_type: byType
|
|
721
|
+
},
|
|
722
|
+
top: {
|
|
723
|
+
columns: [...COLUMNS],
|
|
724
|
+
rows: topRows
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
const memoryList = rows.map((row) => `"${row.title}" (ID: ${row.id})`).join(", ");
|
|
728
|
+
const contentSummary = total > 0 ? `Repo "${validated.repo}" has ${total} active memories. Showing ${rows.length} at offset ${validated.offset}: ${memoryList}. Use memory-detail to read full content.` : `No memories found for repo "${validated.repo}".`;
|
|
729
|
+
return createMcpResponse(structuredContent, contentSummary, {
|
|
730
|
+
contentSummary,
|
|
731
|
+
includeSerializedStructuredContent: false
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// src/mcp/tools/task.manage.ts
|
|
736
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
737
|
+
function describeTaskListFilter(status) {
|
|
738
|
+
if (!status) return "active";
|
|
739
|
+
if (status === "all") return "all";
|
|
740
|
+
const labels = status.split(",").map((part) => part.trim()).filter(Boolean).map((part) => {
|
|
741
|
+
switch (part) {
|
|
742
|
+
case "in_progress":
|
|
743
|
+
return "in progress";
|
|
744
|
+
default:
|
|
745
|
+
return part;
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
if (labels.length === 0) return "active";
|
|
749
|
+
if (labels.length === 1) return labels[0];
|
|
750
|
+
if (labels.length === 2) return `${labels[0]} and ${labels[1]}`;
|
|
751
|
+
return `${labels.slice(0, -1).join(", ")}, and ${labels[labels.length - 1]}`;
|
|
752
|
+
}
|
|
753
|
+
function buildTaskListSummary(repo, count, status, phase, search, stats) {
|
|
754
|
+
const filterLabel = describeTaskListFilter(status);
|
|
755
|
+
const taskLabel = count === 1 ? "task" : "tasks";
|
|
756
|
+
const parts = [`Found ${count} ${filterLabel} ${taskLabel} in repo "${repo}".`];
|
|
757
|
+
if (phase) {
|
|
758
|
+
parts.push(`Phase filter: ${phase}.`);
|
|
759
|
+
}
|
|
760
|
+
if (search) {
|
|
761
|
+
parts.push(`Search filter: "${search}".`);
|
|
762
|
+
}
|
|
763
|
+
parts.push(`Pending: ${stats?.todo ?? 0}.`);
|
|
764
|
+
parts.push(`In progress: ${stats?.inProgress ?? 0}.`);
|
|
765
|
+
parts.push(`Use task-detail with Task ID or task_code to read full details.`);
|
|
766
|
+
return parts.join(" ");
|
|
767
|
+
}
|
|
768
|
+
function deriveTaskStatusTimestamps(status, now, existingTask) {
|
|
769
|
+
const timestamps = {
|
|
770
|
+
in_progress_at: null,
|
|
771
|
+
finished_at: null,
|
|
772
|
+
canceled_at: null
|
|
773
|
+
};
|
|
774
|
+
if (status === "in_progress" && existingTask?.status !== "in_progress") {
|
|
775
|
+
timestamps.in_progress_at = now;
|
|
776
|
+
}
|
|
777
|
+
if (status === "completed") {
|
|
778
|
+
timestamps.finished_at = now;
|
|
779
|
+
}
|
|
780
|
+
if (status === "canceled") {
|
|
781
|
+
timestamps.canceled_at = now;
|
|
782
|
+
}
|
|
783
|
+
return timestamps;
|
|
784
|
+
}
|
|
785
|
+
async function archiveTaskToMemory(taskId, repo, storage, vectors2) {
|
|
786
|
+
const task = storage.tasks.getTaskById(taskId);
|
|
787
|
+
if (!task) return;
|
|
788
|
+
const comments = storage.tasks.getTaskCommentsByTaskId(taskId);
|
|
789
|
+
let content = `Task: [${task.task_code}] ${task.title}
|
|
790
|
+
`;
|
|
791
|
+
content += `Phase: ${task.phase}
|
|
792
|
+
`;
|
|
793
|
+
content += `Description: ${task.description || "No description"}
|
|
794
|
+
`;
|
|
795
|
+
if (comments && comments.length > 0) {
|
|
796
|
+
content += `
|
|
797
|
+
Comments & History:
|
|
798
|
+
`;
|
|
799
|
+
const chronComments = [...comments].reverse();
|
|
800
|
+
for (const c of chronComments) {
|
|
801
|
+
const statusInfo = c.next_status ? ` (Status: ${c.previous_status} -> ${c.next_status})` : "";
|
|
802
|
+
content += `- [${c.created_at}] ${c.agent}${statusInfo}: ${c.comment}
|
|
803
|
+
`;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
const metadata = {
|
|
807
|
+
task_id: taskId,
|
|
808
|
+
task_code: task.task_code,
|
|
809
|
+
original_metadata: task.metadata
|
|
810
|
+
};
|
|
811
|
+
const title = `Completed Task: ${task.title}`;
|
|
812
|
+
const truncatedTitle = title.length > 100 ? title.substring(0, 97) + "..." : title;
|
|
813
|
+
try {
|
|
814
|
+
await handleMemoryStore(
|
|
815
|
+
{
|
|
816
|
+
type: "task_archive",
|
|
817
|
+
title: truncatedTitle,
|
|
818
|
+
content,
|
|
819
|
+
importance: Math.min(5, task.priority + 1),
|
|
820
|
+
// Slightly higher importance for archived tasks
|
|
821
|
+
agent: task.agent || "system",
|
|
822
|
+
role: task.role || "unknown",
|
|
823
|
+
model: "system",
|
|
824
|
+
scope: { repo },
|
|
825
|
+
tags: ["task-archive", ...task.tags],
|
|
826
|
+
metadata
|
|
827
|
+
},
|
|
828
|
+
storage,
|
|
829
|
+
vectors2
|
|
830
|
+
);
|
|
831
|
+
} catch (error) {
|
|
832
|
+
console.error("Failed to archive task to memory", error);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
async function handleTaskList(args, storage) {
|
|
836
|
+
const {
|
|
837
|
+
repo,
|
|
838
|
+
status = "backlog,pending,in_progress,blocked",
|
|
839
|
+
phase,
|
|
840
|
+
query,
|
|
841
|
+
limit,
|
|
842
|
+
offset
|
|
843
|
+
} = TaskListSchema.parse(args);
|
|
844
|
+
let statuses = [];
|
|
845
|
+
if (status !== "all") {
|
|
846
|
+
statuses = status.split(",").map((s) => s.trim()).filter(Boolean);
|
|
847
|
+
}
|
|
848
|
+
const tasks = storage.tasks.getTasksByMultipleStatuses(repo, statuses, limit, offset, query);
|
|
849
|
+
const filteredTasks = phase ? tasks.filter((t) => t.phase.toLowerCase() === phase.toLowerCase()) : tasks;
|
|
850
|
+
const COLUMNS = ["id", "task_code", "title", "status", "priority", "comments_count"];
|
|
851
|
+
const rows = filteredTasks.map((t) => [
|
|
852
|
+
t.id,
|
|
853
|
+
t.task_code,
|
|
854
|
+
t.title,
|
|
855
|
+
t.status,
|
|
856
|
+
t.priority,
|
|
857
|
+
t.comments_count || 0
|
|
858
|
+
]);
|
|
859
|
+
const structured = {
|
|
860
|
+
schema: "task-list",
|
|
861
|
+
tasks: {
|
|
862
|
+
columns: [...COLUMNS],
|
|
863
|
+
rows
|
|
864
|
+
},
|
|
865
|
+
count: rows.length,
|
|
866
|
+
offset
|
|
867
|
+
};
|
|
868
|
+
const _taskList = filteredTasks.map((t) => `[${t.task_code}] ${t.title} (ID: ${t.id})`).join(", ");
|
|
869
|
+
const taskStats = storage.tasks.getTaskStats(repo);
|
|
870
|
+
const summary = buildTaskListSummary(repo, rows.length, status, phase, query, taskStats);
|
|
871
|
+
return createMcpResponse(structured, summary, {
|
|
872
|
+
contentSummary: summary,
|
|
873
|
+
includeSerializedStructuredContent: false
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
async function handleTaskCreate(args, storage) {
|
|
877
|
+
const parsed = TaskCreateSchema.parse(args);
|
|
878
|
+
const { repo, tasks: bulkTasks, ...singleTask } = parsed;
|
|
879
|
+
if (bulkTasks) {
|
|
880
|
+
const createdTasks = [];
|
|
881
|
+
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
882
|
+
const codesInRequest = /* @__PURE__ */ new Set();
|
|
883
|
+
for (const taskData of bulkTasks) {
|
|
884
|
+
if (codesInRequest.has(taskData.task_code)) {
|
|
885
|
+
throw new Error(`Duplicate task_code in request: '${taskData.task_code}'`);
|
|
886
|
+
}
|
|
887
|
+
if (storage.tasks.isTaskCodeDuplicate(repo, taskData.task_code)) {
|
|
888
|
+
throw new Error(`Duplicate task_code: '${taskData.task_code}' already exists in repository '${repo}'`);
|
|
889
|
+
}
|
|
890
|
+
codesInRequest.add(taskData.task_code);
|
|
891
|
+
const normalizedStatus = taskData.status || "backlog";
|
|
892
|
+
if (normalizedStatus !== "backlog" && normalizedStatus !== "pending") {
|
|
893
|
+
throw new Error(`New tasks must be 'backlog' or 'pending'. Task '${taskData.task_code}' has status '${normalizedStatus}'.`);
|
|
894
|
+
}
|
|
895
|
+
if (normalizedStatus === "pending") {
|
|
896
|
+
const stats = storage.tasks.getTaskStats(repo);
|
|
897
|
+
const pendingInRequest = createdTasks.filter((c) => {
|
|
898
|
+
const t = bulkTasks.find((bt) => bt.task_code === c);
|
|
899
|
+
return t?.status === "pending";
|
|
900
|
+
}).length;
|
|
901
|
+
if (stats.todo + pendingInRequest >= 10) {
|
|
902
|
+
throw new Error(`Cannot create task '${taskData.task_code}' as 'pending'. Maximum of 10 pending tasks reached.`);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
const statusTimestamps2 = deriveTaskStatusTimestamps(normalizedStatus, now2);
|
|
906
|
+
const task2 = {
|
|
907
|
+
id: randomUUID2(),
|
|
908
|
+
repo,
|
|
909
|
+
task_code: taskData.task_code,
|
|
910
|
+
phase: taskData.phase,
|
|
911
|
+
title: taskData.title,
|
|
912
|
+
description: taskData.description,
|
|
913
|
+
status: normalizedStatus,
|
|
914
|
+
priority: taskData.priority || 3,
|
|
915
|
+
agent: taskData.agent || singleTask.agent || "unknown",
|
|
916
|
+
role: taskData.role || singleTask.role || "unknown",
|
|
917
|
+
doc_path: taskData.doc_path || null,
|
|
918
|
+
created_at: now2,
|
|
919
|
+
updated_at: now2,
|
|
920
|
+
in_progress_at: statusTimestamps2.in_progress_at,
|
|
921
|
+
finished_at: statusTimestamps2.finished_at,
|
|
922
|
+
canceled_at: statusTimestamps2.canceled_at,
|
|
923
|
+
est_tokens: taskData.est_tokens ?? 0,
|
|
924
|
+
tags: taskData.tags || [],
|
|
925
|
+
metadata: taskData.metadata || {},
|
|
926
|
+
parent_id: taskData.parent_id || null,
|
|
927
|
+
depends_on: taskData.depends_on || null
|
|
928
|
+
};
|
|
929
|
+
storage.tasks.insertTask(task2);
|
|
930
|
+
createdTasks.push(task2.task_code);
|
|
931
|
+
}
|
|
932
|
+
return createMcpResponse(
|
|
933
|
+
{ success: true, repo, createdCount: bulkTasks.length, taskCodes: createdTasks },
|
|
934
|
+
`Created ${bulkTasks.length} tasks in repo "${repo}".`
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
const { task_code, phase, title, description, status, priority, agent, role, doc_path, tags, metadata, parent_id, depends_on, est_tokens } = singleTask;
|
|
938
|
+
if (!task_code || !phase || !title || !description) {
|
|
939
|
+
throw new Error("Missing required fields for single task creation (task_code, phase, title, description)");
|
|
940
|
+
}
|
|
941
|
+
if (storage.tasks.isTaskCodeDuplicate(repo, task_code)) {
|
|
942
|
+
throw new Error(`Duplicate task_code: '${task_code}' already exists in repository '${repo}'`);
|
|
943
|
+
}
|
|
944
|
+
if (status !== "backlog" && status !== "pending" && status !== void 0) {
|
|
945
|
+
throw new Error("New tasks must be created with status 'backlog' or 'pending'.");
|
|
946
|
+
}
|
|
947
|
+
if (status === "pending") {
|
|
948
|
+
const stats = storage.tasks.getTaskStats(repo);
|
|
949
|
+
if (stats.todo >= 10) {
|
|
950
|
+
throw new Error(`Cannot create task as 'pending'. Maximum of 10 pending tasks reached.`);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
const taskId = randomUUID2();
|
|
954
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
955
|
+
const statusTimestamps = deriveTaskStatusTimestamps(status || "backlog", now);
|
|
956
|
+
const task = {
|
|
957
|
+
id: taskId,
|
|
958
|
+
repo,
|
|
959
|
+
task_code,
|
|
960
|
+
phase,
|
|
961
|
+
title,
|
|
962
|
+
description,
|
|
963
|
+
status: status || "backlog",
|
|
964
|
+
priority: priority || 3,
|
|
965
|
+
agent: agent || "unknown",
|
|
966
|
+
role: role || "unknown",
|
|
967
|
+
doc_path: doc_path || null,
|
|
968
|
+
created_at: now,
|
|
969
|
+
updated_at: now,
|
|
970
|
+
in_progress_at: statusTimestamps.in_progress_at,
|
|
971
|
+
finished_at: statusTimestamps.finished_at,
|
|
972
|
+
canceled_at: statusTimestamps.canceled_at,
|
|
973
|
+
est_tokens: est_tokens ?? 0,
|
|
974
|
+
tags: tags || [],
|
|
975
|
+
metadata: metadata || {},
|
|
976
|
+
parent_id: parent_id || null,
|
|
977
|
+
depends_on: depends_on || null
|
|
978
|
+
};
|
|
979
|
+
storage.tasks.insertTask(task);
|
|
980
|
+
return createMcpResponse(
|
|
981
|
+
{
|
|
982
|
+
success: true,
|
|
983
|
+
id: task.id,
|
|
984
|
+
repo: task.repo,
|
|
985
|
+
task_code: task.task_code,
|
|
986
|
+
phase: task.phase,
|
|
987
|
+
title: task.title,
|
|
988
|
+
status: task.status,
|
|
989
|
+
priority: task.priority,
|
|
990
|
+
depends_on: task.depends_on
|
|
991
|
+
},
|
|
992
|
+
`Created task [${task.task_code}] ${task.title} in repo "${task.repo}" with status "${task.status}".`
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
async function handleTaskCreateInteractive(args, storage, options = {}) {
|
|
996
|
+
const partialTaskData = TaskCreateInteractiveSchema.parse(args ?? {});
|
|
997
|
+
const inferredRepo = partialTaskData.repo ?? inferRepoFromSession(options.session);
|
|
998
|
+
const draft = {
|
|
999
|
+
...partialTaskData,
|
|
1000
|
+
repo: inferredRepo
|
|
1001
|
+
};
|
|
1002
|
+
const requestedSchema = buildMissingTaskSchema(draft);
|
|
1003
|
+
let completedDraft = draft;
|
|
1004
|
+
if (Object.keys(requestedSchema.properties).length > 0) {
|
|
1005
|
+
if (!options.session?.supportsElicitationForm || !options.elicit) {
|
|
1006
|
+
throw new Error("Client does not advertise MCP elicitation form support");
|
|
1007
|
+
}
|
|
1008
|
+
const elicited = extractAcceptedElicitationContent(
|
|
1009
|
+
await options.elicit({
|
|
1010
|
+
mode: "form",
|
|
1011
|
+
message: "Please complete the missing task details to create a new task.",
|
|
1012
|
+
requestedSchema
|
|
1013
|
+
})
|
|
1014
|
+
);
|
|
1015
|
+
completedDraft = {
|
|
1016
|
+
...draft,
|
|
1017
|
+
...elicited
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
return await handleTaskCreate(
|
|
1021
|
+
{
|
|
1022
|
+
...completedDraft,
|
|
1023
|
+
status: completedDraft.status ?? "backlog",
|
|
1024
|
+
priority: completedDraft.priority ?? 3
|
|
1025
|
+
},
|
|
1026
|
+
storage
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
function buildMissingTaskSchema(task) {
|
|
1030
|
+
const properties = {};
|
|
1031
|
+
const required = [];
|
|
1032
|
+
addRequiredStringField(properties, required, task, "repo", {
|
|
1033
|
+
title: "Repository",
|
|
1034
|
+
description: "Name of the repository for this task.",
|
|
1035
|
+
minLength: 1
|
|
1036
|
+
});
|
|
1037
|
+
addRequiredStringField(properties, required, task, "task_code", {
|
|
1038
|
+
title: "Task Code",
|
|
1039
|
+
description: "Unique task code in this repository.",
|
|
1040
|
+
minLength: 1
|
|
1041
|
+
});
|
|
1042
|
+
addRequiredStringField(properties, required, task, "phase", {
|
|
1043
|
+
title: "Phase",
|
|
1044
|
+
description: "Project phase or milestone for this task.",
|
|
1045
|
+
minLength: 1
|
|
1046
|
+
});
|
|
1047
|
+
addRequiredStringField(properties, required, task, "title", {
|
|
1048
|
+
title: "Title",
|
|
1049
|
+
description: "Short task title.",
|
|
1050
|
+
minLength: 3,
|
|
1051
|
+
maxLength: 100
|
|
1052
|
+
});
|
|
1053
|
+
addRequiredStringField(properties, required, task, "description", {
|
|
1054
|
+
title: "Description",
|
|
1055
|
+
description: "Detailed description of the task.",
|
|
1056
|
+
minLength: 1
|
|
1057
|
+
});
|
|
1058
|
+
if (!task.status) {
|
|
1059
|
+
properties.status = {
|
|
1060
|
+
type: "string",
|
|
1061
|
+
title: "Status",
|
|
1062
|
+
description: "Initial task status.",
|
|
1063
|
+
enum: ["backlog", "pending"],
|
|
1064
|
+
default: "backlog"
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
if (task.priority === void 0) {
|
|
1068
|
+
properties.priority = {
|
|
1069
|
+
type: "integer",
|
|
1070
|
+
title: "Priority",
|
|
1071
|
+
description: "Task priority from 1 to 5.",
|
|
1072
|
+
minimum: 1,
|
|
1073
|
+
maximum: 5,
|
|
1074
|
+
default: 3
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
return {
|
|
1078
|
+
type: "object",
|
|
1079
|
+
properties,
|
|
1080
|
+
required
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
function addRequiredStringField(properties, required, task, field, schema) {
|
|
1084
|
+
if (typeof task[field] === "string" && task[field].trim()) {
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
properties[field] = {
|
|
1088
|
+
type: "string",
|
|
1089
|
+
...schema
|
|
1090
|
+
};
|
|
1091
|
+
required.push(field);
|
|
1092
|
+
}
|
|
1093
|
+
async function handleTaskUpdate(args, storage, vectors2) {
|
|
1094
|
+
const updateData = TaskUpdateSchema.parse(args);
|
|
1095
|
+
const { repo, id, ids, comment, force, ...updates } = updateData;
|
|
1096
|
+
const targetIds = ids || (id ? [id] : []);
|
|
1097
|
+
if (targetIds.length === 0) {
|
|
1098
|
+
throw new Error("Either 'id' or 'ids' must be provided for update");
|
|
1099
|
+
}
|
|
1100
|
+
let updatedCount = 0;
|
|
1101
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1102
|
+
const isStatusChangingGlobal = updates.status !== void 0;
|
|
1103
|
+
for (const targetId of targetIds) {
|
|
1104
|
+
const existingTask = storage.tasks.getTaskById(targetId);
|
|
1105
|
+
if (!existingTask) {
|
|
1106
|
+
if (id) throw new Error(`Task not found: ${targetId}`);
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
const isStatusChanging = isStatusChangingGlobal && updates.status !== existingTask.status;
|
|
1110
|
+
if (isStatusChanging && !force) {
|
|
1111
|
+
if (!comment || comment.trim() === "") {
|
|
1112
|
+
throw new Error("comment is required when changing task status");
|
|
1113
|
+
}
|
|
1114
|
+
if ((existingTask.status === "backlog" || existingTask.status === "pending" || existingTask.status === "blocked") && updates.status === "completed") {
|
|
1115
|
+
throw new Error(`Cannot transition task ${targetId} from '${existingTask.status}' directly to 'completed'. Must be 'in_progress' first.`);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
if (updates.status === "completed" && isStatusChanging && updates.est_tokens === void 0) {
|
|
1119
|
+
throw new Error("est_tokens is required when changing task status to completed");
|
|
1120
|
+
}
|
|
1121
|
+
if (updates.task_code && storage.tasks.isTaskCodeDuplicate(repo, updates.task_code, targetId)) {
|
|
1122
|
+
throw new Error(`Duplicate task_code: '${updates.task_code}' already exists`);
|
|
1123
|
+
}
|
|
1124
|
+
const finalUpdates = { ...updates };
|
|
1125
|
+
if (updates.status === "completed") finalUpdates.finished_at = now;
|
|
1126
|
+
else if (updates.status === "canceled") finalUpdates.canceled_at = now;
|
|
1127
|
+
else if (updates.status === "in_progress" && existingTask.status !== "in_progress") finalUpdates.in_progress_at = now;
|
|
1128
|
+
storage.tasks.updateTask(targetId, finalUpdates);
|
|
1129
|
+
if (comment !== void 0 || isStatusChanging) {
|
|
1130
|
+
storage.tasks.insertTaskComment({
|
|
1131
|
+
id: randomUUID2(),
|
|
1132
|
+
task_id: targetId,
|
|
1133
|
+
repo,
|
|
1134
|
+
comment: comment || `Status updated to ${updates.status}`,
|
|
1135
|
+
agent: updates.agent || existingTask.agent || "unknown",
|
|
1136
|
+
role: updates.role || existingTask.role || "unknown",
|
|
1137
|
+
model: updates.model || "unknown",
|
|
1138
|
+
previous_status: isStatusChanging ? existingTask.status : null,
|
|
1139
|
+
next_status: isStatusChanging ? updates.status : null,
|
|
1140
|
+
created_at: now
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
if (updates.status === "completed" && existingTask.status !== "completed") {
|
|
1144
|
+
await archiveTaskToMemory(targetId, repo, storage, vectors2);
|
|
1145
|
+
}
|
|
1146
|
+
updatedCount++;
|
|
1147
|
+
}
|
|
1148
|
+
return createMcpResponse(
|
|
1149
|
+
{
|
|
1150
|
+
success: true,
|
|
1151
|
+
id: id || void 0,
|
|
1152
|
+
ids: ids || void 0,
|
|
1153
|
+
repo,
|
|
1154
|
+
status: updates.status,
|
|
1155
|
+
updatedCount,
|
|
1156
|
+
updatedFields: Object.keys(updates)
|
|
1157
|
+
},
|
|
1158
|
+
`Updated ${updatedCount} task(s) in repo "${repo}".`
|
|
1159
|
+
);
|
|
1160
|
+
}
|
|
1161
|
+
async function handleTaskDelete(args, storage) {
|
|
1162
|
+
const { repo, id, ids } = TaskDeleteSchema.parse(args);
|
|
1163
|
+
const targetIds = ids || (id ? [id] : []);
|
|
1164
|
+
if (targetIds.length === 0) {
|
|
1165
|
+
throw new Error("Either 'id' or 'ids' must be provided for deletion");
|
|
1166
|
+
}
|
|
1167
|
+
for (const targetId of targetIds) {
|
|
1168
|
+
storage.tasks.deleteTask(targetId);
|
|
1169
|
+
}
|
|
1170
|
+
return createMcpResponse(
|
|
1171
|
+
{
|
|
1172
|
+
success: true,
|
|
1173
|
+
id: id || void 0,
|
|
1174
|
+
ids: ids || void 0,
|
|
1175
|
+
repo,
|
|
1176
|
+
deletedCount: targetIds.length
|
|
1177
|
+
},
|
|
1178
|
+
`Deleted ${targetIds.length} task(s) from repo "${repo}".`
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// src/mcp/tools/memory.synthesize.ts
|
|
1183
|
+
async function handleMemorySynthesize(params, db2, vectors2, options = {}) {
|
|
1184
|
+
const validated = MemorySynthesizeSchema.parse(params);
|
|
1185
|
+
const session2 = options.session;
|
|
1186
|
+
if (!options.sampleMessage || !session2?.supportsSampling) {
|
|
1187
|
+
throw new Error("Client does not advertise MCP sampling support");
|
|
1188
|
+
}
|
|
1189
|
+
const repo = await resolveRepository(validated.repo, session2, options.elicit);
|
|
1190
|
+
if (!repo) {
|
|
1191
|
+
throw new Error("repo is required when repo cannot be inferred from active MCP roots");
|
|
1192
|
+
}
|
|
1193
|
+
const recap = await handleMemoryRecap({ repo, limit: 8, offset: 0 }, db2);
|
|
1194
|
+
const recapText = getPrimaryTextContent(recap);
|
|
1195
|
+
const summary = validated.include_summary ? db2.summaries.getSummary(repo)?.summary : "";
|
|
1196
|
+
const taskSnapshot = validated.include_tasks ? await handleTaskList({ repo, status: "backlog,pending,in_progress,blocked", limit: 15, offset: 0 }, db2) : null;
|
|
1197
|
+
const taskText = taskSnapshot ? getPrimaryTextContent(taskSnapshot) : "";
|
|
1198
|
+
const systemPrompt = [
|
|
1199
|
+
"You are a repository memory synthesizer.",
|
|
1200
|
+
"Answer strictly from grounded MCP context and tool results.",
|
|
1201
|
+
"If memory is insufficient, say so explicitly instead of inventing details.",
|
|
1202
|
+
"Prefer concise, technical answers with explicit caveats when evidence is incomplete."
|
|
1203
|
+
].join(" ");
|
|
1204
|
+
const contextBlock = [
|
|
1205
|
+
`Repository: ${repo}`,
|
|
1206
|
+
validated.current_file_path ? `Current file: ${validated.current_file_path}` : "",
|
|
1207
|
+
summary ? `Summary:
|
|
1208
|
+
${summary}` : "",
|
|
1209
|
+
recapText ? `Recent context:
|
|
1210
|
+
${recapText}` : "",
|
|
1211
|
+
taskText ? `Active tasks:
|
|
1212
|
+
${taskText}` : ""
|
|
1213
|
+
].filter(Boolean).join("\n\n");
|
|
1214
|
+
const messages = [
|
|
1215
|
+
{
|
|
1216
|
+
role: "user",
|
|
1217
|
+
content: {
|
|
1218
|
+
type: "text",
|
|
1219
|
+
text: `Objective: ${validated.objective}
|
|
1220
|
+
|
|
1221
|
+
Grounding context:
|
|
1222
|
+
${contextBlock || "No additional context provided."}`
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
];
|
|
1226
|
+
const toolDefinitions = buildSamplingTools(session2, validated.use_tools);
|
|
1227
|
+
let lastResponse = null;
|
|
1228
|
+
let totalToolCalls = 0;
|
|
1229
|
+
let iterations = 0;
|
|
1230
|
+
while (iterations < validated.max_iterations) {
|
|
1231
|
+
iterations += 1;
|
|
1232
|
+
const response = await options.sampleMessage({
|
|
1233
|
+
messages,
|
|
1234
|
+
systemPrompt,
|
|
1235
|
+
maxTokens: validated.max_tokens,
|
|
1236
|
+
tools: toolDefinitions.length > 0 ? toolDefinitions : void 0,
|
|
1237
|
+
toolChoice: toolDefinitions.length > 0 ? { mode: iterations === validated.max_iterations ? "none" : "auto" } : void 0,
|
|
1238
|
+
modelPreferences: {
|
|
1239
|
+
intelligencePriority: 0.9,
|
|
1240
|
+
speedPriority: 0.4
|
|
1241
|
+
}
|
|
1242
|
+
});
|
|
1243
|
+
lastResponse = response;
|
|
1244
|
+
messages.push({
|
|
1245
|
+
role: "assistant",
|
|
1246
|
+
content: response.content
|
|
1247
|
+
});
|
|
1248
|
+
const toolUses = extractToolUses(response.content);
|
|
1249
|
+
if (toolUses.length === 0) {
|
|
1250
|
+
break;
|
|
1251
|
+
}
|
|
1252
|
+
totalToolCalls += toolUses.length;
|
|
1253
|
+
const toolResults = await Promise.all(
|
|
1254
|
+
toolUses.map(async (toolUse) => ({
|
|
1255
|
+
type: "tool_result",
|
|
1256
|
+
toolUseId: toolUse.id,
|
|
1257
|
+
content: [
|
|
1258
|
+
{
|
|
1259
|
+
type: "text",
|
|
1260
|
+
text: await executeSamplingTool(toolUse.name, toolUse.input, db2, vectors2)
|
|
1261
|
+
}
|
|
1262
|
+
]
|
|
1263
|
+
}))
|
|
1264
|
+
);
|
|
1265
|
+
messages.push({
|
|
1266
|
+
role: "user",
|
|
1267
|
+
content: toolResults
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
const answer = lastResponse ? extractTextFromContent(lastResponse.content).trim() : "";
|
|
1271
|
+
if (!answer) {
|
|
1272
|
+
throw new Error("Sampling did not return a final text answer");
|
|
1273
|
+
}
|
|
1274
|
+
logger.info("[MCP] memory.synthesize", {
|
|
1275
|
+
repo,
|
|
1276
|
+
objective: validated.objective,
|
|
1277
|
+
iterations,
|
|
1278
|
+
toolCalls: totalToolCalls
|
|
1279
|
+
});
|
|
1280
|
+
return createMcpResponse(
|
|
1281
|
+
{
|
|
1282
|
+
repo,
|
|
1283
|
+
objective: validated.objective,
|
|
1284
|
+
answer,
|
|
1285
|
+
model: lastResponse?.model,
|
|
1286
|
+
stopReason: lastResponse?.stopReason,
|
|
1287
|
+
iterations,
|
|
1288
|
+
toolCalls: totalToolCalls
|
|
1289
|
+
},
|
|
1290
|
+
`Synthesized answer for "${validated.objective}" using repository "${repo}".`,
|
|
1291
|
+
{
|
|
1292
|
+
structuredContentPathHint: "answer"
|
|
1293
|
+
}
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
async function resolveRepository(repo, session2, elicit) {
|
|
1297
|
+
if (repo) return normalizeRepo(repo);
|
|
1298
|
+
const inferredRepo = inferRepoFromSession(session2);
|
|
1299
|
+
if (inferredRepo) return normalizeRepo(inferredRepo);
|
|
1300
|
+
if (!session2?.supportsElicitationForm || !elicit) {
|
|
1301
|
+
return void 0;
|
|
1302
|
+
}
|
|
1303
|
+
const elicited = extractAcceptedElicitationContent(
|
|
1304
|
+
await elicit({
|
|
1305
|
+
mode: "form",
|
|
1306
|
+
message: "Repository tidak bisa diinfer dari roots aktif. Pilih repository yang ingin disintesis.",
|
|
1307
|
+
requestedSchema: {
|
|
1308
|
+
type: "object",
|
|
1309
|
+
properties: {
|
|
1310
|
+
repo: {
|
|
1311
|
+
type: "string",
|
|
1312
|
+
title: "Repository",
|
|
1313
|
+
description: "Nama repository yang akan dipakai untuk sintesis memori.",
|
|
1314
|
+
minLength: 1
|
|
1315
|
+
}
|
|
1316
|
+
},
|
|
1317
|
+
required: ["repo"]
|
|
1318
|
+
}
|
|
1319
|
+
})
|
|
1320
|
+
);
|
|
1321
|
+
return typeof elicited.repo === "string" && elicited.repo.trim() ? normalizeRepo(elicited.repo.trim()) : void 0;
|
|
1322
|
+
}
|
|
1323
|
+
function buildSamplingTools(session2, useTools) {
|
|
1324
|
+
if (!useTools || !session2?.supportsSamplingTools) {
|
|
1325
|
+
return [];
|
|
1326
|
+
}
|
|
1327
|
+
return [
|
|
1328
|
+
{
|
|
1329
|
+
name: "memory_search",
|
|
1330
|
+
description: "Search local repository memories for relevant context.",
|
|
1331
|
+
inputSchema: {
|
|
1332
|
+
type: "object",
|
|
1333
|
+
properties: {
|
|
1334
|
+
repo: { type: "string" },
|
|
1335
|
+
query: { type: "string" },
|
|
1336
|
+
limit: { type: "number", minimum: 1, maximum: 10 }
|
|
1337
|
+
},
|
|
1338
|
+
required: ["repo", "query"]
|
|
1339
|
+
}
|
|
1340
|
+
},
|
|
1341
|
+
{
|
|
1342
|
+
name: "memory_recap",
|
|
1343
|
+
description: "Fetch a recap of the most recent memories and active tasks.",
|
|
1344
|
+
inputSchema: {
|
|
1345
|
+
type: "object",
|
|
1346
|
+
properties: {
|
|
1347
|
+
repo: { type: "string" },
|
|
1348
|
+
limit: { type: "number", minimum: 1, maximum: 20 }
|
|
1349
|
+
},
|
|
1350
|
+
required: ["repo"]
|
|
1351
|
+
}
|
|
1352
|
+
},
|
|
1353
|
+
{
|
|
1354
|
+
name: "task_list",
|
|
1355
|
+
description: "List tasks by status or search term for the repository.",
|
|
1356
|
+
inputSchema: {
|
|
1357
|
+
type: "object",
|
|
1358
|
+
properties: {
|
|
1359
|
+
repo: { type: "string" },
|
|
1360
|
+
status: { type: "string" },
|
|
1361
|
+
search: { type: "string" },
|
|
1362
|
+
limit: { type: "number", minimum: 1, maximum: 20 }
|
|
1363
|
+
},
|
|
1364
|
+
required: ["repo"]
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
];
|
|
1368
|
+
}
|
|
1369
|
+
async function executeSamplingTool(toolName, rawInput, db2, vectors2) {
|
|
1370
|
+
switch (toolName) {
|
|
1371
|
+
case "memory_search": {
|
|
1372
|
+
const response = await handleMemorySearch(
|
|
1373
|
+
{
|
|
1374
|
+
repo: rawInput.repo,
|
|
1375
|
+
query: rawInput.query,
|
|
1376
|
+
limit: rawInput.limit ?? 5
|
|
1377
|
+
},
|
|
1378
|
+
db2,
|
|
1379
|
+
vectors2
|
|
1380
|
+
);
|
|
1381
|
+
return getPrimaryTextContent(response);
|
|
1382
|
+
}
|
|
1383
|
+
case "memory_recap": {
|
|
1384
|
+
const response = await handleMemoryRecap(
|
|
1385
|
+
{
|
|
1386
|
+
repo: rawInput.repo,
|
|
1387
|
+
limit: rawInput.limit ?? 8,
|
|
1388
|
+
offset: 0
|
|
1389
|
+
},
|
|
1390
|
+
db2
|
|
1391
|
+
);
|
|
1392
|
+
return getPrimaryTextContent(response);
|
|
1393
|
+
}
|
|
1394
|
+
case "task_list": {
|
|
1395
|
+
const response = await handleTaskList(
|
|
1396
|
+
{
|
|
1397
|
+
repo: rawInput.repo,
|
|
1398
|
+
status: rawInput.status,
|
|
1399
|
+
search: rawInput.search,
|
|
1400
|
+
limit: rawInput.limit ?? 10,
|
|
1401
|
+
offset: 0
|
|
1402
|
+
},
|
|
1403
|
+
db2
|
|
1404
|
+
);
|
|
1405
|
+
return getPrimaryTextContent(response);
|
|
1406
|
+
}
|
|
1407
|
+
default:
|
|
1408
|
+
throw new Error(`Unsupported sampling tool: ${toolName}`);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
// src/mcp/tools/memory.delete.ts
|
|
1413
|
+
async function handleMemoryDelete(params, db2, vectors2, onProgress) {
|
|
1414
|
+
const { id, ids, repo } = MemoryDeleteSchema.parse(params);
|
|
1415
|
+
const targetIds = ids || (id ? [id] : []);
|
|
1416
|
+
if (targetIds.length === 0) {
|
|
1417
|
+
throw new Error("Either 'id' or 'ids' must be provided for deletion");
|
|
1418
|
+
}
|
|
1419
|
+
let deletedCount = 0;
|
|
1420
|
+
let lastRepo = repo || "unknown";
|
|
1421
|
+
const total = targetIds.length;
|
|
1422
|
+
let progress = 0;
|
|
1423
|
+
for (const targetId of targetIds) {
|
|
1424
|
+
if (onProgress) {
|
|
1425
|
+
onProgress(progress, total);
|
|
1426
|
+
}
|
|
1427
|
+
const existing = db2.memories.getById(targetId);
|
|
1428
|
+
if (existing) {
|
|
1429
|
+
lastRepo = existing.scope.repo;
|
|
1430
|
+
db2.memories.delete(targetId);
|
|
1431
|
+
await vectors2.remove(targetId);
|
|
1432
|
+
deletedCount++;
|
|
1433
|
+
} else if (id) {
|
|
1434
|
+
throw new Error(`Memory not found: ${targetId}`);
|
|
1435
|
+
}
|
|
1436
|
+
progress++;
|
|
1437
|
+
}
|
|
1438
|
+
if (onProgress) {
|
|
1439
|
+
onProgress(progress, total);
|
|
1440
|
+
}
|
|
1441
|
+
logger.info("[MCP] memory.delete", { repo: lastRepo, count: deletedCount });
|
|
1442
|
+
return createMcpResponse(
|
|
1443
|
+
{
|
|
1444
|
+
success: true,
|
|
1445
|
+
id: id || void 0,
|
|
1446
|
+
ids: ids || void 0,
|
|
1447
|
+
repo: lastRepo,
|
|
1448
|
+
deletedCount
|
|
1449
|
+
},
|
|
1450
|
+
`Deleted ${deletedCount} memory entry(ies) from repo "${lastRepo}".`,
|
|
1451
|
+
{
|
|
1452
|
+
structuredContentPathHint: "deletedCount",
|
|
1453
|
+
resourceLinks: [
|
|
1454
|
+
{
|
|
1455
|
+
uri: `repository://${encodeURIComponent(lastRepo)}/memories`,
|
|
1456
|
+
name: `Memory Index (${lastRepo})`,
|
|
1457
|
+
description: "Repository memory index after deletion",
|
|
1458
|
+
mimeType: "application/json",
|
|
1459
|
+
annotations: {
|
|
1460
|
+
audience: ["assistant"],
|
|
1461
|
+
priority: 0.5
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
]
|
|
1465
|
+
}
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// src/mcp/tools/memory.acknowledge.ts
|
|
1470
|
+
async function handleMemoryAcknowledge(params, db2) {
|
|
1471
|
+
const validated = MemoryAcknowledgeSchema.parse(params);
|
|
1472
|
+
const memory = db2.memories.getById(validated.memory_id);
|
|
1473
|
+
if (!memory) {
|
|
1474
|
+
throw new Error(`Memory with ID ${validated.memory_id} not found.`);
|
|
1475
|
+
}
|
|
1476
|
+
if (validated.status === "used") {
|
|
1477
|
+
db2.memories.incrementRecallCount(memory.id);
|
|
1478
|
+
logger.info("[MCP] memory.acknowledge - used", { id: memory.id, context: validated.application_context });
|
|
1479
|
+
} else if (validated.status === "contradictory") {
|
|
1480
|
+
logger.warn("[MCP] memory.acknowledge - contradiction reported", {
|
|
1481
|
+
id: memory.id,
|
|
1482
|
+
context: validated.application_context
|
|
1483
|
+
});
|
|
1484
|
+
} else {
|
|
1485
|
+
logger.info("[MCP] memory.acknowledge - irrelevant", { id: memory.id, context: validated.application_context });
|
|
1486
|
+
}
|
|
1487
|
+
return createMcpResponse(
|
|
1488
|
+
{
|
|
1489
|
+
success: true,
|
|
1490
|
+
id: memory.id,
|
|
1491
|
+
status: validated.status
|
|
1492
|
+
},
|
|
1493
|
+
`Acknowledged memory ${memory.id} as "${validated.status}".`,
|
|
1494
|
+
{
|
|
1495
|
+
structuredContentPathHint: "status"
|
|
1496
|
+
}
|
|
1497
|
+
);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
// src/mcp/tools/memory.detail.ts
|
|
1501
|
+
async function handleMemoryDetail(args, storage) {
|
|
1502
|
+
const { id } = MemoryDetailSchema.parse(args);
|
|
1503
|
+
const memory = storage.memories.getById(id);
|
|
1504
|
+
if (!memory) {
|
|
1505
|
+
throw new Error(`Memory not found: ${id}`);
|
|
1506
|
+
}
|
|
1507
|
+
storage.memories.incrementHitCount(id);
|
|
1508
|
+
const summary = `Memory [${memory.type}] ${memory.title}: ${memory.content.substring(0, 100)}${memory.content.length > 100 ? "..." : ""}`;
|
|
1509
|
+
return createMcpResponse(memory, summary, {
|
|
1510
|
+
contentSummary: summary,
|
|
1511
|
+
includeSerializedStructuredContent: true,
|
|
1512
|
+
resourceLinks: [
|
|
1513
|
+
{
|
|
1514
|
+
uri: `memory://${id}`,
|
|
1515
|
+
name: `Memory: ${memory.title}`,
|
|
1516
|
+
mimeType: "application/json"
|
|
1517
|
+
}
|
|
1518
|
+
]
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// src/mcp/tools/task.get.ts
|
|
1523
|
+
async function handleTaskGet(args, storage) {
|
|
1524
|
+
const { repo, id, task_code } = TaskGetSchema.parse(args);
|
|
1525
|
+
let task;
|
|
1526
|
+
if (id) {
|
|
1527
|
+
task = storage.tasks.getTaskById(id);
|
|
1528
|
+
} else if (task_code) {
|
|
1529
|
+
task = storage.tasks.getTaskByCode(repo, task_code);
|
|
1530
|
+
} else {
|
|
1531
|
+
throw new Error("Either id or task_code must be provided");
|
|
1532
|
+
}
|
|
1533
|
+
if (!task) {
|
|
1534
|
+
throw new Error(`Task not found: ${id || task_code} in repo ${repo}`);
|
|
1535
|
+
}
|
|
1536
|
+
const summary = `Task [${task.task_code}] ${task.title} (${task.status})`;
|
|
1537
|
+
return createMcpResponse(task, summary, {
|
|
1538
|
+
contentSummary: summary,
|
|
1539
|
+
includeSerializedStructuredContent: true,
|
|
1540
|
+
resourceLinks: [
|
|
1541
|
+
{
|
|
1542
|
+
uri: `task://${task.id}`,
|
|
1543
|
+
name: `Task: ${task.title}`,
|
|
1544
|
+
mimeType: "application/json"
|
|
1545
|
+
}
|
|
1546
|
+
]
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
// src/mcp/router.ts
|
|
1551
|
+
function createRouter(db2, vectors2, options) {
|
|
1552
|
+
const getSessionContext = options?.getSessionContext;
|
|
1553
|
+
async function handleMethod2(method, params, signal, onProgress) {
|
|
1554
|
+
switch (method) {
|
|
1555
|
+
// ---- tools ----
|
|
1556
|
+
case "tools/list":
|
|
1557
|
+
return listTools(getSessionContext?.(), params);
|
|
1558
|
+
case "tools/call":
|
|
1559
|
+
return await handleToolCall(
|
|
1560
|
+
params,
|
|
1561
|
+
params?.signal,
|
|
1562
|
+
onProgress
|
|
1563
|
+
);
|
|
1564
|
+
// ---- resources ----
|
|
1565
|
+
case "resources/list":
|
|
1566
|
+
return listResources(getSessionContext?.(), params);
|
|
1567
|
+
case "resources/templates/list":
|
|
1568
|
+
return listResourceTemplates(params);
|
|
1569
|
+
case "resources/read":
|
|
1570
|
+
return readResource(params?.uri, db2, getSessionContext?.());
|
|
1571
|
+
// ---- prompts ----
|
|
1572
|
+
case "prompts/list":
|
|
1573
|
+
return listPrompts(db2, getSessionContext?.(), params);
|
|
1574
|
+
case "logging/setLevel": {
|
|
1575
|
+
const requestedLevel = typeof params?.level === "string" ? params.level : "";
|
|
1576
|
+
const previousLevel = getLogLevel();
|
|
1577
|
+
const level = setLogLevel(requestedLevel);
|
|
1578
|
+
return {
|
|
1579
|
+
level,
|
|
1580
|
+
supportedLevels: LOG_LEVEL_VALUES,
|
|
1581
|
+
previousLevel
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
case "prompts/get": {
|
|
1585
|
+
return getPrompt(params?.name, params?.arguments || {}, db2, getSessionContext?.());
|
|
1586
|
+
}
|
|
1587
|
+
case "completion/complete":
|
|
1588
|
+
return complete(params, db2, getSessionContext?.());
|
|
1589
|
+
default:
|
|
1590
|
+
throw new Error(`Unsupported method: ${method}`);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
async function handleToolCall(params, signal, onProgress) {
|
|
1594
|
+
const { name } = params || {};
|
|
1595
|
+
const args = normalizeToolArguments(params?.arguments, getSessionContext?.());
|
|
1596
|
+
const toolName = String(name).replace(/\./g, "-");
|
|
1597
|
+
let result;
|
|
1598
|
+
const repo = args?.repo || args?.scope?.repo || "unknown";
|
|
1599
|
+
switch (toolName) {
|
|
1600
|
+
case "memory-store":
|
|
1601
|
+
result = await handleMemoryStore(args, db2, vectors2);
|
|
1602
|
+
break;
|
|
1603
|
+
case "memory-acknowledge":
|
|
1604
|
+
result = await handleMemoryAcknowledge(args, db2);
|
|
1605
|
+
break;
|
|
1606
|
+
case "memory-update":
|
|
1607
|
+
result = await handleMemoryUpdate(args, db2, vectors2);
|
|
1608
|
+
break;
|
|
1609
|
+
case "memory-recap":
|
|
1610
|
+
result = await handleMemoryRecap(args, db2);
|
|
1611
|
+
break;
|
|
1612
|
+
case "memory-search":
|
|
1613
|
+
result = await handleMemorySearch(args, db2, vectors2);
|
|
1614
|
+
break;
|
|
1615
|
+
case "memory-summarize":
|
|
1616
|
+
result = await handleMemorySummarize(args, db2);
|
|
1617
|
+
break;
|
|
1618
|
+
case "memory-synthesize":
|
|
1619
|
+
result = await handleMemorySynthesize(args, db2, vectors2, {
|
|
1620
|
+
session: getSessionContext?.(),
|
|
1621
|
+
sampleMessage: options?.sampleMessage,
|
|
1622
|
+
elicit: options?.elicit
|
|
1623
|
+
});
|
|
1624
|
+
break;
|
|
1625
|
+
case "memory-delete":
|
|
1626
|
+
case "memory-bulk-delete":
|
|
1627
|
+
result = await handleMemoryDelete(args, db2, vectors2, onProgress);
|
|
1628
|
+
break;
|
|
1629
|
+
case "memory-detail":
|
|
1630
|
+
result = await handleMemoryDetail(args, db2);
|
|
1631
|
+
break;
|
|
1632
|
+
case "task-create":
|
|
1633
|
+
result = await handleTaskCreate(args, db2);
|
|
1634
|
+
break;
|
|
1635
|
+
case "task-create-interactive":
|
|
1636
|
+
result = await handleTaskCreateInteractive(args, db2, {
|
|
1637
|
+
session: getSessionContext?.(),
|
|
1638
|
+
elicit: options?.elicit
|
|
1639
|
+
});
|
|
1640
|
+
break;
|
|
1641
|
+
case "task-update":
|
|
1642
|
+
result = await handleTaskUpdate(args, db2, vectors2);
|
|
1643
|
+
break;
|
|
1644
|
+
case "task-delete":
|
|
1645
|
+
result = await handleTaskDelete(args, db2);
|
|
1646
|
+
break;
|
|
1647
|
+
case "task-list":
|
|
1648
|
+
result = await handleTaskList(args, db2);
|
|
1649
|
+
break;
|
|
1650
|
+
case "task-detail":
|
|
1651
|
+
result = await handleTaskGet(args, db2);
|
|
1652
|
+
break;
|
|
1653
|
+
default:
|
|
1654
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
1655
|
+
}
|
|
1656
|
+
try {
|
|
1657
|
+
const actionType = toolName.split("-")[1] || toolName;
|
|
1658
|
+
const res = result;
|
|
1659
|
+
const sc = res?.structuredData;
|
|
1660
|
+
const logOptions = {
|
|
1661
|
+
query: args?.query || args?.title || args?.task_code || (toolName === "memory-recap" ? `Offset: ${args?.offset || 0}` : void 0),
|
|
1662
|
+
response: result,
|
|
1663
|
+
memoryId: args?.id || args?.memory_id || sc?.id,
|
|
1664
|
+
taskId: args?.id || args?.task_id || sc?.id,
|
|
1665
|
+
resultCount: Array.isArray(sc?.results) ? sc.results.length : sc?.count || 0
|
|
1666
|
+
};
|
|
1667
|
+
db2.actions.logAction(actionType, repo, logOptions);
|
|
1668
|
+
} catch (e) {
|
|
1669
|
+
logger.error("Failed to log action", { toolName, error: String(e) });
|
|
1670
|
+
}
|
|
1671
|
+
const affectedResources = collectAffectedResourceUris(toolName, args, result);
|
|
1672
|
+
if (affectedResources.length > 0) {
|
|
1673
|
+
options?.onResourcesMutated?.(affectedResources);
|
|
1674
|
+
}
|
|
1675
|
+
return result;
|
|
1676
|
+
}
|
|
1677
|
+
return handleMethod2;
|
|
1678
|
+
}
|
|
1679
|
+
function listTools(session2, params) {
|
|
1680
|
+
const tools = getAvailableToolDefinitions(session2);
|
|
1681
|
+
const limit = normalizePageLimit(params?.limit, tools.length || 1);
|
|
1682
|
+
const start = decodeCursor(params?.cursor);
|
|
1683
|
+
const compliantTools = tools.map((tool) => {
|
|
1684
|
+
const { name, description, inputSchema } = tool;
|
|
1685
|
+
return { name, description, inputSchema };
|
|
1686
|
+
});
|
|
1687
|
+
const page = compliantTools.slice(start, start + limit);
|
|
1688
|
+
const nextCursor = start + limit < tools.length ? encodeCursor(start + limit) : void 0;
|
|
1689
|
+
return {
|
|
1690
|
+
tools: page,
|
|
1691
|
+
nextCursor
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
function getAvailableToolDefinitions(session2) {
|
|
1695
|
+
return TOOL_DEFINITIONS.filter((tool) => {
|
|
1696
|
+
if (tool.name === "memory-synthesize" && !session2?.supportsSampling) {
|
|
1697
|
+
return false;
|
|
1698
|
+
}
|
|
1699
|
+
if (tool.name === "task-create-interactive" && !session2?.supportsElicitationForm) {
|
|
1700
|
+
return false;
|
|
1701
|
+
}
|
|
1702
|
+
return true;
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
function collectAffectedResourceUris(toolName, args, result) {
|
|
1706
|
+
const res = result;
|
|
1707
|
+
const repo = args?.repo || args?.scope?.repo || res?.data?.repo;
|
|
1708
|
+
const uris = /* @__PURE__ */ new Set();
|
|
1709
|
+
const touchesMemory = toolName.startsWith("memory-") || toolName === "task-update" || toolName === "task-delete";
|
|
1710
|
+
const touchesTasks = toolName.startsWith("task-");
|
|
1711
|
+
if (touchesMemory && repo) {
|
|
1712
|
+
uris.add(`repository://${encodeURIComponent(repo)}/memories`);
|
|
1713
|
+
}
|
|
1714
|
+
if (touchesTasks && repo) {
|
|
1715
|
+
uris.add(`repository://${encodeURIComponent(repo)}/tasks`);
|
|
1716
|
+
}
|
|
1717
|
+
if (repo) {
|
|
1718
|
+
uris.add("repository://index");
|
|
1719
|
+
}
|
|
1720
|
+
const memoryId = args?.id || args?.memory_id || res?.data?.id;
|
|
1721
|
+
if (typeof memoryId === "string" && /^[0-9a-f-]{36}$/i.test(memoryId) && toolName.startsWith("memory-")) {
|
|
1722
|
+
uris.add(`memory://${memoryId}`);
|
|
1723
|
+
}
|
|
1724
|
+
const taskId = args?.id || args?.task_id || res?.structuredData?.id;
|
|
1725
|
+
if (typeof taskId === "string" && /^[0-9a-f-]{36}$/i.test(taskId) && toolName.startsWith("task-")) {
|
|
1726
|
+
uris.add(`task://${taskId}`);
|
|
1727
|
+
}
|
|
1728
|
+
return [...uris];
|
|
1729
|
+
}
|
|
1730
|
+
function normalizeToolArguments(args, session2) {
|
|
1731
|
+
if (!args || typeof args !== "object") {
|
|
1732
|
+
return args;
|
|
1733
|
+
}
|
|
1734
|
+
const anyArgs = args;
|
|
1735
|
+
const nextArgs = {
|
|
1736
|
+
...anyArgs,
|
|
1737
|
+
scope: anyArgs.scope ? { ...anyArgs.scope } : void 0
|
|
1738
|
+
};
|
|
1739
|
+
validateRootBoundPath(nextArgs.current_file_path, "current_file_path", session2);
|
|
1740
|
+
validateRootBoundPath(nextArgs.doc_path, "doc_path", session2);
|
|
1741
|
+
if (!nextArgs.repo) {
|
|
1742
|
+
nextArgs.repo = inferRepoFromSession(session2);
|
|
1743
|
+
}
|
|
1744
|
+
const scope = nextArgs.scope;
|
|
1745
|
+
if (scope && !scope.repo) {
|
|
1746
|
+
scope.repo = nextArgs.repo ?? inferRepoFromSession(session2);
|
|
1747
|
+
}
|
|
1748
|
+
if (typeof nextArgs.current_file_path === "string" && scope) {
|
|
1749
|
+
const containingRoot = path2.isAbsolute(nextArgs.current_file_path) ? findContainingRoot(nextArgs.current_file_path, session2) : null;
|
|
1750
|
+
if (containingRoot) {
|
|
1751
|
+
const relativePath = path2.relative(containingRoot, path2.resolve(nextArgs.current_file_path));
|
|
1752
|
+
const relativeFolder = path2.dirname(relativePath);
|
|
1753
|
+
if (relativeFolder && relativeFolder !== "." && !scope.folder) {
|
|
1754
|
+
scope.folder = relativeFolder;
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
return nextArgs;
|
|
1759
|
+
}
|
|
1760
|
+
function validateRootBoundPath(value, field, session2) {
|
|
1761
|
+
if (typeof value !== "string" || !path2.isAbsolute(value)) {
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
if (!isPathWithinRoots(value, session2)) {
|
|
1765
|
+
throw new Error(`${field} must stay within the active MCP roots`);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
function normalizePageLimit(value, fallback) {
|
|
1769
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
1770
|
+
return Math.max(1, fallback);
|
|
1771
|
+
}
|
|
1772
|
+
return Math.min(value, 100);
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
// src/mcp/storage/vectors.ts
|
|
1776
|
+
import { pipeline } from "@xenova/transformers";
|
|
1777
|
+
var RealVectorStore = class {
|
|
1778
|
+
db;
|
|
1779
|
+
extractor = null;
|
|
1780
|
+
modelName = "Xenova/all-MiniLM-L6-v2";
|
|
1781
|
+
constructor(db2) {
|
|
1782
|
+
this.db = db2;
|
|
1783
|
+
}
|
|
1784
|
+
/**
|
|
1785
|
+
* Triggers background loading of the vector model.
|
|
1786
|
+
* Useful for avoiding timeouts on the first search/upsert request.
|
|
1787
|
+
*/
|
|
1788
|
+
async initialize() {
|
|
1789
|
+
await this.getExtractor();
|
|
1790
|
+
}
|
|
1791
|
+
async getExtractor() {
|
|
1792
|
+
if (!this.extractor) {
|
|
1793
|
+
this.extractor = await pipeline("feature-extraction", this.modelName);
|
|
1794
|
+
}
|
|
1795
|
+
return this.extractor;
|
|
1796
|
+
}
|
|
1797
|
+
cosineSimilarity(v1, v2) {
|
|
1798
|
+
let dotProduct = 0;
|
|
1799
|
+
let mag1 = 0;
|
|
1800
|
+
let mag2 = 0;
|
|
1801
|
+
for (let i = 0; i < v1.length; i++) {
|
|
1802
|
+
dotProduct += v1[i] * v2[i];
|
|
1803
|
+
mag1 += v1[i] * v1[i];
|
|
1804
|
+
mag2 += v2[i] * v2[i];
|
|
1805
|
+
}
|
|
1806
|
+
const mag = Math.sqrt(mag1) * Math.sqrt(mag2);
|
|
1807
|
+
return mag === 0 ? 0 : dotProduct / mag;
|
|
1808
|
+
}
|
|
1809
|
+
async upsert(id, text) {
|
|
1810
|
+
try {
|
|
1811
|
+
const extractor = await this.getExtractor();
|
|
1812
|
+
const output = await extractor(text, { pooling: "mean", normalize: true });
|
|
1813
|
+
const vector = Array.from(output.data);
|
|
1814
|
+
this.db.memories.upsertVectorEmbedding(id, vector);
|
|
1815
|
+
} catch (error) {
|
|
1816
|
+
logger.error("[Vectors] Error during upsert", { id, error: String(error) });
|
|
1817
|
+
throw error;
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
async remove(id) {
|
|
1821
|
+
if (!id) return;
|
|
1822
|
+
}
|
|
1823
|
+
async search(query, limit, repo) {
|
|
17
1824
|
try {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
1825
|
+
const extractor = await this.getExtractor();
|
|
1826
|
+
const output = await extractor(query, { pooling: "mean", normalize: true });
|
|
1827
|
+
const queryVector = Array.from(output.data);
|
|
1828
|
+
const rows = this.db.memories.getVectorCandidates(repo, 100);
|
|
1829
|
+
const results = rows.map((row) => {
|
|
1830
|
+
const memoryVector = JSON.parse(row.vector);
|
|
1831
|
+
return {
|
|
1832
|
+
id: row.memory_id,
|
|
1833
|
+
score: this.cosineSimilarity(queryVector, memoryVector)
|
|
1834
|
+
};
|
|
1835
|
+
});
|
|
1836
|
+
return results.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
1837
|
+
} catch (error) {
|
|
1838
|
+
logger.error("[Vectors] Error during search", { error: String(error) });
|
|
1839
|
+
return [];
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
};
|
|
1843
|
+
|
|
1844
|
+
// src/mcp/server.ts
|
|
1845
|
+
import fs2 from "fs";
|
|
1846
|
+
if (process.argv.includes("doctor")) {
|
|
1847
|
+
process.stderr.write("\n\u{1F3E5} MCP Local Memory - System Diagnosis\n\n");
|
|
1848
|
+
const db2 = new SQLiteStore();
|
|
1849
|
+
const dbPath = db2.getDbPath();
|
|
1850
|
+
process.stderr.write(`\u{1F4C2} Database Path: ${dbPath}
|
|
1851
|
+
`);
|
|
1852
|
+
process.stderr.write(`\u{1F4C4} Database Status: ${fs2.existsSync(dbPath) ? "\u2705 Exists" : "\u274C Not Found"}
|
|
1853
|
+
`);
|
|
1854
|
+
try {
|
|
1855
|
+
const stats = db2.system.getStats();
|
|
1856
|
+
process.stderr.write(`\u{1F4CA} Memory Count: ${stats.total} entries
|
|
1857
|
+
`);
|
|
1858
|
+
process.stderr.write(`\u2705 SQLite Connection: Functional
|
|
1859
|
+
`);
|
|
1860
|
+
} catch (err) {
|
|
1861
|
+
process.stderr.write(`\u274C SQLite Connection: Failed (${String(err)})
|
|
1862
|
+
`);
|
|
1863
|
+
}
|
|
1864
|
+
process.stderr.write(`\u{1F916} AI Model: Xenova/all-MiniLM-L6-v2
|
|
1865
|
+
`);
|
|
1866
|
+
process.stderr.write(`\u2699\uFE0F Mode: Local-First (ONNX Runtime)
|
|
1867
|
+
`);
|
|
1868
|
+
const isAutoArchive = process.env.ENABLE_AUTO_ARCHIVE === "true";
|
|
1869
|
+
process.stderr.write(`\u{1F4C9} Auto-Archive: ${isAutoArchive ? "Enabled" : "Disabled (Default)"}
|
|
1870
|
+
`);
|
|
1871
|
+
process.stderr.write("\n\u2728 Diagnosis complete.\n\n");
|
|
1872
|
+
process.exit(0);
|
|
1873
|
+
}
|
|
1874
|
+
var db = new SQLiteStore();
|
|
1875
|
+
var vectors = new RealVectorStore(db);
|
|
1876
|
+
vectors.initialize().catch((err) => {
|
|
1877
|
+
logger.warn("[Server] Initial vector model loading failed. Will retry on first use.", { error: String(err) });
|
|
38
1878
|
});
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const lowScoreArchived = db.archiveLowScoreMemories();
|
|
43
|
-
const totalArchived = (expiredArchived || 0) + (lowScoreArchived || 0);
|
|
1879
|
+
var expiredArchived = db.memories.archiveExpiredMemories();
|
|
1880
|
+
var lowScoreArchived = db.memories.archiveLowScoreMemories();
|
|
1881
|
+
var totalArchived = (expiredArchived || 0) + (lowScoreArchived || 0);
|
|
44
1882
|
if (totalArchived > 0) {
|
|
45
|
-
|
|
1883
|
+
logger.info(
|
|
1884
|
+
`[Server] Archived ${totalArchived} memories (expired: ${expiredArchived}, low-score: ${lowScoreArchived}) on startup.`
|
|
1885
|
+
);
|
|
46
1886
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return;
|
|
51
|
-
logger.error("stdout error", { error: String(err) });
|
|
1887
|
+
process.stdout.on("error", (err) => {
|
|
1888
|
+
if (err.code === "EPIPE") return;
|
|
1889
|
+
logger.error("stdout error", { error: String(err) });
|
|
52
1890
|
});
|
|
53
|
-
process.stderr.on(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
logger.error("stderr error", { error: String(err) });
|
|
1891
|
+
process.stderr.on("error", (err) => {
|
|
1892
|
+
if (err.code === "EPIPE") return;
|
|
1893
|
+
logger.error("stderr error", { error: String(err) });
|
|
57
1894
|
});
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
onResourcesMutated: (uris) => notifyUpdatedResources(uris),
|
|
1895
|
+
var session = createSessionContext();
|
|
1896
|
+
var resourceSubscriptions = /* @__PURE__ */ new Set();
|
|
1897
|
+
var logNotificationsEnabled = false;
|
|
1898
|
+
var handleMethod = createRouter(db, vectors, {
|
|
1899
|
+
getSessionContext: () => session,
|
|
1900
|
+
sampleMessage: (params) => requestClient("sampling/createMessage", params),
|
|
1901
|
+
elicit: (params) => requestClient("elicitation/create", params),
|
|
1902
|
+
onResourcesMutated: (uris) => notifyUpdatedResources(uris)
|
|
67
1903
|
});
|
|
68
1904
|
addLogSink((payload) => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
1905
|
+
if (!logNotificationsEnabled) {
|
|
1906
|
+
return;
|
|
1907
|
+
}
|
|
1908
|
+
reply({
|
|
1909
|
+
jsonrpc: "2.0",
|
|
1910
|
+
method: "notifications/message",
|
|
1911
|
+
params: payload
|
|
1912
|
+
});
|
|
77
1913
|
});
|
|
78
|
-
// Cleanup on exit
|
|
79
1914
|
process.on("SIGINT", () => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
1915
|
+
for (const pending of pendingClientRequests.values()) {
|
|
1916
|
+
pending.reject(new Error("Server stopped"));
|
|
1917
|
+
}
|
|
1918
|
+
pendingClientRequests.clear();
|
|
1919
|
+
db.close();
|
|
1920
|
+
process.exit(0);
|
|
86
1921
|
});
|
|
87
1922
|
process.on("SIGTERM", () => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
1923
|
+
for (const pending of pendingClientRequests.values()) {
|
|
1924
|
+
pending.reject(new Error("Server stopped"));
|
|
1925
|
+
}
|
|
1926
|
+
pendingClientRequests.clear();
|
|
1927
|
+
db.close();
|
|
1928
|
+
process.exit(0);
|
|
94
1929
|
});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
1930
|
+
var rl = readline.createInterface({
|
|
1931
|
+
input: process.stdin,
|
|
1932
|
+
output: process.stdout,
|
|
1933
|
+
terminal: false
|
|
99
1934
|
});
|
|
100
1935
|
function reply(payload) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
logger.error("Reply error", { error: String(err) });
|
|
107
|
-
}
|
|
1936
|
+
try {
|
|
1937
|
+
process.stdout.write(JSON.stringify(payload) + "\n");
|
|
1938
|
+
} catch (err) {
|
|
1939
|
+
logger.error("Reply error", { error: String(err) });
|
|
1940
|
+
}
|
|
108
1941
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
1942
|
+
var isInitialized = false;
|
|
1943
|
+
var activeRequests = /* @__PURE__ */ new Map();
|
|
1944
|
+
var pendingClientRequests = /* @__PURE__ */ new Map();
|
|
1945
|
+
var outgoingRequestId = 0;
|
|
113
1946
|
function requestClient(method, params = {}) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
1947
|
+
const id = `server:${++outgoingRequestId}`;
|
|
1948
|
+
reply({
|
|
1949
|
+
jsonrpc: "2.0",
|
|
1950
|
+
id,
|
|
1951
|
+
method,
|
|
1952
|
+
params
|
|
1953
|
+
});
|
|
1954
|
+
return new Promise((resolve, reject) => {
|
|
1955
|
+
pendingClientRequests.set(id, { method, resolve, reject });
|
|
1956
|
+
});
|
|
124
1957
|
}
|
|
125
1958
|
async function refreshRoots(trigger) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
catch (error) {
|
|
148
|
-
logger.warn("[Server] Failed to refresh client roots", {
|
|
149
|
-
trigger,
|
|
150
|
-
error: String(error),
|
|
151
|
-
});
|
|
1959
|
+
if (!session.supportsRoots) return;
|
|
1960
|
+
try {
|
|
1961
|
+
const result = await requestClient("roots/list");
|
|
1962
|
+
const changed = updateSessionRoots(session, extractRootsFromResult(result));
|
|
1963
|
+
logger.info("[Server] Refreshed client roots", {
|
|
1964
|
+
trigger,
|
|
1965
|
+
count: session.roots.length,
|
|
1966
|
+
changed
|
|
1967
|
+
});
|
|
1968
|
+
if (changed) {
|
|
1969
|
+
reply({
|
|
1970
|
+
jsonrpc: "2.0",
|
|
1971
|
+
method: "notifications/resources/list_changed"
|
|
1972
|
+
});
|
|
1973
|
+
reply({
|
|
1974
|
+
jsonrpc: "2.0",
|
|
1975
|
+
method: "notifications/prompts/list_changed"
|
|
1976
|
+
});
|
|
152
1977
|
}
|
|
1978
|
+
} catch (error) {
|
|
1979
|
+
logger.warn("[Server] Failed to refresh client roots", {
|
|
1980
|
+
trigger,
|
|
1981
|
+
error: String(error)
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
153
1984
|
}
|
|
154
1985
|
function notifyUpdatedResources(uris) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
reply({
|
|
164
|
-
jsonrpc: "2.0",
|
|
165
|
-
method: "notifications/resources/updated",
|
|
166
|
-
params: { uri },
|
|
167
|
-
});
|
|
1986
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1987
|
+
for (const uri of uris) {
|
|
1988
|
+
if (seen.has(uri)) continue;
|
|
1989
|
+
seen.add(uri);
|
|
1990
|
+
if (!resourceSubscriptions.has(uri)) {
|
|
1991
|
+
continue;
|
|
168
1992
|
}
|
|
1993
|
+
reply({
|
|
1994
|
+
jsonrpc: "2.0",
|
|
1995
|
+
method: "notifications/resources/updated",
|
|
1996
|
+
params: { uri }
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
169
1999
|
}
|
|
170
2000
|
rl.on("line", async (line) => {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
if (method === "resources/subscribe") {
|
|
243
|
-
const uri = typeof params?.uri === "string" ? params.uri : "";
|
|
244
|
-
if (!uri) {
|
|
245
|
-
reply({
|
|
246
|
-
jsonrpc: "2.0",
|
|
247
|
-
id,
|
|
248
|
-
error: {
|
|
249
|
-
code: -32602,
|
|
250
|
-
message: "resources/subscribe requires a uri",
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
resourceSubscriptions.add(uri);
|
|
256
|
-
reply({
|
|
257
|
-
jsonrpc: "2.0",
|
|
258
|
-
id,
|
|
259
|
-
result: {},
|
|
260
|
-
});
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
if (method === "resources/unsubscribe") {
|
|
264
|
-
const uri = typeof params?.uri === "string" ? params.uri : "";
|
|
265
|
-
if (!uri) {
|
|
266
|
-
reply({
|
|
267
|
-
jsonrpc: "2.0",
|
|
268
|
-
id,
|
|
269
|
-
error: {
|
|
270
|
-
code: -32602,
|
|
271
|
-
message: "resources/unsubscribe requires a uri",
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
resourceSubscriptions.delete(uri);
|
|
277
|
-
reply({
|
|
278
|
-
jsonrpc: "2.0",
|
|
279
|
-
id,
|
|
280
|
-
result: {},
|
|
281
|
-
});
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
// --- Ensure initialized before processing other requests ---
|
|
285
|
-
if (!isInitialized) {
|
|
286
|
-
reply({
|
|
287
|
-
jsonrpc: "2.0",
|
|
288
|
-
id,
|
|
289
|
-
error: {
|
|
290
|
-
code: -32002,
|
|
291
|
-
message: "Server is not fully initialized yet. Please send notifications/initialized."
|
|
292
|
-
}
|
|
293
|
-
});
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
const abortController = new AbortController();
|
|
297
|
-
activeRequests.set(id, abortController);
|
|
298
|
-
const progressToken = params?._meta?.progressToken;
|
|
299
|
-
const onProgress = progressToken !== undefined ? (progress, total) => {
|
|
300
|
-
reply({
|
|
301
|
-
jsonrpc: "2.0",
|
|
302
|
-
method: "notifications/progress",
|
|
303
|
-
params: {
|
|
304
|
-
progressToken,
|
|
305
|
-
progress,
|
|
306
|
-
total
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
} : undefined;
|
|
310
|
-
// --- route request ---
|
|
311
|
-
try {
|
|
312
|
-
const result = await handleMethod(method, params, abortController.signal, onProgress);
|
|
313
|
-
if (!abortController.signal.aborted) {
|
|
314
|
-
reply({
|
|
315
|
-
jsonrpc: "2.0",
|
|
316
|
-
id,
|
|
317
|
-
result
|
|
318
|
-
});
|
|
2001
|
+
if (!line.trim()) return;
|
|
2002
|
+
let msg;
|
|
2003
|
+
try {
|
|
2004
|
+
msg = JSON.parse(line);
|
|
2005
|
+
} catch {
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
const { id, method, params } = msg;
|
|
2009
|
+
const isNotification = id === void 0 || id === null;
|
|
2010
|
+
if ((method === void 0 || method === null) && id !== void 0 && pendingClientRequests.has(id)) {
|
|
2011
|
+
const pending = pendingClientRequests.get(id);
|
|
2012
|
+
pendingClientRequests.delete(id);
|
|
2013
|
+
if (msg.error) {
|
|
2014
|
+
pending.reject(new Error(msg.error.message || `Client request failed: ${pending.method}`));
|
|
2015
|
+
} else {
|
|
2016
|
+
pending.resolve(msg.result);
|
|
2017
|
+
}
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
2020
|
+
if (method === "initialize" && !isNotification) {
|
|
2021
|
+
updateSessionFromInitialize(session, params);
|
|
2022
|
+
reply({
|
|
2023
|
+
jsonrpc: "2.0",
|
|
2024
|
+
id,
|
|
2025
|
+
result: {
|
|
2026
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
2027
|
+
serverInfo: CAPABILITIES.serverInfo,
|
|
2028
|
+
capabilities: CAPABILITIES.capabilities
|
|
2029
|
+
}
|
|
2030
|
+
});
|
|
2031
|
+
return;
|
|
2032
|
+
}
|
|
2033
|
+
if (isNotification) {
|
|
2034
|
+
if (method === "notifications/initialized") {
|
|
2035
|
+
isInitialized = true;
|
|
2036
|
+
logNotificationsEnabled = true;
|
|
2037
|
+
logger.debug("[Server] Client initialized");
|
|
2038
|
+
void refreshRoots("initialized");
|
|
2039
|
+
} else if (method === "notifications/roots/list_changed") {
|
|
2040
|
+
logger.debug("[Server] Client roots changed");
|
|
2041
|
+
void refreshRoots("roots/list_changed");
|
|
2042
|
+
} else if (method === "notifications/cancelled") {
|
|
2043
|
+
const requestId = params?.requestId;
|
|
2044
|
+
if (requestId !== void 0 && activeRequests.has(requestId)) {
|
|
2045
|
+
activeRequests.get(requestId).abort();
|
|
2046
|
+
activeRequests.delete(requestId);
|
|
2047
|
+
logger.debug(`[Server] Request ${requestId} cancelled`);
|
|
2048
|
+
} else {
|
|
2049
|
+
logger.debug(`[Server] Cancelled notification for unknown or completed request ${requestId}`);
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
return;
|
|
2053
|
+
}
|
|
2054
|
+
if (method === "ping") {
|
|
2055
|
+
reply({
|
|
2056
|
+
jsonrpc: "2.0",
|
|
2057
|
+
id,
|
|
2058
|
+
result: {}
|
|
2059
|
+
});
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
if (method === "resources/subscribe") {
|
|
2063
|
+
const uri = typeof params?.uri === "string" ? params.uri : "";
|
|
2064
|
+
if (!uri) {
|
|
2065
|
+
reply({
|
|
2066
|
+
jsonrpc: "2.0",
|
|
2067
|
+
id,
|
|
2068
|
+
error: {
|
|
2069
|
+
code: -32602,
|
|
2070
|
+
message: "resources/subscribe requires a uri"
|
|
319
2071
|
}
|
|
2072
|
+
});
|
|
2073
|
+
return;
|
|
320
2074
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
2075
|
+
resourceSubscriptions.add(uri);
|
|
2076
|
+
reply({
|
|
2077
|
+
jsonrpc: "2.0",
|
|
2078
|
+
id,
|
|
2079
|
+
result: {}
|
|
2080
|
+
});
|
|
2081
|
+
return;
|
|
2082
|
+
}
|
|
2083
|
+
if (method === "resources/unsubscribe") {
|
|
2084
|
+
const uri = typeof params?.uri === "string" ? params.uri : "";
|
|
2085
|
+
if (!uri) {
|
|
2086
|
+
reply({
|
|
2087
|
+
jsonrpc: "2.0",
|
|
2088
|
+
id,
|
|
2089
|
+
error: {
|
|
2090
|
+
code: -32602,
|
|
2091
|
+
message: "resources/unsubscribe requires a uri"
|
|
332
2092
|
}
|
|
2093
|
+
});
|
|
2094
|
+
return;
|
|
333
2095
|
}
|
|
334
|
-
|
|
335
|
-
|
|
2096
|
+
resourceSubscriptions.delete(uri);
|
|
2097
|
+
reply({
|
|
2098
|
+
jsonrpc: "2.0",
|
|
2099
|
+
id,
|
|
2100
|
+
result: {}
|
|
2101
|
+
});
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
if (!isInitialized) {
|
|
2105
|
+
reply({
|
|
2106
|
+
jsonrpc: "2.0",
|
|
2107
|
+
id,
|
|
2108
|
+
error: {
|
|
2109
|
+
code: -32002,
|
|
2110
|
+
message: "Server is not fully initialized yet. Please send notifications/initialized."
|
|
2111
|
+
}
|
|
2112
|
+
});
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
const abortController = new AbortController();
|
|
2116
|
+
activeRequests.set(id, abortController);
|
|
2117
|
+
const progressToken = params?._meta?.progressToken;
|
|
2118
|
+
const onProgress = progressToken !== void 0 ? (progress, total) => {
|
|
2119
|
+
reply({
|
|
2120
|
+
jsonrpc: "2.0",
|
|
2121
|
+
method: "notifications/progress",
|
|
2122
|
+
params: {
|
|
2123
|
+
progressToken,
|
|
2124
|
+
progress,
|
|
2125
|
+
total
|
|
2126
|
+
}
|
|
2127
|
+
});
|
|
2128
|
+
} : void 0;
|
|
2129
|
+
try {
|
|
2130
|
+
const result = await handleMethod(method, params, abortController.signal, onProgress);
|
|
2131
|
+
if (!abortController.signal.aborted) {
|
|
2132
|
+
reply({
|
|
2133
|
+
jsonrpc: "2.0",
|
|
2134
|
+
id,
|
|
2135
|
+
result
|
|
2136
|
+
});
|
|
2137
|
+
}
|
|
2138
|
+
} catch (err) {
|
|
2139
|
+
if (!abortController.signal.aborted) {
|
|
2140
|
+
const error = err;
|
|
2141
|
+
logger.error("Method handler error", { method, id, message: error.message });
|
|
2142
|
+
reply({
|
|
2143
|
+
jsonrpc: "2.0",
|
|
2144
|
+
id,
|
|
2145
|
+
error: {
|
|
2146
|
+
code: typeof error?.code === "number" ? error.code : -32603,
|
|
2147
|
+
message: error.message || "Internal error"
|
|
2148
|
+
}
|
|
2149
|
+
});
|
|
336
2150
|
}
|
|
2151
|
+
} finally {
|
|
2152
|
+
activeRequests.delete(id);
|
|
2153
|
+
}
|
|
337
2154
|
});
|
|
338
|
-
//# sourceMappingURL=server.js.map
|