memory-journal-mcp 3.0.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.
Files changed (107) hide show
  1. package/.dockerignore +88 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +76 -0
  3. package/.github/ISSUE_TEMPLATE/config.yml +11 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +89 -0
  5. package/.github/ISSUE_TEMPLATE/question.md +63 -0
  6. package/.github/dependabot.yml +110 -0
  7. package/.github/pull_request_template.md +110 -0
  8. package/.github/workflows/DOCKER_DEPLOYMENT_SETUP.md +346 -0
  9. package/.github/workflows/codeql.yml +45 -0
  10. package/.github/workflows/dependabot-auto-merge.yml +42 -0
  11. package/.github/workflows/docker-publish.yml +277 -0
  12. package/.github/workflows/lint-and-test.yml +58 -0
  13. package/.github/workflows/publish-npm.yml +75 -0
  14. package/.github/workflows/secrets-scanning.yml +32 -0
  15. package/.github/workflows/security-update.yml +99 -0
  16. package/.memory-journal-team.db +0 -0
  17. package/.trivyignore +18 -0
  18. package/CHANGELOG.md +19 -0
  19. package/CODE_OF_CONDUCT.md +128 -0
  20. package/CONTRIBUTING.md +209 -0
  21. package/DOCKER_README.md +377 -0
  22. package/Dockerfile +64 -0
  23. package/LICENSE +21 -0
  24. package/README.md +461 -0
  25. package/SECURITY.md +200 -0
  26. package/VERSION +1 -0
  27. package/dist/cli.d.ts +5 -0
  28. package/dist/cli.d.ts.map +1 -0
  29. package/dist/cli.js +42 -0
  30. package/dist/cli.js.map +1 -0
  31. package/dist/constants/ServerInstructions.d.ts +8 -0
  32. package/dist/constants/ServerInstructions.d.ts.map +1 -0
  33. package/dist/constants/ServerInstructions.js +26 -0
  34. package/dist/constants/ServerInstructions.js.map +1 -0
  35. package/dist/database/SqliteAdapter.d.ts +198 -0
  36. package/dist/database/SqliteAdapter.d.ts.map +1 -0
  37. package/dist/database/SqliteAdapter.js +736 -0
  38. package/dist/database/SqliteAdapter.js.map +1 -0
  39. package/dist/filtering/ToolFilter.d.ts +63 -0
  40. package/dist/filtering/ToolFilter.d.ts.map +1 -0
  41. package/dist/filtering/ToolFilter.js +242 -0
  42. package/dist/filtering/ToolFilter.js.map +1 -0
  43. package/dist/github/GitHubIntegration.d.ts +91 -0
  44. package/dist/github/GitHubIntegration.d.ts.map +1 -0
  45. package/dist/github/GitHubIntegration.js +317 -0
  46. package/dist/github/GitHubIntegration.js.map +1 -0
  47. package/dist/handlers/prompts/index.d.ts +28 -0
  48. package/dist/handlers/prompts/index.d.ts.map +1 -0
  49. package/dist/handlers/prompts/index.js +366 -0
  50. package/dist/handlers/prompts/index.js.map +1 -0
  51. package/dist/handlers/resources/index.d.ts +27 -0
  52. package/dist/handlers/resources/index.d.ts.map +1 -0
  53. package/dist/handlers/resources/index.js +453 -0
  54. package/dist/handlers/resources/index.js.map +1 -0
  55. package/dist/handlers/tools/index.d.ts +26 -0
  56. package/dist/handlers/tools/index.d.ts.map +1 -0
  57. package/dist/handlers/tools/index.js +982 -0
  58. package/dist/handlers/tools/index.js.map +1 -0
  59. package/dist/index.d.ts +11 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +13 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/server/McpServer.d.ts +18 -0
  64. package/dist/server/McpServer.d.ts.map +1 -0
  65. package/dist/server/McpServer.js +171 -0
  66. package/dist/server/McpServer.js.map +1 -0
  67. package/dist/types/index.d.ts +300 -0
  68. package/dist/types/index.d.ts.map +1 -0
  69. package/dist/types/index.js +15 -0
  70. package/dist/types/index.js.map +1 -0
  71. package/dist/utils/McpLogger.d.ts +61 -0
  72. package/dist/utils/McpLogger.d.ts.map +1 -0
  73. package/dist/utils/McpLogger.js +113 -0
  74. package/dist/utils/McpLogger.js.map +1 -0
  75. package/dist/utils/logger.d.ts +30 -0
  76. package/dist/utils/logger.d.ts.map +1 -0
  77. package/dist/utils/logger.js +70 -0
  78. package/dist/utils/logger.js.map +1 -0
  79. package/dist/vector/VectorSearchManager.d.ts +63 -0
  80. package/dist/vector/VectorSearchManager.d.ts.map +1 -0
  81. package/dist/vector/VectorSearchManager.js +235 -0
  82. package/dist/vector/VectorSearchManager.js.map +1 -0
  83. package/docker-compose.yml +37 -0
  84. package/eslint.config.js +86 -0
  85. package/mcp-config-example.json +21 -0
  86. package/package.json +71 -0
  87. package/releases/release-notes-v2.2.0.md +165 -0
  88. package/releases/release-notes.md +214 -0
  89. package/releases/v3.0.0.md +236 -0
  90. package/server.json +42 -0
  91. package/src/cli.ts +52 -0
  92. package/src/constants/ServerInstructions.ts +25 -0
  93. package/src/database/SqliteAdapter.ts +952 -0
  94. package/src/filtering/ToolFilter.ts +271 -0
  95. package/src/github/GitHubIntegration.ts +409 -0
  96. package/src/handlers/prompts/index.ts +420 -0
  97. package/src/handlers/resources/index.ts +529 -0
  98. package/src/handlers/tools/index.ts +1081 -0
  99. package/src/index.ts +53 -0
  100. package/src/server/McpServer.ts +230 -0
  101. package/src/types/index.ts +435 -0
  102. package/src/types/sql.js.d.ts +34 -0
  103. package/src/utils/McpLogger.ts +155 -0
  104. package/src/utils/logger.ts +98 -0
  105. package/src/vector/VectorSearchManager.ts +277 -0
  106. package/tools.json +300 -0
  107. package/tsconfig.json +51 -0
