shabti 2.0.0 → 2.2.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 +47 -0
- package/package.json +4 -2
- package/src/index.js +16 -0
- package/src/mcp/server.js +382 -0
package/README.md
CHANGED
|
@@ -117,6 +117,53 @@ you> /help
|
|
|
117
117
|
|
|
118
118
|
Requires `OPENAI_API_KEY` in `.env` for chat functionality. Memory commands (`/remember`, `/recall`) work with the local Qdrant engine.
|
|
119
119
|
|
|
120
|
+
## MCP Server
|
|
121
|
+
|
|
122
|
+
shabti includes an MCP (Model Context Protocol) server for integration with Claude Code, Cursor, and other MCP-compatible tools.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Start the MCP server (stdio transport)
|
|
126
|
+
npx shabti-mcp
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Claude Code configuration
|
|
130
|
+
|
|
131
|
+
Generate the MCP settings JSON:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
shabti mcp-config
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Or manually add to your MCP settings:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"mcpServers": {
|
|
142
|
+
"shabti-memory": {
|
|
143
|
+
"command": "npx",
|
|
144
|
+
"args": ["shabti-mcp"]
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Available tools
|
|
151
|
+
|
|
152
|
+
| Tool | Description |
|
|
153
|
+
| --------------- | -------------------------------------- |
|
|
154
|
+
| `memory_store` | Store a memory entry |
|
|
155
|
+
| `memory_search` | Search memories by semantic similarity |
|
|
156
|
+
| `memory_delete` | Delete a memory entry by ID |
|
|
157
|
+
| `memory_list` | List recent memory entries |
|
|
158
|
+
| `memory_status` | Get engine status |
|
|
159
|
+
|
|
160
|
+
### Available resources
|
|
161
|
+
|
|
162
|
+
| URI | Description |
|
|
163
|
+
| ----------------- | ---------------------------- |
|
|
164
|
+
| `shabti://status` | Engine status and statistics |
|
|
165
|
+
| `shabti://config` | Current configuration |
|
|
166
|
+
|
|
120
167
|
## Node.js API
|
|
121
168
|
|
|
122
169
|
```javascript
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shabti",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Agent Memory OS — semantic memory for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "native.cjs",
|
|
7
7
|
"types": "native.d.ts",
|
|
8
8
|
"bin": {
|
|
9
|
-
"shabti": "src/index.js"
|
|
9
|
+
"shabti": "src/index.js",
|
|
10
|
+
"shabti-mcp": "src/mcp/server.js"
|
|
10
11
|
},
|
|
11
12
|
"files": [
|
|
12
13
|
"src/",
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
"vector-database",
|
|
43
44
|
"qdrant",
|
|
44
45
|
"embeddings",
|
|
46
|
+
"mcp",
|
|
45
47
|
"cli"
|
|
46
48
|
],
|
|
47
49
|
"author": "Kaga Hinata",
|
package/src/index.js
CHANGED
|
@@ -78,6 +78,22 @@ function buildProgram() {
|
|
|
78
78
|
registerSpin(program);
|
|
79
79
|
registerStatus(program);
|
|
80
80
|
registerStore(program);
|
|
81
|
+
|
|
82
|
+
program
|
|
83
|
+
.command("mcp-config")
|
|
84
|
+
.description("Print MCP server configuration JSON for Claude Code / Cursor")
|
|
85
|
+
.action(() => {
|
|
86
|
+
const config = {
|
|
87
|
+
mcpServers: {
|
|
88
|
+
"shabti-memory": {
|
|
89
|
+
command: "npx",
|
|
90
|
+
args: ["shabti-mcp"],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
console.log(JSON.stringify(config, null, 2));
|
|
95
|
+
});
|
|
96
|
+
|
|
81
97
|
return program;
|
|
82
98
|
}
|
|
83
99
|
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createInterface } from "readline";
|
|
3
|
+
import { createEngine, loadConfig } from "../core/engine.js";
|
|
4
|
+
|
|
5
|
+
const SERVER_INFO = {
|
|
6
|
+
name: "shabti-memory",
|
|
7
|
+
version: "2.0.0",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const TOOLS = [
|
|
11
|
+
{
|
|
12
|
+
name: "memory_store",
|
|
13
|
+
description: "Store a memory entry in shabti",
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {
|
|
17
|
+
content: { type: "string", description: "The text content to store" },
|
|
18
|
+
namespace: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Target namespace (default: 'default')",
|
|
21
|
+
},
|
|
22
|
+
tags: {
|
|
23
|
+
type: "array",
|
|
24
|
+
items: { type: "string" },
|
|
25
|
+
description: "Tags to associate with the memory",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
required: ["content"],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "memory_search",
|
|
33
|
+
description: "Search stored memories by semantic similarity",
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: "object",
|
|
36
|
+
properties: {
|
|
37
|
+
query: { type: "string", description: "Search query text" },
|
|
38
|
+
limit: {
|
|
39
|
+
type: "integer",
|
|
40
|
+
description: "Maximum number of results (default: 10)",
|
|
41
|
+
},
|
|
42
|
+
namespace: { type: "string", description: "Namespace to search within" },
|
|
43
|
+
},
|
|
44
|
+
required: ["query"],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "memory_delete",
|
|
49
|
+
description: "Delete a memory entry by ID",
|
|
50
|
+
inputSchema: {
|
|
51
|
+
type: "object",
|
|
52
|
+
properties: {
|
|
53
|
+
id: { type: "string", description: "UUID of the memory entry to delete" },
|
|
54
|
+
},
|
|
55
|
+
required: ["id"],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "memory_list",
|
|
60
|
+
description: "List recent memory entries",
|
|
61
|
+
inputSchema: {
|
|
62
|
+
type: "object",
|
|
63
|
+
properties: {
|
|
64
|
+
limit: {
|
|
65
|
+
type: "integer",
|
|
66
|
+
description: "Maximum number of entries to return (default: 10)",
|
|
67
|
+
},
|
|
68
|
+
namespace: { type: "string", description: "Filter by namespace" },
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "memory_status",
|
|
74
|
+
description: "Get the current status of the memory engine",
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: "object",
|
|
77
|
+
properties: {},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
const RESOURCES = [
|
|
83
|
+
{
|
|
84
|
+
uri: "shabti://status",
|
|
85
|
+
name: "Engine status",
|
|
86
|
+
description: "Current shabti engine status and statistics",
|
|
87
|
+
mimeType: "application/json",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
uri: "shabti://config",
|
|
91
|
+
name: "Engine configuration",
|
|
92
|
+
description: "Current engine configuration",
|
|
93
|
+
mimeType: "application/json",
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
let engine = null;
|
|
98
|
+
let engineInitAttempted = false;
|
|
99
|
+
|
|
100
|
+
function initEngine() {
|
|
101
|
+
if (engine) return engine;
|
|
102
|
+
if (engineInitAttempted) return null;
|
|
103
|
+
engineInitAttempted = true;
|
|
104
|
+
try {
|
|
105
|
+
engine = createEngine();
|
|
106
|
+
} catch {
|
|
107
|
+
// engine stays null — tools will return errors
|
|
108
|
+
}
|
|
109
|
+
return engine;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function respond(id, result) {
|
|
113
|
+
const msg = JSON.stringify({ jsonrpc: "2.0", id, result });
|
|
114
|
+
process.stdout.write(msg + "\n");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function respondError(id, code, message) {
|
|
118
|
+
const msg = JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } });
|
|
119
|
+
process.stdout.write(msg + "\n");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function handleInitialize(id) {
|
|
123
|
+
respond(id, {
|
|
124
|
+
protocolVersion: "2024-11-05",
|
|
125
|
+
serverInfo: SERVER_INFO,
|
|
126
|
+
capabilities: {
|
|
127
|
+
tools: {},
|
|
128
|
+
resources: {},
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function handleToolsList(id) {
|
|
134
|
+
respond(id, { tools: TOOLS });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function handleResourcesList(id) {
|
|
138
|
+
respond(id, { resources: RESOURCES });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function handleToolsCall(id, params) {
|
|
142
|
+
const { name, arguments: args } = params;
|
|
143
|
+
|
|
144
|
+
const eng = initEngine();
|
|
145
|
+
|
|
146
|
+
if (name === "memory_status") {
|
|
147
|
+
if (!eng) {
|
|
148
|
+
return respond(id, {
|
|
149
|
+
content: [
|
|
150
|
+
{
|
|
151
|
+
type: "text",
|
|
152
|
+
text: JSON.stringify(
|
|
153
|
+
{ status: "unavailable", entry_count: 0, model_id: "unknown" },
|
|
154
|
+
null,
|
|
155
|
+
2,
|
|
156
|
+
),
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
const status = eng.status();
|
|
162
|
+
return respond(id, {
|
|
163
|
+
content: [
|
|
164
|
+
{
|
|
165
|
+
type: "text",
|
|
166
|
+
text: JSON.stringify(
|
|
167
|
+
{
|
|
168
|
+
status: "ok",
|
|
169
|
+
entry_count: status.entryCount,
|
|
170
|
+
tier: status.tier,
|
|
171
|
+
model_id: status.modelId,
|
|
172
|
+
qdrant_url: status.qdrantUrl,
|
|
173
|
+
},
|
|
174
|
+
null,
|
|
175
|
+
2,
|
|
176
|
+
),
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (name === "memory_store") {
|
|
183
|
+
if (!eng) {
|
|
184
|
+
return respondError(id, -32603, "Engine not available");
|
|
185
|
+
}
|
|
186
|
+
const content = args?.content;
|
|
187
|
+
if (!content) {
|
|
188
|
+
return respondError(id, -32602, "Missing required parameter: content");
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
const opts = {};
|
|
192
|
+
if (args.namespace) opts.namespace = args.namespace;
|
|
193
|
+
if (args.tags) opts.tags = args.tags;
|
|
194
|
+
const result = await eng.store(content, opts);
|
|
195
|
+
return respond(id, {
|
|
196
|
+
content: [
|
|
197
|
+
{
|
|
198
|
+
type: "text",
|
|
199
|
+
text: JSON.stringify(
|
|
200
|
+
{ status: result.status, id: result.id || result.existingId },
|
|
201
|
+
null,
|
|
202
|
+
2,
|
|
203
|
+
),
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
});
|
|
207
|
+
} catch (err) {
|
|
208
|
+
return respondError(id, -32603, err.message);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (name === "memory_search") {
|
|
213
|
+
if (!eng) {
|
|
214
|
+
return respondError(id, -32603, "Engine not available");
|
|
215
|
+
}
|
|
216
|
+
const query = args?.query;
|
|
217
|
+
if (!query) {
|
|
218
|
+
return respondError(id, -32602, "Missing required parameter: query");
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
const limit = args.limit || 10;
|
|
222
|
+
const queryObj = { text: query, limit };
|
|
223
|
+
if (args.namespace) queryObj.namespace = args.namespace;
|
|
224
|
+
const results = await eng.executeQuery(queryObj);
|
|
225
|
+
const formatted = results.map((r) => ({
|
|
226
|
+
content: r.content,
|
|
227
|
+
score: r.score,
|
|
228
|
+
id: r.id,
|
|
229
|
+
namespace: r.namespace,
|
|
230
|
+
}));
|
|
231
|
+
return respond(id, {
|
|
232
|
+
content: [
|
|
233
|
+
{
|
|
234
|
+
type: "text",
|
|
235
|
+
text: JSON.stringify({ query, results: formatted }, null, 2),
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
});
|
|
239
|
+
} catch (err) {
|
|
240
|
+
return respondError(id, -32603, err.message);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (name === "memory_delete") {
|
|
245
|
+
if (!eng) {
|
|
246
|
+
return respondError(id, -32603, "Engine not available");
|
|
247
|
+
}
|
|
248
|
+
const entryId = args?.id;
|
|
249
|
+
if (!entryId) {
|
|
250
|
+
return respondError(id, -32602, "Missing required parameter: id");
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
await eng.delete(entryId);
|
|
254
|
+
return respond(id, {
|
|
255
|
+
content: [
|
|
256
|
+
{
|
|
257
|
+
type: "text",
|
|
258
|
+
text: JSON.stringify({ deleted: true, id: entryId }, null, 2),
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
});
|
|
262
|
+
} catch (err) {
|
|
263
|
+
return respondError(id, -32603, err.message);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (name === "memory_list") {
|
|
268
|
+
if (!eng) {
|
|
269
|
+
return respondError(id, -32603, "Engine not available");
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
const limit = args?.limit || 10;
|
|
273
|
+
const queryObj = { text: "*", limit };
|
|
274
|
+
if (args?.namespace) queryObj.namespace = args.namespace;
|
|
275
|
+
const results = await eng.executeQuery(queryObj);
|
|
276
|
+
const entries = results.map((r) => ({
|
|
277
|
+
id: r.id,
|
|
278
|
+
content: r.content,
|
|
279
|
+
score: r.score,
|
|
280
|
+
namespace: r.namespace,
|
|
281
|
+
createdAt: r.createdAt,
|
|
282
|
+
}));
|
|
283
|
+
return respond(id, {
|
|
284
|
+
content: [
|
|
285
|
+
{
|
|
286
|
+
type: "text",
|
|
287
|
+
text: JSON.stringify({ entries, count: entries.length }, null, 2),
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
});
|
|
291
|
+
} catch (err) {
|
|
292
|
+
return respondError(id, -32603, err.message);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
respondError(id, -32601, `Unknown tool: ${name}`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function handleResourcesRead(id, params) {
|
|
300
|
+
const { uri } = params;
|
|
301
|
+
const eng = initEngine();
|
|
302
|
+
|
|
303
|
+
if (uri === "shabti://status") {
|
|
304
|
+
if (!eng) {
|
|
305
|
+
return respond(id, {
|
|
306
|
+
contents: [
|
|
307
|
+
{
|
|
308
|
+
uri,
|
|
309
|
+
mimeType: "application/json",
|
|
310
|
+
text: JSON.stringify({ status: "unavailable", entry_count: 0 }),
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
const status = eng.status();
|
|
316
|
+
return respond(id, {
|
|
317
|
+
contents: [
|
|
318
|
+
{
|
|
319
|
+
uri,
|
|
320
|
+
mimeType: "application/json",
|
|
321
|
+
text: JSON.stringify({
|
|
322
|
+
status: "ok",
|
|
323
|
+
entry_count: status.entryCount,
|
|
324
|
+
tier: status.tier,
|
|
325
|
+
model_id: status.modelId,
|
|
326
|
+
}),
|
|
327
|
+
},
|
|
328
|
+
],
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (uri === "shabti://config") {
|
|
333
|
+
const config = loadConfig();
|
|
334
|
+
return respond(id, {
|
|
335
|
+
contents: [
|
|
336
|
+
{
|
|
337
|
+
uri,
|
|
338
|
+
mimeType: "application/json",
|
|
339
|
+
text: JSON.stringify(config),
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
respondError(id, -32602, `Unknown resource: ${uri}`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async function handleRequest(line) {
|
|
349
|
+
let req;
|
|
350
|
+
try {
|
|
351
|
+
req = JSON.parse(line);
|
|
352
|
+
} catch {
|
|
353
|
+
return respondError(null, -32700, "Parse error");
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const { id, method, params } = req;
|
|
357
|
+
|
|
358
|
+
switch (method) {
|
|
359
|
+
case "initialize":
|
|
360
|
+
return handleInitialize(id);
|
|
361
|
+
case "notifications/initialized":
|
|
362
|
+
return; // no response needed for notifications
|
|
363
|
+
case "tools/list":
|
|
364
|
+
return handleToolsList(id);
|
|
365
|
+
case "tools/call":
|
|
366
|
+
return handleToolsCall(id, params || {});
|
|
367
|
+
case "resources/list":
|
|
368
|
+
return handleResourcesList(id);
|
|
369
|
+
case "resources/read":
|
|
370
|
+
return handleResourcesRead(id, params || {});
|
|
371
|
+
default:
|
|
372
|
+
return respondError(id, -32601, "Method not found");
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// stdio transport: read newline-delimited JSON from stdin
|
|
377
|
+
const rl = createInterface({ input: process.stdin, terminal: false });
|
|
378
|
+
rl.on("line", (line) => {
|
|
379
|
+
const trimmed = line.trim();
|
|
380
|
+
if (trimmed) handleRequest(trimmed);
|
|
381
|
+
});
|
|
382
|
+
rl.on("close", () => process.exit(0));
|