@slorenzot/memento-mcp-server 0.1.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/package.json +21 -0
- package/src/MCPServer.test.ts +116 -0
- package/src/MCPServer.ts +512 -0
- package/src/index.ts +13 -0
- package/tsconfig.json +13 -0
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@slorenzot/memento-mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server implementation for Memento",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "bun run src/index.ts",
|
|
9
|
+
"build": "bun tsc --declaration --declarationMap --emitDeclarationOnly --outDir ./dist",
|
|
10
|
+
"test": "bun test",
|
|
11
|
+
"typecheck": "bun tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@slorenzot/memento-core": "workspace:*",
|
|
15
|
+
"@modelcontextprotocol/sdk": "^0.4.0",
|
|
16
|
+
"zod": "^3.22.4"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"bun-types": "^1.3.11"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
|
2
|
+
import { MCPServer } from './MCPServer';
|
|
3
|
+
|
|
4
|
+
describe('MCPServer', () => {
|
|
5
|
+
let server: MCPServer;
|
|
6
|
+
let testDbPath: string;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
testDbPath = `/tmp/test-mcp-${Date.now()}.db`;
|
|
10
|
+
server = new MCPServer(testDbPath);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
server.close();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should initialize without errors', () => {
|
|
18
|
+
expect(server).toBeDefined();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('Tool Handling', () => {
|
|
22
|
+
it('should have all required tools', async () => {
|
|
23
|
+
const tools = server['getToolDefinitions']();
|
|
24
|
+
const toolNames = tools.map((t) => t.name);
|
|
25
|
+
|
|
26
|
+
expect(toolNames).toContain('mem_save');
|
|
27
|
+
expect(toolNames).toContain('mem_update');
|
|
28
|
+
expect(toolNames).toContain('mem_delete');
|
|
29
|
+
expect(toolNames).toContain('mem_search');
|
|
30
|
+
expect(toolNames).toContain('mem_get_observation');
|
|
31
|
+
expect(toolNames).toContain('mem_save_prompt');
|
|
32
|
+
expect(toolNames).toContain('mem_session_summary');
|
|
33
|
+
expect(toolNames).toContain('mem_context');
|
|
34
|
+
expect(toolNames).toContain('mem_timeline');
|
|
35
|
+
expect(toolNames).toContain('mem_stats');
|
|
36
|
+
expect(toolNames).toContain('mem_session_start');
|
|
37
|
+
expect(toolNames).toContain('mem_session_end');
|
|
38
|
+
expect(toolNames).toContain('mem_suggest_topic_key');
|
|
39
|
+
expect(toolNames).toContain('mem_capture_passive');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should handle mem_save tool', async () => {
|
|
43
|
+
const result = await server['handleToolCall']('mem_save', {
|
|
44
|
+
title: 'Test Observation',
|
|
45
|
+
content: 'Test content',
|
|
46
|
+
type: 'note',
|
|
47
|
+
project_id: 'test-project',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(result.isError).toBeFalse();
|
|
51
|
+
const data = JSON.parse(result.content[0].text as string);
|
|
52
|
+
expect(data.success).toBeTrue();
|
|
53
|
+
expect(data.id).toBeDefined();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should handle mem_search tool', async () => {
|
|
57
|
+
const result = await server['handleToolCall']('mem_search', {
|
|
58
|
+
query: 'test',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(result.content).toBeDefined();
|
|
62
|
+
expect(result.content[0].text).toBeDefined();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should handle mem_suggest_topic_key tool', async () => {
|
|
66
|
+
const result = await server['handleToolCall']('mem_suggest_topic_key', {
|
|
67
|
+
title: 'Authentication Bug Fix',
|
|
68
|
+
content: 'Fixed JWT token validation',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(result.isError).toBeFalse();
|
|
72
|
+
const data = JSON.parse(result.content[0].text as string);
|
|
73
|
+
expect(data.topicKey).toBeDefined();
|
|
74
|
+
expect(data.confidence).toBeNumber();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should handle mem_session_start tool', async () => {
|
|
78
|
+
const result = await server['handleToolCall']('mem_session_start', {
|
|
79
|
+
project_id: 'test-project',
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
expect(result.isError).toBeFalse();
|
|
83
|
+
const data = JSON.parse(result.content[0].text as string);
|
|
84
|
+
expect(data.success).toBeTrue();
|
|
85
|
+
expect(data.id).toBeDefined();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should handle mem_stats tool', async () => {
|
|
89
|
+
const result = await server['handleToolCall']('mem_stats', {});
|
|
90
|
+
|
|
91
|
+
expect(result.isError).toBeFalse();
|
|
92
|
+
const data = JSON.parse(result.content[0].text as string);
|
|
93
|
+
expect(data.totalObservations).toBeNumber();
|
|
94
|
+
expect(data.byType).toBeDefined();
|
|
95
|
+
expect(data.byProject).toBeDefined();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('Error Handling', () => {
|
|
100
|
+
it('should handle invalid tool name', async () => {
|
|
101
|
+
const result = await server['handleToolCall']('invalid_tool', {});
|
|
102
|
+
|
|
103
|
+
expect(result.isError).toBeTrue();
|
|
104
|
+
const data = JSON.parse(result.content[0].text as string);
|
|
105
|
+
expect(data.error).toBeDefined();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should handle missing required parameters', async () => {
|
|
109
|
+
const result = await server['handleToolCall']('mem_save', {
|
|
110
|
+
title: 'Missing required fields',
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(result.isError).toBeTrue();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
package/src/MCPServer.ts
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import {
|
|
4
|
+
CallToolRequestSchema,
|
|
5
|
+
ListToolsRequestSchema,
|
|
6
|
+
type CallToolResult,
|
|
7
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
8
|
+
import { MemoryEngine } from '@slorenzot/memento-core';
|
|
9
|
+
import type { Observation, Session } from '@slorenzot/memento-core';
|
|
10
|
+
|
|
11
|
+
export class MCPServer {
|
|
12
|
+
private server: Server;
|
|
13
|
+
private memory: MemoryEngine;
|
|
14
|
+
private activeSession: Session | null = null;
|
|
15
|
+
|
|
16
|
+
constructor(dbPath: string = './data/memento.db') {
|
|
17
|
+
this.server = new Server({
|
|
18
|
+
name: 'memento-server',
|
|
19
|
+
version: '0.1.0',
|
|
20
|
+
});
|
|
21
|
+
this.memory = new MemoryEngine(dbPath);
|
|
22
|
+
this.setupHandlers();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private setupHandlers() {
|
|
26
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
27
|
+
return {
|
|
28
|
+
tools: this.getToolDefinitions(),
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
33
|
+
return this.handleToolCall(request.params.name, request.params.arguments as Record<string, unknown>);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private getToolDefinitions() {
|
|
38
|
+
return [
|
|
39
|
+
{
|
|
40
|
+
name: 'mem_save',
|
|
41
|
+
description: 'Save an observation to memory',
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {
|
|
45
|
+
title: { type: 'string', description: 'Title of the observation' },
|
|
46
|
+
content: { type: 'string', description: 'Content of the observation' },
|
|
47
|
+
type: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
enum: ['decision', 'bug', 'discovery', 'note'],
|
|
50
|
+
description: 'Type of observation',
|
|
51
|
+
},
|
|
52
|
+
topic_key: { type: 'string', description: 'Topic key for grouping' },
|
|
53
|
+
project_id: { type: 'string', description: 'Project identifier' },
|
|
54
|
+
metadata: { type: 'object', description: 'Additional metadata' },
|
|
55
|
+
},
|
|
56
|
+
required: ['title', 'content', 'type', 'project_id'],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'mem_update',
|
|
61
|
+
description: 'Update an existing observation',
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
id: { type: 'number', description: 'Observation ID' },
|
|
66
|
+
title: { type: 'string', description: 'New title' },
|
|
67
|
+
content: { type: 'string', description: 'New content' },
|
|
68
|
+
topic_key: { type: 'string', description: 'New topic key' },
|
|
69
|
+
metadata: { type: 'object', description: 'New metadata' },
|
|
70
|
+
},
|
|
71
|
+
required: ['id'],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'mem_delete',
|
|
76
|
+
description: 'Delete an observation',
|
|
77
|
+
inputSchema: {
|
|
78
|
+
type: 'object',
|
|
79
|
+
properties: {
|
|
80
|
+
id: { type: 'number', description: 'Observation ID' },
|
|
81
|
+
soft: { type: 'boolean', description: 'Soft delete (default: true)' },
|
|
82
|
+
},
|
|
83
|
+
required: ['id'],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'mem_search',
|
|
88
|
+
description: 'Search observations',
|
|
89
|
+
inputSchema: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: {
|
|
92
|
+
query: { type: 'string', description: 'Full-text search query' },
|
|
93
|
+
type: {
|
|
94
|
+
type: 'string',
|
|
95
|
+
enum: ['decision', 'bug', 'discovery', 'note'],
|
|
96
|
+
description: 'Filter by type',
|
|
97
|
+
},
|
|
98
|
+
project_id: { type: 'string', description: 'Filter by project' },
|
|
99
|
+
topic_key: { type: 'string', description: 'Filter by topic' },
|
|
100
|
+
limit: { type: 'number', description: 'Result limit' },
|
|
101
|
+
offset: { type: 'number', description: 'Result offset' },
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'mem_get_observation',
|
|
107
|
+
description: 'Get observation by ID',
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: {
|
|
111
|
+
id: { type: 'number', description: 'Observation ID' },
|
|
112
|
+
},
|
|
113
|
+
required: ['id'],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'mem_save_prompt',
|
|
118
|
+
description: 'Save a user prompt',
|
|
119
|
+
inputSchema: {
|
|
120
|
+
type: 'object',
|
|
121
|
+
properties: {
|
|
122
|
+
content: { type: 'string', description: 'Prompt content' },
|
|
123
|
+
project_id: { type: 'string', description: 'Project identifier' },
|
|
124
|
+
metadata: { type: 'object', description: 'Additional metadata' },
|
|
125
|
+
},
|
|
126
|
+
required: ['content', 'project_id'],
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'mem_session_summary',
|
|
131
|
+
description: 'Get current session summary',
|
|
132
|
+
inputSchema: {
|
|
133
|
+
type: 'object',
|
|
134
|
+
properties: {},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'mem_context',
|
|
139
|
+
description: 'Get recent context',
|
|
140
|
+
inputSchema: {
|
|
141
|
+
type: 'object',
|
|
142
|
+
properties: {
|
|
143
|
+
project_id: { type: 'string', description: 'Filter by project' },
|
|
144
|
+
limit: { type: 'number', description: 'Number of recent items' },
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: 'mem_timeline',
|
|
150
|
+
description: 'Get timeline of observations',
|
|
151
|
+
inputSchema: {
|
|
152
|
+
type: 'object',
|
|
153
|
+
properties: {
|
|
154
|
+
project_id: { type: 'string', description: 'Filter by project' },
|
|
155
|
+
limit: { type: 'number', description: 'Result limit' },
|
|
156
|
+
offset: { type: 'number', description: 'Result offset' },
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: 'mem_stats',
|
|
162
|
+
description: 'Get memory statistics',
|
|
163
|
+
inputSchema: {
|
|
164
|
+
type: 'object',
|
|
165
|
+
properties: {},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: 'mem_session_start',
|
|
170
|
+
description: 'Start a new session',
|
|
171
|
+
inputSchema: {
|
|
172
|
+
type: 'object',
|
|
173
|
+
properties: {
|
|
174
|
+
project_id: { type: 'string', description: 'Project identifier' },
|
|
175
|
+
metadata: { type: 'object', description: 'Session metadata' },
|
|
176
|
+
},
|
|
177
|
+
required: ['project_id'],
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: 'mem_session_end',
|
|
182
|
+
description: 'End current session',
|
|
183
|
+
inputSchema: {
|
|
184
|
+
type: 'object',
|
|
185
|
+
properties: {},
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: 'mem_suggest_topic_key',
|
|
190
|
+
description: 'Suggest a topic key based on content',
|
|
191
|
+
inputSchema: {
|
|
192
|
+
type: 'object',
|
|
193
|
+
properties: {
|
|
194
|
+
title: { type: 'string', description: 'Observation title' },
|
|
195
|
+
content: { type: 'string', description: 'Observation content' },
|
|
196
|
+
},
|
|
197
|
+
required: ['title'],
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: 'mem_capture_passive',
|
|
202
|
+
description: 'Passively capture context',
|
|
203
|
+
inputSchema: {
|
|
204
|
+
type: 'object',
|
|
205
|
+
properties: {
|
|
206
|
+
observations: {
|
|
207
|
+
type: 'array',
|
|
208
|
+
items: { type: 'string' },
|
|
209
|
+
description: 'List of observations to capture',
|
|
210
|
+
},
|
|
211
|
+
project_id: { type: 'string', description: 'Project identifier' },
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private async handleToolCall(name: string, args: Record<string, unknown>): Promise<CallToolResult> {
|
|
219
|
+
try {
|
|
220
|
+
let result: unknown;
|
|
221
|
+
|
|
222
|
+
switch (name) {
|
|
223
|
+
case 'mem_save': {
|
|
224
|
+
const projectId = args.project_id as string;
|
|
225
|
+
const sessionId = await this.getOrCreateSession(projectId);
|
|
226
|
+
const observation = await this.memory.createObservation({
|
|
227
|
+
sessionId,
|
|
228
|
+
title: args.title as string,
|
|
229
|
+
content: args.content as string,
|
|
230
|
+
type: args.type as Observation['type'],
|
|
231
|
+
topicKey: (args.topic_key as string) || null,
|
|
232
|
+
projectId,
|
|
233
|
+
metadata: (args.metadata as Record<string, unknown>) || {},
|
|
234
|
+
});
|
|
235
|
+
result = { id: observation.id, uuid: observation.uuid, success: true };
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
case 'mem_update': {
|
|
240
|
+
const observation = await this.memory.updateObservation(args.id as number, {
|
|
241
|
+
title: args.title as string | undefined,
|
|
242
|
+
content: args.content as string | undefined,
|
|
243
|
+
topicKey: args.topic_key as string | undefined,
|
|
244
|
+
metadata: args.metadata as Record<string, unknown> | undefined,
|
|
245
|
+
});
|
|
246
|
+
result = { id: observation.id, success: true };
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
case 'mem_delete': {
|
|
251
|
+
await this.memory.deleteObservation(args.id as number);
|
|
252
|
+
result = { id: args.id, success: true };
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
case 'mem_search': {
|
|
257
|
+
const searchResult = await this.memory.search({
|
|
258
|
+
query: args.query as string | undefined,
|
|
259
|
+
type: args.type as Observation['type'] | undefined,
|
|
260
|
+
projectId: args.project_id as string | undefined,
|
|
261
|
+
topicKey: args.topic_key as string | undefined,
|
|
262
|
+
limit: args.limit as number | undefined,
|
|
263
|
+
offset: args.offset as number | undefined,
|
|
264
|
+
});
|
|
265
|
+
result = {
|
|
266
|
+
observations: searchResult.observations.map((o) => ({
|
|
267
|
+
id: o.id,
|
|
268
|
+
uuid: o.uuid,
|
|
269
|
+
title: o.title,
|
|
270
|
+
content: o.content,
|
|
271
|
+
type: o.type,
|
|
272
|
+
topicKey: o.topicKey,
|
|
273
|
+
projectId: o.projectId,
|
|
274
|
+
createdAt: o.createdAt.toISOString(),
|
|
275
|
+
})),
|
|
276
|
+
total: searchResult.total,
|
|
277
|
+
};
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
case 'mem_get_observation': {
|
|
282
|
+
const observation = await this.memory.getObservation(args.id as number);
|
|
283
|
+
if (!observation) {
|
|
284
|
+
throw new Error('Observation not found');
|
|
285
|
+
}
|
|
286
|
+
result = {
|
|
287
|
+
id: observation.id,
|
|
288
|
+
uuid: observation.uuid,
|
|
289
|
+
title: observation.title,
|
|
290
|
+
content: observation.content,
|
|
291
|
+
type: observation.type,
|
|
292
|
+
topicKey: observation.topicKey,
|
|
293
|
+
projectId: observation.projectId,
|
|
294
|
+
createdAt: observation.createdAt.toISOString(),
|
|
295
|
+
metadata: observation.metadata,
|
|
296
|
+
};
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
case 'mem_save_prompt': {
|
|
301
|
+
if (!this.activeSession) {
|
|
302
|
+
throw new Error('No active session');
|
|
303
|
+
}
|
|
304
|
+
const projectId = args.project_id as string;
|
|
305
|
+
const prompt = await this.memory.savePrompt({
|
|
306
|
+
sessionId: this.activeSession.id,
|
|
307
|
+
content: args.content as string,
|
|
308
|
+
projectId,
|
|
309
|
+
metadata: (args.metadata as Record<string, unknown>) || {},
|
|
310
|
+
});
|
|
311
|
+
result = { id: prompt.id, uuid: prompt.uuid, success: true };
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
case 'mem_session_summary': {
|
|
316
|
+
if (!this.activeSession) {
|
|
317
|
+
throw new Error('No active session');
|
|
318
|
+
}
|
|
319
|
+
const searchResult = await this.memory.search({
|
|
320
|
+
projectId: this.activeSession.projectId,
|
|
321
|
+
limit: 10,
|
|
322
|
+
});
|
|
323
|
+
result = {
|
|
324
|
+
session: {
|
|
325
|
+
id: this.activeSession.id,
|
|
326
|
+
uuid: this.activeSession.uuid,
|
|
327
|
+
projectId: this.activeSession.projectId,
|
|
328
|
+
startedAt: this.activeSession.startedAt.toISOString(),
|
|
329
|
+
endedAt: this.activeSession.endedAt?.toISOString(),
|
|
330
|
+
},
|
|
331
|
+
recentObservations: searchResult.observations.map((o) => ({
|
|
332
|
+
id: o.id,
|
|
333
|
+
title: o.title,
|
|
334
|
+
type: o.type,
|
|
335
|
+
createdAt: o.createdAt.toISOString(),
|
|
336
|
+
})),
|
|
337
|
+
};
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
case 'mem_context': {
|
|
342
|
+
const limit = (args.limit as number) || 20;
|
|
343
|
+
const searchResult = await this.memory.search({
|
|
344
|
+
projectId: args.project_id as string | undefined,
|
|
345
|
+
limit,
|
|
346
|
+
});
|
|
347
|
+
result = {
|
|
348
|
+
observations: searchResult.observations.map((o) => ({
|
|
349
|
+
id: o.id,
|
|
350
|
+
title: o.title,
|
|
351
|
+
type: o.type,
|
|
352
|
+
content: o.content.slice(0, 200),
|
|
353
|
+
createdAt: o.createdAt.toISOString(),
|
|
354
|
+
})),
|
|
355
|
+
total: searchResult.total,
|
|
356
|
+
};
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
case 'mem_timeline': {
|
|
361
|
+
const searchResult = await this.memory.search({
|
|
362
|
+
projectId: args.project_id as string | undefined,
|
|
363
|
+
limit: args.limit as number | undefined,
|
|
364
|
+
offset: args.offset as number | undefined,
|
|
365
|
+
});
|
|
366
|
+
result = {
|
|
367
|
+
observations: searchResult.observations.map((o) => ({
|
|
368
|
+
id: o.id,
|
|
369
|
+
title: o.title,
|
|
370
|
+
type: o.type,
|
|
371
|
+
topicKey: o.topicKey,
|
|
372
|
+
createdAt: o.createdAt.toISOString(),
|
|
373
|
+
})),
|
|
374
|
+
total: searchResult.total,
|
|
375
|
+
};
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
case 'mem_stats': {
|
|
380
|
+
const allResult = await this.memory.search({});
|
|
381
|
+
const byType = allResult.observations.reduce(
|
|
382
|
+
(acc, obs) => {
|
|
383
|
+
acc[obs.type] = (acc[obs.type] || 0) + 1;
|
|
384
|
+
return acc;
|
|
385
|
+
},
|
|
386
|
+
{} as Record<string, number>
|
|
387
|
+
);
|
|
388
|
+
const byProject = allResult.observations.reduce(
|
|
389
|
+
(acc, obs) => {
|
|
390
|
+
acc[obs.projectId] = (acc[obs.projectId] || 0) + 1;
|
|
391
|
+
return acc;
|
|
392
|
+
},
|
|
393
|
+
{} as Record<string, number>
|
|
394
|
+
);
|
|
395
|
+
result = {
|
|
396
|
+
totalObservations: allResult.total,
|
|
397
|
+
byType,
|
|
398
|
+
byProject,
|
|
399
|
+
activeSession: this.activeSession
|
|
400
|
+
? {
|
|
401
|
+
id: this.activeSession.id,
|
|
402
|
+
projectId: this.activeSession.projectId,
|
|
403
|
+
startedAt: this.activeSession.startedAt.toISOString(),
|
|
404
|
+
}
|
|
405
|
+
: null,
|
|
406
|
+
};
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
case 'mem_session_start': {
|
|
411
|
+
this.activeSession = await this.memory.createSession({
|
|
412
|
+
projectId: args.project_id as string,
|
|
413
|
+
endedAt: null,
|
|
414
|
+
metadata: (args.metadata as Record<string, unknown>) || {},
|
|
415
|
+
});
|
|
416
|
+
result = {
|
|
417
|
+
id: this.activeSession.id,
|
|
418
|
+
uuid: this.activeSession.uuid,
|
|
419
|
+
success: true,
|
|
420
|
+
};
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
case 'mem_session_end': {
|
|
425
|
+
if (!this.activeSession) {
|
|
426
|
+
throw new Error('No active session');
|
|
427
|
+
}
|
|
428
|
+
this.activeSession = await this.memory.endSession(this.activeSession.id);
|
|
429
|
+
result = {
|
|
430
|
+
id: this.activeSession.id,
|
|
431
|
+
uuid: this.activeSession.uuid,
|
|
432
|
+
endedAt: this.activeSession.endedAt?.toISOString(),
|
|
433
|
+
success: true,
|
|
434
|
+
};
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
case 'mem_suggest_topic_key': {
|
|
439
|
+
const title = args.title as string;
|
|
440
|
+
const content = args.content as string;
|
|
441
|
+
const text = `${title} ${content || ''}`;
|
|
442
|
+
const words = text.toLowerCase().split(/\s+/);
|
|
443
|
+
const keywords = words.filter((w) => w.length > 3);
|
|
444
|
+
const topicKey = keywords.slice(0, 3).join('-').toLowerCase();
|
|
445
|
+
result = { topicKey, confidence: 0.8 };
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
case 'mem_capture_passive': {
|
|
450
|
+
const projectId = args.project_id as string;
|
|
451
|
+
const observations = args.observations as string[];
|
|
452
|
+
const sessionId = await this.getOrCreateSession(projectId);
|
|
453
|
+
|
|
454
|
+
const created = [];
|
|
455
|
+
for (const obsText of observations) {
|
|
456
|
+
const obs = await this.memory.createObservation({
|
|
457
|
+
sessionId,
|
|
458
|
+
title: `Passive Capture ${created.length + 1}`,
|
|
459
|
+
content: obsText,
|
|
460
|
+
type: 'note',
|
|
461
|
+
topicKey: null,
|
|
462
|
+
projectId,
|
|
463
|
+
metadata: { passive: true },
|
|
464
|
+
});
|
|
465
|
+
created.push({ id: obs.id, uuid: obs.uuid });
|
|
466
|
+
}
|
|
467
|
+
result = { observations: created, success: true };
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
default:
|
|
472
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return {
|
|
476
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
477
|
+
isError: false,
|
|
478
|
+
};
|
|
479
|
+
} catch (error) {
|
|
480
|
+
return {
|
|
481
|
+
content: [
|
|
482
|
+
{
|
|
483
|
+
type: 'text',
|
|
484
|
+
text: JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error' }, null, 2),
|
|
485
|
+
},
|
|
486
|
+
],
|
|
487
|
+
isError: true,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
private async getOrCreateSession(projectId: string): Promise<number> {
|
|
493
|
+
if (this.activeSession) {
|
|
494
|
+
return this.activeSession.id;
|
|
495
|
+
}
|
|
496
|
+
this.activeSession = await this.memory.createSession({
|
|
497
|
+
projectId,
|
|
498
|
+
endedAt: null,
|
|
499
|
+
metadata: {},
|
|
500
|
+
});
|
|
501
|
+
return this.activeSession.id;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
async start() {
|
|
505
|
+
const transport = new StdioServerTransport();
|
|
506
|
+
await this.server.connect(transport); // eslint-disable-line @typescript-eslint/require-await
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
close() {
|
|
510
|
+
this.memory.close();
|
|
511
|
+
}
|
|
512
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { MCPServer } from './MCPServer';
|
|
2
|
+
|
|
3
|
+
const server = new MCPServer();
|
|
4
|
+
|
|
5
|
+
process.on('SIGINT', () => {
|
|
6
|
+
server.close();
|
|
7
|
+
process.exit(0);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
server.start().catch((error) => {
|
|
11
|
+
console.error('Failed to start MCP server:', error);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"sourceMap": true,
|
|
9
|
+
"removeComments": true
|
|
10
|
+
},
|
|
11
|
+
"include": ["src/**/*"],
|
|
12
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx"]
|
|
13
|
+
}
|