@mndrk/memx 0.3.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/.claude/settings.local.json +7 -0
- package/README.md +183 -0
- package/index.js +1969 -0
- package/mcp.js +308 -0
- package/package.json +27 -0
package/mcp.js
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP Server for mem - exposes memory operations as tools
|
|
5
|
+
*
|
|
6
|
+
* Usage: mem mcp [--dir <path>]
|
|
7
|
+
*
|
|
8
|
+
* Tools exposed:
|
|
9
|
+
* - mem_context: Get full context (goal, state, learnings)
|
|
10
|
+
* - mem_checkpoint: Save a progress checkpoint
|
|
11
|
+
* - mem_learn: Add a learning
|
|
12
|
+
* - mem_next: Set next step
|
|
13
|
+
* - mem_status: Get current status
|
|
14
|
+
* - mem_stuck: Mark/clear blocker
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { spawnSync } = require('child_process');
|
|
18
|
+
const readline = require('readline');
|
|
19
|
+
|
|
20
|
+
// Run mem command and return output
|
|
21
|
+
function runMem(args, cwd) {
|
|
22
|
+
const result = spawnSync('mem', args, {
|
|
23
|
+
cwd: cwd || process.cwd(),
|
|
24
|
+
encoding: 'utf8',
|
|
25
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
success: result.status === 0,
|
|
29
|
+
output: result.stdout?.trim() || '',
|
|
30
|
+
error: result.stderr?.trim() || ''
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// MCP Protocol handler
|
|
35
|
+
class MCPServer {
|
|
36
|
+
constructor(workDir) {
|
|
37
|
+
this.workDir = workDir || process.cwd();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// List available tools
|
|
41
|
+
listTools() {
|
|
42
|
+
return {
|
|
43
|
+
tools: [
|
|
44
|
+
{
|
|
45
|
+
name: 'mem_context',
|
|
46
|
+
description: 'Get full memory context - goal, state, learnings, playbook. Use on wake to hydrate.',
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {},
|
|
50
|
+
required: []
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'mem_status',
|
|
55
|
+
description: 'Get current task status summary',
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {},
|
|
59
|
+
required: []
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'mem_checkpoint',
|
|
64
|
+
description: 'Save a progress checkpoint',
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
message: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
description: 'Checkpoint message describing what was accomplished'
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
required: ['message']
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'mem_learn',
|
|
78
|
+
description: 'Add a learning or insight',
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
properties: {
|
|
82
|
+
insight: {
|
|
83
|
+
type: 'string',
|
|
84
|
+
description: 'The learning or insight to record'
|
|
85
|
+
},
|
|
86
|
+
global: {
|
|
87
|
+
type: 'boolean',
|
|
88
|
+
description: 'If true, add to global playbook instead of task memory'
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
required: ['insight']
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'mem_next',
|
|
96
|
+
description: 'Set the next step to work on',
|
|
97
|
+
inputSchema: {
|
|
98
|
+
type: 'object',
|
|
99
|
+
properties: {
|
|
100
|
+
step: {
|
|
101
|
+
type: 'string',
|
|
102
|
+
description: 'Description of the next step'
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
required: ['step']
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'mem_stuck',
|
|
110
|
+
description: 'Mark task as stuck with a blocker, or clear blockers',
|
|
111
|
+
inputSchema: {
|
|
112
|
+
type: 'object',
|
|
113
|
+
properties: {
|
|
114
|
+
reason: {
|
|
115
|
+
type: 'string',
|
|
116
|
+
description: 'Reason for being stuck, or "clear" to remove blocker'
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
required: ['reason']
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'mem_goal',
|
|
124
|
+
description: 'Get or set the current goal',
|
|
125
|
+
inputSchema: {
|
|
126
|
+
type: 'object',
|
|
127
|
+
properties: {
|
|
128
|
+
goal: {
|
|
129
|
+
type: 'string',
|
|
130
|
+
description: 'New goal to set (omit to just get current goal)'
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
required: []
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: 'mem_tasks',
|
|
138
|
+
description: 'List all tasks (branches)',
|
|
139
|
+
inputSchema: {
|
|
140
|
+
type: 'object',
|
|
141
|
+
properties: {},
|
|
142
|
+
required: []
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: 'mem_switch',
|
|
147
|
+
description: 'Switch to a different task',
|
|
148
|
+
inputSchema: {
|
|
149
|
+
type: 'object',
|
|
150
|
+
properties: {
|
|
151
|
+
task: {
|
|
152
|
+
type: 'string',
|
|
153
|
+
description: 'Task name to switch to'
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
required: ['task']
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Execute a tool
|
|
164
|
+
callTool(name, args) {
|
|
165
|
+
switch (name) {
|
|
166
|
+
case 'mem_context':
|
|
167
|
+
return runMem(['context'], this.workDir);
|
|
168
|
+
|
|
169
|
+
case 'mem_status':
|
|
170
|
+
return runMem(['status'], this.workDir);
|
|
171
|
+
|
|
172
|
+
case 'mem_checkpoint':
|
|
173
|
+
return runMem(['checkpoint', args.message], this.workDir);
|
|
174
|
+
|
|
175
|
+
case 'mem_learn':
|
|
176
|
+
const learnArgs = args.global ? ['learn', '-g', args.insight] : ['learn', args.insight];
|
|
177
|
+
return runMem(learnArgs, this.workDir);
|
|
178
|
+
|
|
179
|
+
case 'mem_next':
|
|
180
|
+
return runMem(['next', args.step], this.workDir);
|
|
181
|
+
|
|
182
|
+
case 'mem_stuck':
|
|
183
|
+
return runMem(['stuck', args.reason], this.workDir);
|
|
184
|
+
|
|
185
|
+
case 'mem_goal':
|
|
186
|
+
return args.goal
|
|
187
|
+
? runMem(['goal', args.goal], this.workDir)
|
|
188
|
+
: runMem(['goal'], this.workDir);
|
|
189
|
+
|
|
190
|
+
case 'mem_tasks':
|
|
191
|
+
return runMem(['tasks'], this.workDir);
|
|
192
|
+
|
|
193
|
+
case 'mem_switch':
|
|
194
|
+
return runMem(['switch', args.task], this.workDir);
|
|
195
|
+
|
|
196
|
+
default:
|
|
197
|
+
return { success: false, error: `Unknown tool: ${name}` };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Handle incoming JSON-RPC message
|
|
202
|
+
handleMessage(message) {
|
|
203
|
+
const { id, method, params } = message;
|
|
204
|
+
|
|
205
|
+
switch (method) {
|
|
206
|
+
case 'initialize':
|
|
207
|
+
return {
|
|
208
|
+
jsonrpc: '2.0',
|
|
209
|
+
id,
|
|
210
|
+
result: {
|
|
211
|
+
protocolVersion: '2024-11-05',
|
|
212
|
+
capabilities: { tools: {} },
|
|
213
|
+
serverInfo: {
|
|
214
|
+
name: 'mem',
|
|
215
|
+
version: '0.1.0'
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
case 'tools/list':
|
|
221
|
+
return {
|
|
222
|
+
jsonrpc: '2.0',
|
|
223
|
+
id,
|
|
224
|
+
result: this.listTools()
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
case 'tools/call':
|
|
228
|
+
const { name, arguments: toolArgs } = params;
|
|
229
|
+
const result = this.callTool(name, toolArgs || {});
|
|
230
|
+
return {
|
|
231
|
+
jsonrpc: '2.0',
|
|
232
|
+
id,
|
|
233
|
+
result: {
|
|
234
|
+
content: [
|
|
235
|
+
{
|
|
236
|
+
type: 'text',
|
|
237
|
+
text: result.success ? result.output : `Error: ${result.error}`
|
|
238
|
+
}
|
|
239
|
+
],
|
|
240
|
+
isError: !result.success
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
case 'notifications/initialized':
|
|
245
|
+
// Client ready, no response needed
|
|
246
|
+
return null;
|
|
247
|
+
|
|
248
|
+
default:
|
|
249
|
+
return {
|
|
250
|
+
jsonrpc: '2.0',
|
|
251
|
+
id,
|
|
252
|
+
error: {
|
|
253
|
+
code: -32601,
|
|
254
|
+
message: `Method not found: ${method}`
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Start the server (stdio transport)
|
|
261
|
+
start() {
|
|
262
|
+
const rl = readline.createInterface({
|
|
263
|
+
input: process.stdin,
|
|
264
|
+
output: process.stdout,
|
|
265
|
+
terminal: false
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
rl.on('line', (line) => {
|
|
269
|
+
try {
|
|
270
|
+
const message = JSON.parse(line);
|
|
271
|
+
const response = this.handleMessage(message);
|
|
272
|
+
if (response) {
|
|
273
|
+
console.log(JSON.stringify(response));
|
|
274
|
+
}
|
|
275
|
+
} catch (err) {
|
|
276
|
+
console.log(JSON.stringify({
|
|
277
|
+
jsonrpc: '2.0',
|
|
278
|
+
id: null,
|
|
279
|
+
error: {
|
|
280
|
+
code: -32700,
|
|
281
|
+
message: 'Parse error'
|
|
282
|
+
}
|
|
283
|
+
}));
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
rl.on('close', () => {
|
|
288
|
+
process.exit(0);
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Run if called directly
|
|
294
|
+
if (require.main === module) {
|
|
295
|
+
const args = process.argv.slice(2);
|
|
296
|
+
let workDir = process.cwd();
|
|
297
|
+
|
|
298
|
+
// Parse --dir argument
|
|
299
|
+
const dirIndex = args.indexOf('--dir');
|
|
300
|
+
if (dirIndex !== -1 && args[dirIndex + 1]) {
|
|
301
|
+
workDir = args[dirIndex + 1];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const server = new MCPServer(workDir);
|
|
305
|
+
server.start();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
module.exports = { MCPServer };
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mndrk/memx",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Persistent memory for AI agents - git-backed, branch-per-task, CLI interface",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mem": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"ai",
|
|
14
|
+
"agent",
|
|
15
|
+
"memory",
|
|
16
|
+
"cli",
|
|
17
|
+
"persistent",
|
|
18
|
+
"llm",
|
|
19
|
+
"context"
|
|
20
|
+
],
|
|
21
|
+
"author": "mnrdk",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/ramarlina/memx"
|
|
26
|
+
}
|
|
27
|
+
}
|