@@ -0,0 +1,529 @@
1
+ /**
2
+ * Memory Journal MCP Server - Resource Handlers
3
+ *
4
+ * Exports all MCP resources with annotations following MCP 2025-11-25 spec.
5
+ */
6
+
7
+ import type { SqliteAdapter } from '../../database/SqliteAdapter.js';
8
+ import type { VectorSearchManager } from '../../vector/VectorSearchManager.js';
9
+ import type { ToolFilterConfig } from '../../filtering/ToolFilter.js';
10
+ import type { Tag } from '../../types/index.js';
11
+ import type { GitHubIntegration } from '../../github/GitHubIntegration.js';
12
+
13
+ /**
14
+ * Resource context for handlers that need extended access
15
+ */
16
+ export interface ResourceContext {
17
+ db: SqliteAdapter;
18
+ vectorManager?: VectorSearchManager;
19
+ filterConfig?: ToolFilterConfig | null;
20
+ github?: GitHubIntegration | null;
21
+ }
22
+
23
+ /**
24
+ * Internal resource definition with db handler
25
+ */
26
+ interface InternalResourceDef {
27
+ uri: string;
28
+ name: string;
29
+ title: string;
30
+ description: string;
31
+ mimeType: string;
32
+ annotations?: {
33
+ audience?: ('user' | 'assistant')[];
34
+ priority?: number;
35
+ };
36
+ handler: (uri: string, context: ResourceContext) => unknown;
37
+ }
38
+
39
+ /**
40
+ * Get all resource definitions for MCP list
41
+ */
42
+ export function getResources(): object[] {
43
+ const resources = getAllResourceDefinitions();
44
+ return resources.map(r => ({
45
+ uri: r.uri,
46
+ name: r.name,
47
+ description: r.description,
48
+ mimeType: r.mimeType,
49
+ annotations: r.annotations,
50
+ }));
51
+ }
52
+
53
+ /**
54
+ * Read a resource by URI
55
+ */
56
+ export function readResource(
57
+ uri: string,
58
+ db: SqliteAdapter,
59
+ vectorManager?: VectorSearchManager,
60
+ filterConfig?: ToolFilterConfig | null,
61
+ github?: GitHubIntegration | null
62
+ ): Promise<unknown> {
63
+ const resources = getAllResourceDefinitions();
64
+ const context: ResourceContext = { db, vectorManager, filterConfig, github };
65
+
66
+ // Check for exact match first
67
+ const exactMatch = resources.find(r => r.uri === uri);
68
+ if (exactMatch) {
69
+ return Promise.resolve(exactMatch.handler(uri, context));
70
+ }
71
+
72
+ // Check for template matches
73
+ for (const resource of resources) {
74
+ if (resource.uri.includes('{')) {
75
+ const pattern = resource.uri.replace(/\{[^}]+\}/g, '([^/]+)');
76
+ const regex = new RegExp(`^${pattern}$`);
77
+ if (regex.test(uri)) {
78
+ return Promise.resolve(resource.handler(uri, context));
79
+ }
80
+ }
81
+ }
82
+
83
+ throw new Error(`Unknown resource: ${uri}`);
84
+ }
85
+
86
+ /**
87
+ * Execute a raw SQL query on the database
88
+ */
89
+ function execQuery(db: SqliteAdapter, sql: string, params: unknown[] = []): Record<string, unknown>[] {
90
+ const rawDb = db.getRawDb();
91
+ const result = rawDb.exec(sql, params);
92
+ if (result.length === 0) return [];
93
+
94
+ const columns = result[0]?.columns ?? [];
95
+ return (result[0]?.values ?? []).map((values: unknown[]) => {
96
+ const obj: Record<string, unknown> = {};
97
+ columns.forEach((col: string, i: number) => {
98
+ obj[col] = values[i];
99
+ });
100
+ return obj;
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Get total tool count for health status
106
+ */
107
+ function getTotalToolCount(): number {
108
+ // Import dynamically to avoid circular dependency
109
+ return 27; // 24 original + 3 backup tools
110
+ }
111
+
112
+ /**
113
+ * Get all resource definitions
114
+ */
115
+ function getAllResourceDefinitions(): InternalResourceDef[] {
116
+ return [
117
+ {
118
+ uri: 'memory://recent',
119
+ name: 'Recent Entries',
120
+ title: 'Recent Journal Entries',
121
+ description: '10 most recent journal entries',
122
+ mimeType: 'application/json',
123
+ annotations: {
124
+ audience: ['assistant'],
125
+ priority: 0.8,
126
+ },
127
+ handler: (_uri: string, context: ResourceContext) => {
128
+ const entries = context.db.getRecentEntries(10);
129
+ return { entries, count: entries.length };
130
+ },
131
+ },
132
+ {
133
+ uri: 'memory://significant',
134
+ name: 'Significant Entries',
135
+ title: 'Significant Milestones',
136
+ description: 'Significant milestones and breakthroughs',
137
+ mimeType: 'application/json',
138
+ annotations: {
139
+ audience: ['assistant'],
140
+ priority: 0.7,
141
+ },
142
+ handler: (_uri: string, context: ResourceContext) => {
143
+ const rows = execQuery(context.db, `
144
+ SELECT * FROM memory_journal
145
+ WHERE significance_type IS NOT NULL
146
+ AND deleted_at IS NULL
147
+ ORDER BY timestamp DESC
148
+ LIMIT 20
149
+ `);
150
+ return { entries: rows, count: rows.length };
151
+ },
152
+ },
153
+ {
154
+ uri: 'memory://graph/recent',
155
+ name: 'Recent Relationship Graph',
156
+ title: 'Live Mermaid Diagram',
157
+ description: 'Live Mermaid diagram of recent relationships',
158
+ mimeType: 'text/plain',
159
+ annotations: {
160
+ audience: ['user', 'assistant'],
161
+ priority: 0.5,
162
+ },
163
+ handler: (_uri: string, context: ResourceContext) => {
164
+ // Get recent relationships from database
165
+ const relationships = execQuery(context.db, `
166
+ SELECT
167
+ r.id, r.from_entry_id, r.to_entry_id, r.relationship_type, r.description,
168
+ e1.content as from_content,
169
+ e2.content as to_content
170
+ FROM relationships r
171
+ JOIN memory_journal e1 ON r.from_entry_id = e1.id
172
+ JOIN memory_journal e2 ON r.to_entry_id = e2.id
173
+ WHERE e1.deleted_at IS NULL AND e2.deleted_at IS NULL
174
+ ORDER BY r.created_at DESC
175
+ LIMIT 20
176
+ `) as {
177
+ from_entry_id: number;
178
+ to_entry_id: number;
179
+ relationship_type: string;
180
+ from_content: string;
181
+ to_content: string;
182
+ }[];
183
+
184
+ if (relationships.length === 0) {
185
+ return {
186
+ format: 'mermaid',
187
+ diagram: 'graph TD\n NoData[No relationships found]',
188
+ message: 'No entry relationships exist yet. Use link_entries tool to create relationships.',
189
+ };
190
+ }
191
+
192
+ // Build Mermaid graph
193
+ const lines: string[] = ['graph TD'];
194
+ const seenNodes = new Set<number>();
195
+
196
+ // Relationship type to arrow style mapping
197
+ const arrowStyles: Record<string, string> = {
198
+ 'references': '-->',
199
+ 'implements': '-.->',
200
+ 'evolves_from': '==>',
201
+ 'related_to': '<-->',
202
+ 'depends_on': '-->',
203
+ };
204
+
205
+ for (const rel of relationships) {
206
+ // Add node definitions if not seen
207
+ if (!seenNodes.has(rel.from_entry_id)) {
208
+ const label = rel.from_content.slice(0, 30).replace(/[\]"'`[]/g, ' ').trim();
209
+ lines.push(` E${String(rel.from_entry_id)}["#${String(rel.from_entry_id)}: ${label}..."]`);
210
+ seenNodes.add(rel.from_entry_id);
211
+ }
212
+ if (!seenNodes.has(rel.to_entry_id)) {
213
+ const label = rel.to_content.slice(0, 30).replace(/[\]"'`[]/g, ' ').trim();
214
+ lines.push(` E${String(rel.to_entry_id)}["#${String(rel.to_entry_id)}: ${label}..."]`);
215
+ seenNodes.add(rel.to_entry_id);
216
+ }
217
+
218
+ // Add edge with relationship label
219
+ const arrow = arrowStyles[rel.relationship_type] ?? '-->';
220
+ lines.push(` E${String(rel.from_entry_id)} ${arrow}|${rel.relationship_type}| E${String(rel.to_entry_id)}`);
221
+ }
222
+
223
+ return {
224
+ format: 'mermaid',
225
+ diagram: lines.join('\n'),
226
+ relationshipCount: relationships.length,
227
+ nodeCount: seenNodes.size,
228
+ };
229
+ },
230
+ },
231
+ {
232
+ uri: 'memory://team/recent',
233
+ name: 'Team Entries',
234
+ title: 'Recent Team-Shared Entries',
235
+ description: 'Recent team-shared entries',
236
+ mimeType: 'application/json',
237
+ annotations: {
238
+ audience: ['assistant'],
239
+ priority: 0.6,
240
+ },
241
+ handler: (_uri: string, context: ResourceContext) => {
242
+ const entries = context.db.getRecentEntries(10, false);
243
+ return { entries, count: entries.length };
244
+ },
245
+ },
246
+ {
247
+ uri: 'memory://projects/{number}/timeline',
248
+ name: 'Project Timeline',
249
+ title: 'Project Activity Timeline',
250
+ description: 'Project activity timeline',
251
+ mimeType: 'application/json',
252
+ annotations: {
253
+ audience: ['assistant'],
254
+ priority: 0.6,
255
+ },
256
+ handler: (uri: string, context: ResourceContext) => {
257
+ const match = /memory:\/\/projects\/(\d+)\/timeline/.exec(uri);
258
+ const projectNumber = match?.[1] ? parseInt(match[1], 10) : null;
259
+
260
+ if (projectNumber === null) {
261
+ return { error: 'Invalid project number' };
262
+ }
263
+
264
+ const entries = execQuery(context.db, `
265
+ SELECT * FROM memory_journal
266
+ WHERE project_number = ?
267
+ AND deleted_at IS NULL
268
+ ORDER BY timestamp DESC
269
+ LIMIT 50
270
+ `, [projectNumber]);
271
+ return { projectNumber, entries, count: entries.length };
272
+ },
273
+ },
274
+ {
275
+ uri: 'memory://issues/{issue_number}/entries',
276
+ name: 'Issue Entries',
277
+ title: 'Entries Linked to Issue',
278
+ description: 'All entries linked to a specific issue',
279
+ mimeType: 'application/json',
280
+ annotations: {
281
+ audience: ['assistant'],
282
+ priority: 0.6,
283
+ },
284
+ handler: (uri: string, context: ResourceContext) => {
285
+ const match = /memory:\/\/issues\/(\d+)\/entries/.exec(uri);
286
+ const issueNumber = match?.[1] ? parseInt(match[1], 10) : null;
287
+
288
+ if (issueNumber === null) {
289
+ return { error: 'Invalid issue number' };
290
+ }
291
+
292
+ const entries = execQuery(context.db, `
293
+ SELECT * FROM memory_journal
294
+ WHERE issue_number = ?
295
+ AND deleted_at IS NULL
296
+ ORDER BY timestamp DESC
297
+ `, [issueNumber]);
298
+ return { issueNumber, entries, count: entries.length };
299
+ },
300
+ },
301
+ {
302
+ uri: 'memory://prs/{pr_number}/entries',
303
+ name: 'PR Entries',
304
+ title: 'Entries Linked to PR',
305
+ description: 'All entries linked to a specific pull request',
306
+ mimeType: 'application/json',
307
+ annotations: {
308
+ audience: ['assistant'],
309
+ priority: 0.6,
310
+ },
311
+ handler: (uri: string, context: ResourceContext) => {
312
+ const match = /memory:\/\/prs\/(\d+)\/entries/.exec(uri);
313
+ const prNumber = match?.[1] ? parseInt(match[1], 10) : null;
314
+
315
+ if (prNumber === null) {
316
+ return { error: 'Invalid PR number' };
317
+ }
318
+
319
+ const entries = execQuery(context.db, `
320
+ SELECT * FROM memory_journal
321
+ WHERE pr_number = ?
322
+ AND deleted_at IS NULL
323
+ ORDER BY timestamp DESC
324
+ `, [prNumber]);
325
+ return { prNumber, entries, count: entries.length };
326
+ },
327
+ },
328
+ {
329
+ uri: 'memory://prs/{pr_number}/timeline',
330
+ name: 'PR Timeline',
331
+ title: 'Combined PR and Journal Timeline',
332
+ description: 'Combined PR + journal timeline',
333
+ mimeType: 'application/json',
334
+ annotations: {
335
+ audience: ['assistant'],
336
+ priority: 0.5,
337
+ },
338
+ handler: (uri: string, context: ResourceContext) => {
339
+ const match = /memory:\/\/prs\/(\d+)\/timeline/.exec(uri);
340
+ const prNumber = match?.[1] ? parseInt(match[1], 10) : null;
341
+
342
+ if (prNumber === null) {
343
+ return { error: 'Invalid PR number' };
344
+ }
345
+
346
+ const entries = execQuery(context.db, `
347
+ SELECT * FROM memory_journal
348
+ WHERE pr_number = ?
349
+ AND deleted_at IS NULL
350
+ ORDER BY timestamp DESC
351
+ `, [prNumber]);
352
+ return { prNumber, entries, count: entries.length };
353
+ },
354
+ },
355
+ {
356
+ uri: 'memory://graph/actions',
357
+ name: 'Actions Graph',
358
+ title: 'CI/CD Narrative Graph',
359
+ description: 'CI/CD narrative graph: commits → runs → failures → entries → fixes → deployments',
360
+ mimeType: 'text/plain',
361
+ annotations: {
362
+ audience: ['user', 'assistant'],
363
+ priority: 0.5,
364
+ },
365
+ handler: async (_uri: string, context: ResourceContext) => {
366
+ // Check if GitHub integration is available
367
+ if (!context.github) {
368
+ return {
369
+ format: 'mermaid',
370
+ diagram: 'graph LR\n NoGitHub[GitHub integration not available]',
371
+ message: 'GitHub integration not configured. Set GITHUB_TOKEN and GITHUB_REPO_PATH.',
372
+ };
373
+ }
374
+
375
+ // Get repository info and workflow runs
376
+ const repoInfo = await context.github.getRepoInfo();
377
+ if (!repoInfo.owner || !repoInfo.repo) {
378
+ return {
379
+ format: 'mermaid',
380
+ diagram: 'graph LR\n NoRepo[Repository not detected]',
381
+ message: 'Could not detect repository. Set GITHUB_REPO_PATH in your config.',
382
+ };
383
+ }
384
+
385
+ const workflowRuns = await context.github.getWorkflowRuns(repoInfo.owner, repoInfo.repo, 10);
386
+
387
+ if (workflowRuns.length === 0) {
388
+ return {
389
+ format: 'mermaid',
390
+ diagram: 'graph LR\n NoRuns[No workflow runs found]',
391
+ message: 'No GitHub Actions workflow runs found for this repository.',
392
+ };
393
+ }
394
+
395
+ // Build Mermaid graph showing workflow runs
396
+ const lines: string[] = ['graph LR'];
397
+
398
+ // Status to styling map
399
+ const statusStyles: Record<string, string> = {
400
+ 'success': ':::success',
401
+ 'failure': ':::failure',
402
+ 'cancelled': ':::cancelled',
403
+ 'skipped': ':::skipped',
404
+ };
405
+
406
+ // Add style definitions
407
+ lines.push(' classDef success fill:#28a745,color:#fff');
408
+ lines.push(' classDef failure fill:#dc3545,color:#fff');
409
+ lines.push(' classDef cancelled fill:#6c757d,color:#fff');
410
+ lines.push(' classDef skipped fill:#ffc107,color:#000');
411
+
412
+ for (const run of workflowRuns) {
413
+ const shortSha = run.headSha.slice(0, 7);
414
+ const nodeId = `R${String(run.id)}`;
415
+ const commitId = `C${shortSha}`;
416
+ const style = statusStyles[run.conclusion ?? 'skipped'] ?? '';
417
+ const statusIcon = run.conclusion === 'success' ? '✓' : run.conclusion === 'failure' ? '✗' : '○';
418
+
419
+ // Add commit and run nodes
420
+ lines.push(` ${commitId}["${shortSha}"]`);
421
+ lines.push(` ${nodeId}["${statusIcon} ${run.name}"]${style}`);
422
+ lines.push(` ${commitId} --> ${nodeId}`);
423
+ }
424
+
425
+ return {
426
+ format: 'mermaid',
427
+ diagram: lines.join('\n'),
428
+ workflowRunCount: workflowRuns.length,
429
+ repository: `${repoInfo.owner}/${repoInfo.repo}`,
430
+ };
431
+ },
432
+ },
433
+ {
434
+ uri: 'memory://actions/recent',
435
+ name: 'Recent Actions',
436
+ title: 'Recent Workflow Runs',
437
+ description: 'Recent workflow runs with CI status',
438
+ mimeType: 'application/json',
439
+ annotations: {
440
+ audience: ['assistant'],
441
+ priority: 0.5,
442
+ },
443
+ handler: (_uri: string, context: ResourceContext) => {
444
+ const entries = execQuery(context.db, `
445
+ SELECT * FROM memory_journal
446
+ WHERE workflow_run_id IS NOT NULL
447
+ AND deleted_at IS NULL
448
+ ORDER BY timestamp DESC
449
+ LIMIT 10
450
+ `);
451
+ return { entries, count: entries.length };
452
+ },
453
+ },
454
+ {
455
+ uri: 'memory://tags',
456
+ name: 'All Tags',
457
+ title: 'Tag List',
458
+ description: 'All available tags with usage counts',
459
+ mimeType: 'application/json',
460
+ annotations: {
461
+ audience: ['assistant'],
462
+ priority: 0.4,
463
+ },
464
+ handler: (_uri: string, context: ResourceContext) => {
465
+ const tags: Tag[] = context.db.listTags();
466
+ return { tags, count: tags.length };
467
+ },
468
+ },
469
+ {
470
+ uri: 'memory://statistics',
471
+ name: 'Statistics',
472
+ title: 'Journal Statistics',
473
+ description: 'Overall journal statistics',
474
+ mimeType: 'application/json',
475
+ annotations: {
476
+ audience: ['assistant'],
477
+ priority: 0.4,
478
+ },
479
+ handler: (_uri: string, context: ResourceContext) => {
480
+ return context.db.getStatistics('week');
481
+ },
482
+ },
483
+ {
484
+ uri: 'memory://health',
485
+ name: 'Server Health',
486
+ title: 'Server Health & Diagnostics',
487
+ description: 'Server health status including database, backups, vector index, and tool filter status',
488
+ mimeType: 'application/json',
489
+ annotations: {
490
+ audience: ['assistant'],
491
+ priority: 0.9,
492
+ },
493
+ handler: async (_uri: string, context: ResourceContext) => {
494
+ const dbHealth = context.db.getHealthStatus();
495
+
496
+ // Get vector index status if available
497
+ let vectorIndex: { available: boolean; indexedEntries: number; modelName: string | null } | null = null;
498
+ if (context.vectorManager) {
499
+ try {
500
+ const stats = await context.vectorManager.getStats();
501
+ vectorIndex = {
502
+ available: true,
503
+ indexedEntries: stats.itemCount,
504
+ modelName: stats.modelName,
505
+ };
506
+ } catch {
507
+ vectorIndex = { available: false, indexedEntries: 0, modelName: null };
508
+ }
509
+ }
510
+
511
+ // Get tool filter status
512
+ const totalTools = getTotalToolCount();
513
+ const toolFilter = {
514
+ active: context.filterConfig !== null && context.filterConfig !== undefined,
515
+ enabledCount: context.filterConfig?.enabledTools.size ?? totalTools,
516
+ totalCount: totalTools,
517
+ filterString: context.filterConfig?.raw ?? null,
518
+ };
519
+
520
+ return {
521
+ ...dbHealth,
522
+ vectorIndex,
523
+ toolFilter,
524
+ timestamp: new Date().toISOString(),
525
+ };
526
+ },
527
+ },
528
+ ];
529
+ }