@aitytech/agentkits-memory 1.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.
- package/README.md +250 -0
- package/dist/cache-manager.d.ts +134 -0
- package/dist/cache-manager.d.ts.map +1 -0
- package/dist/cache-manager.js +407 -0
- package/dist/cache-manager.js.map +1 -0
- package/dist/cli/save.d.ts +20 -0
- package/dist/cli/save.d.ts.map +1 -0
- package/dist/cli/save.js +94 -0
- package/dist/cli/save.js.map +1 -0
- package/dist/cli/setup.d.ts +18 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +163 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/cli/viewer.d.ts +21 -0
- package/dist/cli/viewer.d.ts.map +1 -0
- package/dist/cli/viewer.js +182 -0
- package/dist/cli/viewer.js.map +1 -0
- package/dist/hnsw-index.d.ts +111 -0
- package/dist/hnsw-index.d.ts.map +1 -0
- package/dist/hnsw-index.js +781 -0
- package/dist/hnsw-index.js.map +1 -0
- package/dist/hooks/cli.d.ts +20 -0
- package/dist/hooks/cli.d.ts.map +1 -0
- package/dist/hooks/cli.js +102 -0
- package/dist/hooks/cli.js.map +1 -0
- package/dist/hooks/context.d.ts +31 -0
- package/dist/hooks/context.d.ts.map +1 -0
- package/dist/hooks/context.js +64 -0
- package/dist/hooks/context.js.map +1 -0
- package/dist/hooks/index.d.ts +16 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +20 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/observation.d.ts +30 -0
- package/dist/hooks/observation.d.ts.map +1 -0
- package/dist/hooks/observation.js +79 -0
- package/dist/hooks/observation.js.map +1 -0
- package/dist/hooks/service.d.ts +102 -0
- package/dist/hooks/service.d.ts.map +1 -0
- package/dist/hooks/service.js +454 -0
- package/dist/hooks/service.js.map +1 -0
- package/dist/hooks/session-init.d.ts +30 -0
- package/dist/hooks/session-init.d.ts.map +1 -0
- package/dist/hooks/session-init.js +54 -0
- package/dist/hooks/session-init.js.map +1 -0
- package/dist/hooks/summarize.d.ts +30 -0
- package/dist/hooks/summarize.d.ts.map +1 -0
- package/dist/hooks/summarize.js +74 -0
- package/dist/hooks/summarize.js.map +1 -0
- package/dist/hooks/types.d.ts +193 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +137 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/index.d.ts +173 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +564 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +9 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +9 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +22 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +368 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +14 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +110 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp/types.d.ts +100 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +9 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/migration.d.ts +77 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +457 -0
- package/dist/migration.js.map +1 -0
- package/dist/sqljs-backend.d.ts +128 -0
- package/dist/sqljs-backend.d.ts.map +1 -0
- package/dist/sqljs-backend.js +623 -0
- package/dist/sqljs-backend.js.map +1 -0
- package/dist/types.d.ts +481 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +73 -0
- package/dist/types.js.map +1 -0
- package/hooks.json +46 -0
- package/package.json +67 -0
- package/src/__tests__/index.test.ts +407 -0
- package/src/__tests__/sqljs-backend.test.ts +410 -0
- package/src/cache-manager.ts +515 -0
- package/src/cli/save.ts +109 -0
- package/src/cli/setup.ts +203 -0
- package/src/cli/viewer.ts +218 -0
- package/src/hnsw-index.ts +1013 -0
- package/src/hooks/__tests__/handlers.test.ts +298 -0
- package/src/hooks/__tests__/integration.test.ts +431 -0
- package/src/hooks/__tests__/service.test.ts +487 -0
- package/src/hooks/__tests__/types.test.ts +341 -0
- package/src/hooks/cli.ts +121 -0
- package/src/hooks/context.ts +77 -0
- package/src/hooks/index.ts +23 -0
- package/src/hooks/observation.ts +102 -0
- package/src/hooks/service.ts +582 -0
- package/src/hooks/session-init.ts +70 -0
- package/src/hooks/summarize.ts +89 -0
- package/src/hooks/types.ts +365 -0
- package/src/index.ts +755 -0
- package/src/mcp/__tests__/server.test.ts +181 -0
- package/src/mcp/index.ts +9 -0
- package/src/mcp/server.ts +441 -0
- package/src/mcp/tools.ts +113 -0
- package/src/mcp/types.ts +109 -0
- package/src/migration.ts +574 -0
- package/src/sql.js.d.ts +70 -0
- package/src/sqljs-backend.ts +789 -0
- package/src/types.ts +715 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for the Memory MCP Server tools.
|
|
5
|
+
*
|
|
6
|
+
* @module @agentkits/memory/mcp/__tests__/server.test
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
10
|
+
import { ProjectMemoryService, DEFAULT_NAMESPACES } from '../../index.js';
|
|
11
|
+
import { MEMORY_TOOLS } from '../tools.js';
|
|
12
|
+
import type {
|
|
13
|
+
MemorySaveArgs,
|
|
14
|
+
MemorySearchArgs,
|
|
15
|
+
MemoryRecallArgs,
|
|
16
|
+
MemoryListArgs,
|
|
17
|
+
} from '../types.js';
|
|
18
|
+
|
|
19
|
+
// Mock ProjectMemoryService for isolated testing
|
|
20
|
+
vi.mock('../../index.js', async () => {
|
|
21
|
+
const actual = await vi.importActual('../../index.js');
|
|
22
|
+
return {
|
|
23
|
+
...actual,
|
|
24
|
+
ProjectMemoryService: vi.fn(),
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('MCP Server', () => {
|
|
29
|
+
describe('MEMORY_TOOLS', () => {
|
|
30
|
+
it('should export all required tools', () => {
|
|
31
|
+
const toolNames = MEMORY_TOOLS.map(t => t.name);
|
|
32
|
+
|
|
33
|
+
expect(toolNames).toContain('memory_save');
|
|
34
|
+
expect(toolNames).toContain('memory_search');
|
|
35
|
+
expect(toolNames).toContain('memory_recall');
|
|
36
|
+
expect(toolNames).toContain('memory_list');
|
|
37
|
+
expect(toolNames).toContain('memory_status');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should have valid input schemas for all tools', () => {
|
|
41
|
+
for (const tool of MEMORY_TOOLS) {
|
|
42
|
+
expect(tool.inputSchema).toBeDefined();
|
|
43
|
+
expect(tool.inputSchema.type).toBe('object');
|
|
44
|
+
expect(tool.inputSchema.properties).toBeDefined();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('memory_save tool', () => {
|
|
49
|
+
const saveTool = MEMORY_TOOLS.find(t => t.name === 'memory_save')!;
|
|
50
|
+
|
|
51
|
+
it('should require content parameter', () => {
|
|
52
|
+
expect(saveTool.inputSchema.required).toContain('content');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should have valid category enum', () => {
|
|
56
|
+
const categoryProp = saveTool.inputSchema.properties.category;
|
|
57
|
+
expect(categoryProp.enum).toEqual(['decision', 'pattern', 'error', 'context', 'observation']);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should have valid importance enum', () => {
|
|
61
|
+
const importanceProp = saveTool.inputSchema.properties.importance;
|
|
62
|
+
expect(importanceProp.enum).toEqual(['low', 'medium', 'high', 'critical']);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('memory_search tool', () => {
|
|
67
|
+
const searchTool = MEMORY_TOOLS.find(t => t.name === 'memory_search')!;
|
|
68
|
+
|
|
69
|
+
it('should require query parameter', () => {
|
|
70
|
+
expect(searchTool.inputSchema.required).toContain('query');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should have category filter option', () => {
|
|
74
|
+
expect(searchTool.inputSchema.properties.category).toBeDefined();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should have limit option', () => {
|
|
78
|
+
expect(searchTool.inputSchema.properties.limit).toBeDefined();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('memory_recall tool', () => {
|
|
83
|
+
const recallTool = MEMORY_TOOLS.find(t => t.name === 'memory_recall')!;
|
|
84
|
+
|
|
85
|
+
it('should require topic parameter', () => {
|
|
86
|
+
expect(recallTool.inputSchema.required).toContain('topic');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should have valid timeRange enum', () => {
|
|
90
|
+
const timeRangeProp = recallTool.inputSchema.properties.timeRange;
|
|
91
|
+
expect(timeRangeProp.enum).toEqual(['today', 'week', 'month', 'all']);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('memory_list tool', () => {
|
|
96
|
+
const listTool = MEMORY_TOOLS.find(t => t.name === 'memory_list')!;
|
|
97
|
+
|
|
98
|
+
it('should have optional category filter', () => {
|
|
99
|
+
expect(listTool.inputSchema.properties.category).toBeDefined();
|
|
100
|
+
// required is undefined or doesn't contain 'category'
|
|
101
|
+
const required = listTool.inputSchema.required || [];
|
|
102
|
+
expect(required).not.toContain('category');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should have optional limit parameter', () => {
|
|
106
|
+
expect(listTool.inputSchema.properties.limit).toBeDefined();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('memory_status tool', () => {
|
|
111
|
+
const statusTool = MEMORY_TOOLS.find(t => t.name === 'memory_status')!;
|
|
112
|
+
|
|
113
|
+
it('should have no required parameters', () => {
|
|
114
|
+
expect(statusTool.inputSchema.required).toBeUndefined();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should have empty properties', () => {
|
|
118
|
+
expect(Object.keys(statusTool.inputSchema.properties)).toHaveLength(0);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('Tool Argument Types', () => {
|
|
124
|
+
it('MemorySaveArgs should accept valid arguments', () => {
|
|
125
|
+
const args: MemorySaveArgs = {
|
|
126
|
+
content: 'Test content',
|
|
127
|
+
category: 'pattern',
|
|
128
|
+
tags: 'tag1,tag2',
|
|
129
|
+
importance: 'high',
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
expect(args.content).toBe('Test content');
|
|
133
|
+
expect(args.category).toBe('pattern');
|
|
134
|
+
expect(args.tags).toBe('tag1,tag2');
|
|
135
|
+
expect(args.importance).toBe('high');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('MemorySaveArgs should work with minimal arguments', () => {
|
|
139
|
+
const args: MemorySaveArgs = {
|
|
140
|
+
content: 'Minimal content',
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
expect(args.content).toBe('Minimal content');
|
|
144
|
+
expect(args.category).toBeUndefined();
|
|
145
|
+
expect(args.tags).toBeUndefined();
|
|
146
|
+
expect(args.importance).toBeUndefined();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('MemorySearchArgs should accept valid arguments', () => {
|
|
150
|
+
const args: MemorySearchArgs = {
|
|
151
|
+
query: 'search term',
|
|
152
|
+
limit: 10,
|
|
153
|
+
category: 'decision',
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
expect(args.query).toBe('search term');
|
|
157
|
+
expect(args.limit).toBe(10);
|
|
158
|
+
expect(args.category).toBe('decision');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('MemoryRecallArgs should accept valid arguments', () => {
|
|
162
|
+
const args: MemoryRecallArgs = {
|
|
163
|
+
topic: 'authentication',
|
|
164
|
+
timeRange: 'week',
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
expect(args.topic).toBe('authentication');
|
|
168
|
+
expect(args.timeRange).toBe('week');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('MemoryListArgs should accept valid arguments', () => {
|
|
172
|
+
const args: MemoryListArgs = {
|
|
173
|
+
category: 'error',
|
|
174
|
+
limit: 5,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
expect(args.category).toBe('error');
|
|
178
|
+
expect(args.limit).toBe(5);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
package/src/mcp/index.ts
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AgentKits Memory MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Model Context Protocol server for Claude Code memory access.
|
|
6
|
+
* Provides tools for saving, searching, and recalling memories.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* Add to .mcp.json:
|
|
10
|
+
* {
|
|
11
|
+
* "mcpServers": {
|
|
12
|
+
* "memory": {
|
|
13
|
+
* "command": "npx",
|
|
14
|
+
* "args": ["agentkits-memory-server"]
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* @module @agentkits/memory/mcp/server
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import * as readline from 'node:readline';
|
|
23
|
+
import { ProjectMemoryService, MemoryEntry, MemoryQuery, DEFAULT_NAMESPACES } from '../index.js';
|
|
24
|
+
import { MEMORY_TOOLS } from './tools.js';
|
|
25
|
+
import type {
|
|
26
|
+
JSONRPCRequest,
|
|
27
|
+
JSONRPCResponse,
|
|
28
|
+
ToolCallRequest,
|
|
29
|
+
ToolCallResult,
|
|
30
|
+
MemorySaveArgs,
|
|
31
|
+
MemorySearchArgs,
|
|
32
|
+
MemoryRecallArgs,
|
|
33
|
+
MemoryListArgs,
|
|
34
|
+
} from './types.js';
|
|
35
|
+
|
|
36
|
+
// Map category names to namespaces
|
|
37
|
+
const CATEGORY_TO_NAMESPACE: Record<string, string> = {
|
|
38
|
+
decision: DEFAULT_NAMESPACES.DECISIONS,
|
|
39
|
+
pattern: DEFAULT_NAMESPACES.PATTERNS,
|
|
40
|
+
error: DEFAULT_NAMESPACES.ERRORS,
|
|
41
|
+
context: DEFAULT_NAMESPACES.CONTEXT,
|
|
42
|
+
observation: DEFAULT_NAMESPACES.ACTIVE,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Memory MCP Server
|
|
47
|
+
*/
|
|
48
|
+
class MemoryMCPServer {
|
|
49
|
+
private service: ProjectMemoryService | null = null;
|
|
50
|
+
private projectDir: string;
|
|
51
|
+
private initialized = false;
|
|
52
|
+
|
|
53
|
+
constructor() {
|
|
54
|
+
this.projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Initialize the memory service
|
|
59
|
+
*/
|
|
60
|
+
private async ensureInitialized(): Promise<ProjectMemoryService> {
|
|
61
|
+
if (!this.service || !this.initialized) {
|
|
62
|
+
this.service = new ProjectMemoryService({
|
|
63
|
+
baseDir: `${this.projectDir}/.claude/memory`,
|
|
64
|
+
dbFilename: 'memory.db',
|
|
65
|
+
});
|
|
66
|
+
await this.service.initialize();
|
|
67
|
+
this.initialized = true;
|
|
68
|
+
}
|
|
69
|
+
return this.service;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Handle JSON-RPC request
|
|
74
|
+
*/
|
|
75
|
+
async handleRequest(request: JSONRPCRequest): Promise<JSONRPCResponse> {
|
|
76
|
+
try {
|
|
77
|
+
switch (request.method) {
|
|
78
|
+
case 'initialize':
|
|
79
|
+
return this.handleInitialize(request);
|
|
80
|
+
|
|
81
|
+
case 'tools/list':
|
|
82
|
+
return this.handleToolsList(request);
|
|
83
|
+
|
|
84
|
+
case 'tools/call':
|
|
85
|
+
return this.handleToolCall(request);
|
|
86
|
+
|
|
87
|
+
case 'notifications/initialized':
|
|
88
|
+
// Client initialized notification - acknowledge silently
|
|
89
|
+
return { jsonrpc: '2.0', id: request.id, result: {} };
|
|
90
|
+
|
|
91
|
+
default:
|
|
92
|
+
return {
|
|
93
|
+
jsonrpc: '2.0',
|
|
94
|
+
id: request.id,
|
|
95
|
+
error: {
|
|
96
|
+
code: -32601,
|
|
97
|
+
message: `Method not found: ${request.method}`,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
jsonrpc: '2.0',
|
|
104
|
+
id: request.id,
|
|
105
|
+
error: {
|
|
106
|
+
code: -32603,
|
|
107
|
+
message: error instanceof Error ? error.message : 'Internal error',
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Handle initialize request
|
|
115
|
+
*/
|
|
116
|
+
private handleInitialize(request: JSONRPCRequest): JSONRPCResponse {
|
|
117
|
+
return {
|
|
118
|
+
jsonrpc: '2.0',
|
|
119
|
+
id: request.id,
|
|
120
|
+
result: {
|
|
121
|
+
protocolVersion: '2024-11-05',
|
|
122
|
+
capabilities: {
|
|
123
|
+
tools: {},
|
|
124
|
+
},
|
|
125
|
+
serverInfo: {
|
|
126
|
+
name: 'agentkits-memory',
|
|
127
|
+
version: '1.0.0',
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Handle tools/list request
|
|
135
|
+
*/
|
|
136
|
+
private handleToolsList(request: JSONRPCRequest): JSONRPCResponse {
|
|
137
|
+
return {
|
|
138
|
+
jsonrpc: '2.0',
|
|
139
|
+
id: request.id,
|
|
140
|
+
result: {
|
|
141
|
+
tools: MEMORY_TOOLS,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Handle tools/call request
|
|
148
|
+
*/
|
|
149
|
+
private async handleToolCall(request: JSONRPCRequest): Promise<JSONRPCResponse> {
|
|
150
|
+
const params = request.params as ToolCallRequest;
|
|
151
|
+
const result = await this.executeTool(params.name, params.arguments);
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
jsonrpc: '2.0',
|
|
155
|
+
id: request.id,
|
|
156
|
+
result,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Execute a tool
|
|
162
|
+
*/
|
|
163
|
+
private async executeTool(
|
|
164
|
+
name: string,
|
|
165
|
+
args: Record<string, unknown>
|
|
166
|
+
): Promise<ToolCallResult> {
|
|
167
|
+
try {
|
|
168
|
+
const service = await this.ensureInitialized();
|
|
169
|
+
|
|
170
|
+
switch (name) {
|
|
171
|
+
case 'memory_save':
|
|
172
|
+
return this.toolSave(service, args as unknown as MemorySaveArgs);
|
|
173
|
+
|
|
174
|
+
case 'memory_search':
|
|
175
|
+
return this.toolSearch(service, args as unknown as MemorySearchArgs);
|
|
176
|
+
|
|
177
|
+
case 'memory_recall':
|
|
178
|
+
return this.toolRecall(service, args as unknown as MemoryRecallArgs);
|
|
179
|
+
|
|
180
|
+
case 'memory_list':
|
|
181
|
+
return this.toolList(service, args as unknown as MemoryListArgs);
|
|
182
|
+
|
|
183
|
+
case 'memory_status':
|
|
184
|
+
return this.toolStatus(service);
|
|
185
|
+
|
|
186
|
+
default:
|
|
187
|
+
return {
|
|
188
|
+
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
189
|
+
isError: true,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
return {
|
|
194
|
+
content: [{
|
|
195
|
+
type: 'text',
|
|
196
|
+
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
197
|
+
}],
|
|
198
|
+
isError: true,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Save memory tool
|
|
205
|
+
*/
|
|
206
|
+
private async toolSave(
|
|
207
|
+
service: ProjectMemoryService,
|
|
208
|
+
args: MemorySaveArgs
|
|
209
|
+
): Promise<ToolCallResult> {
|
|
210
|
+
const tags = typeof args.tags === 'string'
|
|
211
|
+
? args.tags.split(',').map((t: string) => t.trim())
|
|
212
|
+
: args.tags || [];
|
|
213
|
+
|
|
214
|
+
// Map category to namespace
|
|
215
|
+
const category = args.category || 'observation';
|
|
216
|
+
const namespace = CATEGORY_TO_NAMESPACE[category] || DEFAULT_NAMESPACES.ACTIVE;
|
|
217
|
+
|
|
218
|
+
// Store entry using storeEntry convenience method
|
|
219
|
+
const entry = await service.storeEntry({
|
|
220
|
+
key: `${category}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
221
|
+
content: args.content,
|
|
222
|
+
namespace,
|
|
223
|
+
tags: [...tags, category],
|
|
224
|
+
metadata: {
|
|
225
|
+
importance: args.importance || 'medium',
|
|
226
|
+
source: 'mcp',
|
|
227
|
+
savedAt: new Date().toISOString(),
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
content: [{
|
|
233
|
+
type: 'text',
|
|
234
|
+
text: `Saved to memory (${category}): "${args.content.slice(0, 100)}${args.content.length > 100 ? '...' : ''}"`,
|
|
235
|
+
}],
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Search memory tool
|
|
241
|
+
*/
|
|
242
|
+
private async toolSearch(
|
|
243
|
+
service: ProjectMemoryService,
|
|
244
|
+
args: MemorySearchArgs
|
|
245
|
+
): Promise<ToolCallResult> {
|
|
246
|
+
const limit = typeof args.limit === 'string' ? parseInt(args.limit, 10) : (args.limit || 5);
|
|
247
|
+
|
|
248
|
+
// Map category to namespace
|
|
249
|
+
const namespace = args.category ? CATEGORY_TO_NAMESPACE[args.category] : undefined;
|
|
250
|
+
|
|
251
|
+
// Build query
|
|
252
|
+
const query: MemoryQuery = {
|
|
253
|
+
type: 'hybrid',
|
|
254
|
+
limit,
|
|
255
|
+
namespace,
|
|
256
|
+
content: args.query,
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const results = await service.query(query);
|
|
260
|
+
|
|
261
|
+
if (results.length === 0) {
|
|
262
|
+
return {
|
|
263
|
+
content: [{
|
|
264
|
+
type: 'text',
|
|
265
|
+
text: `No memories found for: "${args.query}"`,
|
|
266
|
+
}],
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const formatted = results.map((entry: MemoryEntry, i: number) => {
|
|
271
|
+
const category = entry.tags.find(t => Object.keys(CATEGORY_TO_NAMESPACE).includes(t)) || entry.namespace;
|
|
272
|
+
return `${i + 1}. [${category}]\n ${entry.content}\n Tags: ${entry.tags.join(', ') || 'none'}`;
|
|
273
|
+
}).join('\n\n');
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
content: [{
|
|
277
|
+
type: 'text',
|
|
278
|
+
text: `Found ${results.length} memories:\n\n${formatted}`,
|
|
279
|
+
}],
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Recall topic tool
|
|
285
|
+
*/
|
|
286
|
+
private async toolRecall(
|
|
287
|
+
service: ProjectMemoryService,
|
|
288
|
+
args: MemoryRecallArgs
|
|
289
|
+
): Promise<ToolCallResult> {
|
|
290
|
+
// Search for topic
|
|
291
|
+
const query: MemoryQuery = {
|
|
292
|
+
type: 'hybrid',
|
|
293
|
+
limit: 10,
|
|
294
|
+
content: args.topic,
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const results = await service.query(query);
|
|
298
|
+
|
|
299
|
+
if (results.length === 0) {
|
|
300
|
+
return {
|
|
301
|
+
content: [{
|
|
302
|
+
type: 'text',
|
|
303
|
+
text: `No memories found about: "${args.topic}"`,
|
|
304
|
+
}],
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Group by namespace
|
|
309
|
+
const byNamespace: Record<string, string[]> = {};
|
|
310
|
+
for (const entry of results) {
|
|
311
|
+
const ns = entry.namespace || 'general';
|
|
312
|
+
if (!byNamespace[ns]) byNamespace[ns] = [];
|
|
313
|
+
byNamespace[ns].push(entry.content);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Format output
|
|
317
|
+
let output = `## Memory Recall: ${args.topic}\n\n`;
|
|
318
|
+
for (const [namespace, items] of Object.entries(byNamespace)) {
|
|
319
|
+
output += `### ${namespace.charAt(0).toUpperCase() + namespace.slice(1)}\n`;
|
|
320
|
+
items.forEach((item: string) => {
|
|
321
|
+
output += `- ${item}\n`;
|
|
322
|
+
});
|
|
323
|
+
output += '\n';
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
content: [{ type: 'text', text: output }],
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* List memories tool
|
|
333
|
+
*/
|
|
334
|
+
private async toolList(
|
|
335
|
+
service: ProjectMemoryService,
|
|
336
|
+
args: MemoryListArgs
|
|
337
|
+
): Promise<ToolCallResult> {
|
|
338
|
+
const limit = typeof args.limit === 'string' ? parseInt(args.limit, 10) : (args.limit || 10);
|
|
339
|
+
|
|
340
|
+
// Map category to namespace
|
|
341
|
+
const namespace = args.category ? CATEGORY_TO_NAMESPACE[args.category] : undefined;
|
|
342
|
+
|
|
343
|
+
// Get recent entries
|
|
344
|
+
const query: MemoryQuery = {
|
|
345
|
+
type: 'hybrid',
|
|
346
|
+
limit,
|
|
347
|
+
namespace,
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const results = await service.query(query);
|
|
351
|
+
|
|
352
|
+
if (results.length === 0) {
|
|
353
|
+
return {
|
|
354
|
+
content: [{
|
|
355
|
+
type: 'text',
|
|
356
|
+
text: 'No memories stored yet.',
|
|
357
|
+
}],
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const formatted = results.map((entry: MemoryEntry, i: number) => {
|
|
362
|
+
const date = new Date(entry.createdAt).toLocaleString();
|
|
363
|
+
const category = entry.tags.find(t => Object.keys(CATEGORY_TO_NAMESPACE).includes(t)) || entry.namespace;
|
|
364
|
+
return `${i + 1}. [${category}] ${entry.content.slice(0, 80)}${entry.content.length > 80 ? '...' : ''}\n Created: ${date}`;
|
|
365
|
+
}).join('\n\n');
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
content: [{
|
|
369
|
+
type: 'text',
|
|
370
|
+
text: `Recent memories (${results.length}):\n\n${formatted}`,
|
|
371
|
+
}],
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Memory status tool
|
|
377
|
+
*/
|
|
378
|
+
private async toolStatus(service: ProjectMemoryService): Promise<ToolCallResult> {
|
|
379
|
+
const stats = await service.getStats();
|
|
380
|
+
|
|
381
|
+
const output = `## Memory System Status
|
|
382
|
+
|
|
383
|
+
- **Entries**: ${stats.totalEntries}
|
|
384
|
+
- **Namespaces**: ${Object.keys(stats.entriesByNamespace || {}).join(', ') || 'none'}
|
|
385
|
+
- **Database**: ${this.projectDir}/.claude/memory/memory.db
|
|
386
|
+
- **Status**: Connected
|
|
387
|
+
|
|
388
|
+
### Namespace Breakdown
|
|
389
|
+
${Object.entries(stats.entriesByNamespace || {}).map(([ns, count]) => `- ${ns}: ${count}`).join('\n') || '- No entries yet'}
|
|
390
|
+
`;
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
content: [{ type: 'text', text: output }],
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Start the server
|
|
399
|
+
*/
|
|
400
|
+
async start(): Promise<void> {
|
|
401
|
+
const rl = readline.createInterface({
|
|
402
|
+
input: process.stdin,
|
|
403
|
+
output: process.stdout,
|
|
404
|
+
terminal: false,
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// Handle each line as a JSON-RPC request
|
|
408
|
+
rl.on('line', async (line) => {
|
|
409
|
+
try {
|
|
410
|
+
const request = JSON.parse(line) as JSONRPCRequest;
|
|
411
|
+
const response = await this.handleRequest(request);
|
|
412
|
+
|
|
413
|
+
// Only send response if there's an id (not a notification)
|
|
414
|
+
if (request.id !== undefined) {
|
|
415
|
+
console.log(JSON.stringify(response));
|
|
416
|
+
}
|
|
417
|
+
} catch (error) {
|
|
418
|
+
// Parse error
|
|
419
|
+
console.log(JSON.stringify({
|
|
420
|
+
jsonrpc: '2.0',
|
|
421
|
+
id: null,
|
|
422
|
+
error: {
|
|
423
|
+
code: -32700,
|
|
424
|
+
message: 'Parse error',
|
|
425
|
+
},
|
|
426
|
+
}));
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
rl.on('close', () => {
|
|
431
|
+
process.exit(0);
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Start server
|
|
437
|
+
const server = new MemoryMCPServer();
|
|
438
|
+
server.start().catch((error) => {
|
|
439
|
+
console.error('Failed to start MCP server:', error);
|
|
440
|
+
process.exit(1);
|
|
441
|
+
});
|