@plur-ai/mcp 0.2.3 → 0.2.4
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/dist/index.js +5 -398
- package/dist/server-ZKOBQAZR.js +397 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,403 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/server.ts
|
|
4
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
-
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
-
import { Plur, checkForUpdate } from "@plur-ai/core";
|
|
8
|
-
|
|
9
|
-
// src/tools.ts
|
|
10
|
-
function getToolDefinitions() {
|
|
11
|
-
return [
|
|
12
|
-
{
|
|
13
|
-
name: "plur.learn",
|
|
14
|
-
description: "Create an engram \u2014 record a reusable learning, preference, or correction",
|
|
15
|
-
inputSchema: {
|
|
16
|
-
type: "object",
|
|
17
|
-
properties: {
|
|
18
|
-
statement: { type: "string", description: "The knowledge assertion to store" },
|
|
19
|
-
type: {
|
|
20
|
-
type: "string",
|
|
21
|
-
enum: ["behavioral", "terminological", "procedural", "architectural"],
|
|
22
|
-
description: "Category of the engram"
|
|
23
|
-
},
|
|
24
|
-
scope: { type: "string", description: "Namespace, e.g. global, project:myapp" },
|
|
25
|
-
domain: { type: "string", description: "Domain tag, e.g. software.deployment" },
|
|
26
|
-
source: { type: "string", description: "Origin of this knowledge" }
|
|
27
|
-
},
|
|
28
|
-
required: ["statement"]
|
|
29
|
-
},
|
|
30
|
-
handler: async (args, plur) => {
|
|
31
|
-
const engram = plur.learn(args.statement, {
|
|
32
|
-
type: args.type,
|
|
33
|
-
scope: args.scope,
|
|
34
|
-
domain: args.domain,
|
|
35
|
-
source: args.source
|
|
36
|
-
});
|
|
37
|
-
return { id: engram.id, statement: engram.statement, scope: engram.scope, type: engram.type };
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
name: "plur.recall",
|
|
42
|
-
description: "Query engrams by semantic similarity \u2014 retrieve relevant learned knowledge",
|
|
43
|
-
inputSchema: {
|
|
44
|
-
type: "object",
|
|
45
|
-
properties: {
|
|
46
|
-
query: { type: "string", description: "Search query to find relevant engrams" },
|
|
47
|
-
scope: { type: "string", description: "Filter by scope (also includes global)" },
|
|
48
|
-
domain: { type: "string", description: "Filter by domain prefix" },
|
|
49
|
-
limit: { type: "number", description: "Max results to return (default 20)" },
|
|
50
|
-
min_strength: { type: "number", description: "Minimum retrieval strength (0-1)" }
|
|
51
|
-
},
|
|
52
|
-
required: ["query"]
|
|
53
|
-
},
|
|
54
|
-
handler: async (args, plur) => {
|
|
55
|
-
const results = plur.recall(args.query, {
|
|
56
|
-
scope: args.scope,
|
|
57
|
-
domain: args.domain,
|
|
58
|
-
limit: args.limit,
|
|
59
|
-
min_strength: args.min_strength
|
|
60
|
-
});
|
|
61
|
-
return {
|
|
62
|
-
results: results.map((e) => ({
|
|
63
|
-
id: e.id,
|
|
64
|
-
statement: e.statement,
|
|
65
|
-
type: e.type,
|
|
66
|
-
scope: e.scope,
|
|
67
|
-
domain: e.domain,
|
|
68
|
-
retrieval_strength: e.activation.retrieval_strength
|
|
69
|
-
})),
|
|
70
|
-
count: results.length
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
name: "plur.recall.hybrid",
|
|
76
|
-
description: "Hybrid search \u2014 BM25 + local embeddings merged via Reciprocal Rank Fusion. No API calls, fully local. Best default for most use cases.",
|
|
77
|
-
inputSchema: {
|
|
78
|
-
type: "object",
|
|
79
|
-
properties: {
|
|
80
|
-
query: { type: "string", description: "Search query to find relevant engrams" },
|
|
81
|
-
scope: { type: "string", description: "Filter by scope (also includes global)" },
|
|
82
|
-
domain: { type: "string", description: "Filter by domain prefix" },
|
|
83
|
-
limit: { type: "number", description: "Max results to return (default 20)" },
|
|
84
|
-
min_strength: { type: "number", description: "Minimum retrieval strength (0-1)" }
|
|
85
|
-
},
|
|
86
|
-
required: ["query"]
|
|
87
|
-
},
|
|
88
|
-
handler: async (args, plur) => {
|
|
89
|
-
const results = await plur.recallHybrid(args.query, {
|
|
90
|
-
scope: args.scope,
|
|
91
|
-
domain: args.domain,
|
|
92
|
-
limit: args.limit,
|
|
93
|
-
min_strength: args.min_strength
|
|
94
|
-
});
|
|
95
|
-
return {
|
|
96
|
-
results: results.map((e) => ({
|
|
97
|
-
id: e.id,
|
|
98
|
-
statement: e.statement,
|
|
99
|
-
type: e.type,
|
|
100
|
-
scope: e.scope,
|
|
101
|
-
domain: e.domain,
|
|
102
|
-
retrieval_strength: e.activation.retrieval_strength
|
|
103
|
-
})),
|
|
104
|
-
count: results.length,
|
|
105
|
-
mode: "hybrid"
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
name: "plur.inject",
|
|
111
|
-
description: "Get a scored context injection for a task \u2014 returns directives and considerations within token budget",
|
|
112
|
-
inputSchema: {
|
|
113
|
-
type: "object",
|
|
114
|
-
properties: {
|
|
115
|
-
task: { type: "string", description: "The task description to inject context for" },
|
|
116
|
-
budget: { type: "number", description: "Token budget for injection (default 2000)" },
|
|
117
|
-
scope: { type: "string", description: "Scope filter for engram selection" }
|
|
118
|
-
},
|
|
119
|
-
required: ["task"]
|
|
120
|
-
},
|
|
121
|
-
handler: async (args, plur) => {
|
|
122
|
-
const result = plur.inject(args.task, {
|
|
123
|
-
budget: args.budget,
|
|
124
|
-
scope: args.scope
|
|
125
|
-
});
|
|
126
|
-
return {
|
|
127
|
-
directives: result.directives,
|
|
128
|
-
consider: result.consider,
|
|
129
|
-
count: result.count,
|
|
130
|
-
tokens_used: result.tokens_used
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
name: "plur.feedback",
|
|
136
|
-
description: "Rate an engram's usefulness \u2014 trains injection relevance over time",
|
|
137
|
-
inputSchema: {
|
|
138
|
-
type: "object",
|
|
139
|
-
properties: {
|
|
140
|
-
id: { type: "string", description: "Engram ID (e.g. ENG-001)" },
|
|
141
|
-
signal: {
|
|
142
|
-
type: "string",
|
|
143
|
-
enum: ["positive", "negative", "neutral"],
|
|
144
|
-
description: "Feedback signal to apply"
|
|
145
|
-
}
|
|
146
|
-
},
|
|
147
|
-
required: ["id", "signal"]
|
|
148
|
-
},
|
|
149
|
-
handler: async (args, plur) => {
|
|
150
|
-
plur.feedback(args.id, args.signal);
|
|
151
|
-
return { success: true, id: args.id, signal: args.signal };
|
|
152
|
-
}
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
name: "plur.forget",
|
|
156
|
-
description: "Retire an engram \u2014 marks it as no longer active without deleting history",
|
|
157
|
-
inputSchema: {
|
|
158
|
-
type: "object",
|
|
159
|
-
properties: {
|
|
160
|
-
id: { type: "string", description: "Engram ID to retire" },
|
|
161
|
-
reason: { type: "string", description: "Optional reason for retiring this engram" }
|
|
162
|
-
},
|
|
163
|
-
required: ["id"]
|
|
164
|
-
},
|
|
165
|
-
handler: async (args, plur) => {
|
|
166
|
-
plur.forget(args.id, args.reason);
|
|
167
|
-
return { success: true, id: args.id, status: "retired" };
|
|
168
|
-
}
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
name: "plur.capture",
|
|
172
|
-
description: "Append an episode to the episodic timeline \u2014 records what happened in a session",
|
|
173
|
-
inputSchema: {
|
|
174
|
-
type: "object",
|
|
175
|
-
properties: {
|
|
176
|
-
summary: { type: "string", description: "What happened or was accomplished" },
|
|
177
|
-
agent: { type: "string", description: "Agent identifier capturing this episode" },
|
|
178
|
-
channel: { type: "string", description: "Communication channel (e.g. claude-code, chat)" },
|
|
179
|
-
session_id: { type: "string", description: "Session identifier for grouping episodes" },
|
|
180
|
-
tags: {
|
|
181
|
-
type: "array",
|
|
182
|
-
items: { type: "string" },
|
|
183
|
-
description: "Tags for categorizing the episode"
|
|
184
|
-
}
|
|
185
|
-
},
|
|
186
|
-
required: ["summary"]
|
|
187
|
-
},
|
|
188
|
-
handler: async (args, plur) => {
|
|
189
|
-
const episode = plur.capture(args.summary, {
|
|
190
|
-
agent: args.agent,
|
|
191
|
-
channel: args.channel,
|
|
192
|
-
session_id: args.session_id,
|
|
193
|
-
tags: args.tags
|
|
194
|
-
});
|
|
195
|
-
return { id: episode.id, summary: episode.summary, timestamp: episode.timestamp };
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
name: "plur.timeline",
|
|
200
|
-
description: "Query the episodic timeline \u2014 retrieve past episodes filtered by time, agent, or search",
|
|
201
|
-
inputSchema: {
|
|
202
|
-
type: "object",
|
|
203
|
-
properties: {
|
|
204
|
-
since: { type: "string", description: "ISO date string \u2014 only episodes after this time" },
|
|
205
|
-
until: { type: "string", description: "ISO date string \u2014 only episodes before this time" },
|
|
206
|
-
agent: { type: "string", description: "Filter by agent identifier" },
|
|
207
|
-
channel: { type: "string", description: "Filter by channel" },
|
|
208
|
-
search: { type: "string", description: "Full-text search within episode summaries" }
|
|
209
|
-
}
|
|
210
|
-
},
|
|
211
|
-
handler: async (args, plur) => {
|
|
212
|
-
const query = {};
|
|
213
|
-
if (args.since) query.since = new Date(args.since);
|
|
214
|
-
if (args.until) query.until = new Date(args.until);
|
|
215
|
-
if (args.agent) query.agent = args.agent;
|
|
216
|
-
if (args.channel) query.channel = args.channel;
|
|
217
|
-
if (args.search) query.search = args.search;
|
|
218
|
-
const episodes = plur.timeline(Object.keys(query).length > 0 ? query : void 0);
|
|
219
|
-
return {
|
|
220
|
-
episodes: episodes.map((e) => ({
|
|
221
|
-
id: e.id,
|
|
222
|
-
summary: e.summary,
|
|
223
|
-
timestamp: e.timestamp,
|
|
224
|
-
agent: e.agent,
|
|
225
|
-
channel: e.channel,
|
|
226
|
-
tags: e.tags
|
|
227
|
-
})),
|
|
228
|
-
count: episodes.length
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
name: "plur.ingest",
|
|
234
|
-
description: "Extract engram candidates from content using pattern matching \u2014 optionally auto-save them",
|
|
235
|
-
inputSchema: {
|
|
236
|
-
type: "object",
|
|
237
|
-
properties: {
|
|
238
|
-
content: { type: "string", description: "Text content to extract learnings from" },
|
|
239
|
-
source: { type: "string", description: "Source attribution for extracted engrams" },
|
|
240
|
-
extract_only: {
|
|
241
|
-
type: "boolean",
|
|
242
|
-
description: "If true, return candidates without saving (default false \u2014 saves automatically)"
|
|
243
|
-
},
|
|
244
|
-
scope: { type: "string", description: "Scope to assign to saved engrams" },
|
|
245
|
-
domain: { type: "string", description: "Domain to assign to saved engrams" }
|
|
246
|
-
},
|
|
247
|
-
required: ["content"]
|
|
248
|
-
},
|
|
249
|
-
handler: async (args, plur) => {
|
|
250
|
-
const candidates = plur.ingest(args.content, {
|
|
251
|
-
source: args.source,
|
|
252
|
-
extract_only: args.extract_only,
|
|
253
|
-
scope: args.scope,
|
|
254
|
-
domain: args.domain
|
|
255
|
-
});
|
|
256
|
-
return {
|
|
257
|
-
candidates: candidates.map((c) => ({
|
|
258
|
-
statement: c.statement,
|
|
259
|
-
type: c.type,
|
|
260
|
-
source: c.source
|
|
261
|
-
})),
|
|
262
|
-
count: candidates.length,
|
|
263
|
-
saved: !(args.extract_only ?? false)
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
},
|
|
267
|
-
{
|
|
268
|
-
name: "plur.packs.install",
|
|
269
|
-
description: "Install an engram pack from a directory path \u2014 adds curated engrams to the store",
|
|
270
|
-
inputSchema: {
|
|
271
|
-
type: "object",
|
|
272
|
-
properties: {
|
|
273
|
-
source: { type: "string", description: "Path to the pack directory to install" }
|
|
274
|
-
},
|
|
275
|
-
required: ["source"]
|
|
276
|
-
},
|
|
277
|
-
handler: async (args, plur) => {
|
|
278
|
-
const result = plur.installPack(args.source);
|
|
279
|
-
return { installed: result.installed, name: result.name, success: true };
|
|
280
|
-
}
|
|
281
|
-
},
|
|
282
|
-
{
|
|
283
|
-
name: "plur.packs.list",
|
|
284
|
-
description: "List all installed engram packs",
|
|
285
|
-
inputSchema: {
|
|
286
|
-
type: "object",
|
|
287
|
-
properties: {}
|
|
288
|
-
},
|
|
289
|
-
handler: async (_args, plur) => {
|
|
290
|
-
const packs = plur.listPacks();
|
|
291
|
-
return {
|
|
292
|
-
packs: packs.map((p) => ({
|
|
293
|
-
name: p.name,
|
|
294
|
-
version: p.version,
|
|
295
|
-
description: p.description,
|
|
296
|
-
engram_count: p.engram_count
|
|
297
|
-
})),
|
|
298
|
-
count: packs.length
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
},
|
|
302
|
-
{
|
|
303
|
-
name: "plur.sync",
|
|
304
|
-
description: "Sync engrams via git \u2014 initializes repo on first call, commits and pushes/pulls on subsequent calls. Provide a remote URL on first call to enable cross-device sync.",
|
|
305
|
-
inputSchema: {
|
|
306
|
-
type: "object",
|
|
307
|
-
properties: {
|
|
308
|
-
remote: {
|
|
309
|
-
type: "string",
|
|
310
|
-
description: "Git remote URL (e.g. git@github.com:user/plur-engrams.git). Only needed on first call to set up remote."
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
},
|
|
314
|
-
handler: async (args, plur) => {
|
|
315
|
-
const result = plur.sync(args.remote);
|
|
316
|
-
return result;
|
|
317
|
-
}
|
|
318
|
-
},
|
|
319
|
-
{
|
|
320
|
-
name: "plur.sync.status",
|
|
321
|
-
description: "Check git sync status \u2014 whether repo is initialized, has remote, is dirty, ahead/behind counts",
|
|
322
|
-
inputSchema: {
|
|
323
|
-
type: "object",
|
|
324
|
-
properties: {}
|
|
325
|
-
},
|
|
326
|
-
handler: async (_args, plur) => {
|
|
327
|
-
return plur.syncStatus();
|
|
328
|
-
}
|
|
329
|
-
},
|
|
330
|
-
{
|
|
331
|
-
name: "plur.status",
|
|
332
|
-
description: "Return system health \u2014 engram count, episode count, pack count, storage root",
|
|
333
|
-
inputSchema: {
|
|
334
|
-
type: "object",
|
|
335
|
-
properties: {}
|
|
336
|
-
},
|
|
337
|
-
handler: async (_args, plur) => {
|
|
338
|
-
const status = plur.status();
|
|
339
|
-
return {
|
|
340
|
-
engram_count: status.engram_count,
|
|
341
|
-
episode_count: status.episode_count,
|
|
342
|
-
pack_count: status.pack_count,
|
|
343
|
-
storage_root: status.storage_root
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
];
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// src/server.ts
|
|
351
|
-
var VERSION = "0.2.3";
|
|
352
|
-
async function createServer(plur) {
|
|
353
|
-
const instance = plur ?? new Plur();
|
|
354
|
-
const tools = getToolDefinitions();
|
|
355
|
-
checkForUpdate("@plur-ai/mcp", VERSION, (r) => {
|
|
356
|
-
if (r.updateAvailable) {
|
|
357
|
-
console.error(`[plur] Update available: ${r.current} \u2192 ${r.latest}. Run: npx @plur-ai/mcp@latest`);
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
const server = new Server(
|
|
361
|
-
{ name: "plur-mcp", version: VERSION },
|
|
362
|
-
{ capabilities: { tools: {} } }
|
|
363
|
-
);
|
|
364
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
365
|
-
tools: tools.map((t) => ({
|
|
366
|
-
name: t.name,
|
|
367
|
-
description: t.description,
|
|
368
|
-
inputSchema: t.inputSchema
|
|
369
|
-
}))
|
|
370
|
-
}));
|
|
371
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
372
|
-
const tool = tools.find((t) => t.name === request.params.name);
|
|
373
|
-
if (!tool) {
|
|
374
|
-
return {
|
|
375
|
-
content: [{ type: "text", text: `Unknown tool: ${request.params.name}` }],
|
|
376
|
-
isError: true
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
try {
|
|
380
|
-
const result = await tool.handler(request.params.arguments ?? {}, instance);
|
|
381
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
382
|
-
} catch (err) {
|
|
383
|
-
return {
|
|
384
|
-
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
385
|
-
isError: true
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
});
|
|
389
|
-
return server;
|
|
390
|
-
}
|
|
391
|
-
async function runStdio() {
|
|
392
|
-
const server = await createServer();
|
|
393
|
-
const transport = new StdioServerTransport();
|
|
394
|
-
await server.connect(transport);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
3
|
// src/index.ts
|
|
398
|
-
|
|
399
|
-
var
|
|
400
|
-
var HELP = `plur-mcp v${VERSION2} \u2014 persistent memory for AI agents
|
|
4
|
+
var VERSION = "0.2.4";
|
|
5
|
+
var HELP = `plur-mcp v${VERSION} \u2014 persistent memory for AI agents
|
|
401
6
|
|
|
402
7
|
Usage:
|
|
403
8
|
plur-mcp Start the MCP server (stdio transport)
|
|
@@ -425,11 +30,12 @@ if (arg === "--help" || arg === "-h") {
|
|
|
425
30
|
process.exit(0);
|
|
426
31
|
}
|
|
427
32
|
if (arg === "--version" || arg === "-v") {
|
|
428
|
-
process.stdout.write(`${
|
|
33
|
+
process.stdout.write(`${VERSION}
|
|
429
34
|
`);
|
|
430
35
|
process.exit(0);
|
|
431
36
|
}
|
|
432
37
|
if (arg === "init") {
|
|
38
|
+
const { detectPlurStorage } = await import("@plur-ai/core");
|
|
433
39
|
const paths = detectPlurStorage();
|
|
434
40
|
process.stdout.write(`PLUR initialized.
|
|
435
41
|
|
|
@@ -463,6 +69,7 @@ if (arg === "init") {
|
|
|
463
69
|
process.exit(0);
|
|
464
70
|
}
|
|
465
71
|
if (arg === "serve" || arg === void 0) {
|
|
72
|
+
const { runStdio } = await import("./server-ZKOBQAZR.js");
|
|
466
73
|
runStdio().catch((err) => {
|
|
467
74
|
console.error("Failed to start PLUR MCP server:", err);
|
|
468
75
|
process.exit(1);
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
// src/server.ts
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { Plur, checkForUpdate } from "@plur-ai/core";
|
|
6
|
+
|
|
7
|
+
// src/tools.ts
|
|
8
|
+
function getToolDefinitions() {
|
|
9
|
+
return [
|
|
10
|
+
{
|
|
11
|
+
name: "plur.learn",
|
|
12
|
+
description: "Create an engram \u2014 record a reusable learning, preference, or correction",
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
statement: { type: "string", description: "The knowledge assertion to store" },
|
|
17
|
+
type: {
|
|
18
|
+
type: "string",
|
|
19
|
+
enum: ["behavioral", "terminological", "procedural", "architectural"],
|
|
20
|
+
description: "Category of the engram"
|
|
21
|
+
},
|
|
22
|
+
scope: { type: "string", description: "Namespace, e.g. global, project:myapp" },
|
|
23
|
+
domain: { type: "string", description: "Domain tag, e.g. software.deployment" },
|
|
24
|
+
source: { type: "string", description: "Origin of this knowledge" }
|
|
25
|
+
},
|
|
26
|
+
required: ["statement"]
|
|
27
|
+
},
|
|
28
|
+
handler: async (args, plur) => {
|
|
29
|
+
const engram = plur.learn(args.statement, {
|
|
30
|
+
type: args.type,
|
|
31
|
+
scope: args.scope,
|
|
32
|
+
domain: args.domain,
|
|
33
|
+
source: args.source
|
|
34
|
+
});
|
|
35
|
+
return { id: engram.id, statement: engram.statement, scope: engram.scope, type: engram.type };
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "plur.recall",
|
|
40
|
+
description: "Query engrams by semantic similarity \u2014 retrieve relevant learned knowledge",
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
query: { type: "string", description: "Search query to find relevant engrams" },
|
|
45
|
+
scope: { type: "string", description: "Filter by scope (also includes global)" },
|
|
46
|
+
domain: { type: "string", description: "Filter by domain prefix" },
|
|
47
|
+
limit: { type: "number", description: "Max results to return (default 20)" },
|
|
48
|
+
min_strength: { type: "number", description: "Minimum retrieval strength (0-1)" }
|
|
49
|
+
},
|
|
50
|
+
required: ["query"]
|
|
51
|
+
},
|
|
52
|
+
handler: async (args, plur) => {
|
|
53
|
+
const results = plur.recall(args.query, {
|
|
54
|
+
scope: args.scope,
|
|
55
|
+
domain: args.domain,
|
|
56
|
+
limit: args.limit,
|
|
57
|
+
min_strength: args.min_strength
|
|
58
|
+
});
|
|
59
|
+
return {
|
|
60
|
+
results: results.map((e) => ({
|
|
61
|
+
id: e.id,
|
|
62
|
+
statement: e.statement,
|
|
63
|
+
type: e.type,
|
|
64
|
+
scope: e.scope,
|
|
65
|
+
domain: e.domain,
|
|
66
|
+
retrieval_strength: e.activation.retrieval_strength
|
|
67
|
+
})),
|
|
68
|
+
count: results.length
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "plur.recall.hybrid",
|
|
74
|
+
description: "Hybrid search \u2014 BM25 + local embeddings merged via Reciprocal Rank Fusion. No API calls, fully local. Best default for most use cases.",
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: "object",
|
|
77
|
+
properties: {
|
|
78
|
+
query: { type: "string", description: "Search query to find relevant engrams" },
|
|
79
|
+
scope: { type: "string", description: "Filter by scope (also includes global)" },
|
|
80
|
+
domain: { type: "string", description: "Filter by domain prefix" },
|
|
81
|
+
limit: { type: "number", description: "Max results to return (default 20)" },
|
|
82
|
+
min_strength: { type: "number", description: "Minimum retrieval strength (0-1)" }
|
|
83
|
+
},
|
|
84
|
+
required: ["query"]
|
|
85
|
+
},
|
|
86
|
+
handler: async (args, plur) => {
|
|
87
|
+
const results = await plur.recallHybrid(args.query, {
|
|
88
|
+
scope: args.scope,
|
|
89
|
+
domain: args.domain,
|
|
90
|
+
limit: args.limit,
|
|
91
|
+
min_strength: args.min_strength
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
results: results.map((e) => ({
|
|
95
|
+
id: e.id,
|
|
96
|
+
statement: e.statement,
|
|
97
|
+
type: e.type,
|
|
98
|
+
scope: e.scope,
|
|
99
|
+
domain: e.domain,
|
|
100
|
+
retrieval_strength: e.activation.retrieval_strength
|
|
101
|
+
})),
|
|
102
|
+
count: results.length,
|
|
103
|
+
mode: "hybrid"
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "plur.inject",
|
|
109
|
+
description: "Get a scored context injection for a task \u2014 returns directives and considerations within token budget",
|
|
110
|
+
inputSchema: {
|
|
111
|
+
type: "object",
|
|
112
|
+
properties: {
|
|
113
|
+
task: { type: "string", description: "The task description to inject context for" },
|
|
114
|
+
budget: { type: "number", description: "Token budget for injection (default 2000)" },
|
|
115
|
+
scope: { type: "string", description: "Scope filter for engram selection" }
|
|
116
|
+
},
|
|
117
|
+
required: ["task"]
|
|
118
|
+
},
|
|
119
|
+
handler: async (args, plur) => {
|
|
120
|
+
const result = plur.inject(args.task, {
|
|
121
|
+
budget: args.budget,
|
|
122
|
+
scope: args.scope
|
|
123
|
+
});
|
|
124
|
+
return {
|
|
125
|
+
directives: result.directives,
|
|
126
|
+
consider: result.consider,
|
|
127
|
+
count: result.count,
|
|
128
|
+
tokens_used: result.tokens_used
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "plur.feedback",
|
|
134
|
+
description: "Rate an engram's usefulness \u2014 trains injection relevance over time",
|
|
135
|
+
inputSchema: {
|
|
136
|
+
type: "object",
|
|
137
|
+
properties: {
|
|
138
|
+
id: { type: "string", description: "Engram ID (e.g. ENG-001)" },
|
|
139
|
+
signal: {
|
|
140
|
+
type: "string",
|
|
141
|
+
enum: ["positive", "negative", "neutral"],
|
|
142
|
+
description: "Feedback signal to apply"
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
required: ["id", "signal"]
|
|
146
|
+
},
|
|
147
|
+
handler: async (args, plur) => {
|
|
148
|
+
plur.feedback(args.id, args.signal);
|
|
149
|
+
return { success: true, id: args.id, signal: args.signal };
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: "plur.forget",
|
|
154
|
+
description: "Retire an engram \u2014 marks it as no longer active without deleting history",
|
|
155
|
+
inputSchema: {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: {
|
|
158
|
+
id: { type: "string", description: "Engram ID to retire" },
|
|
159
|
+
reason: { type: "string", description: "Optional reason for retiring this engram" }
|
|
160
|
+
},
|
|
161
|
+
required: ["id"]
|
|
162
|
+
},
|
|
163
|
+
handler: async (args, plur) => {
|
|
164
|
+
plur.forget(args.id, args.reason);
|
|
165
|
+
return { success: true, id: args.id, status: "retired" };
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "plur.capture",
|
|
170
|
+
description: "Append an episode to the episodic timeline \u2014 records what happened in a session",
|
|
171
|
+
inputSchema: {
|
|
172
|
+
type: "object",
|
|
173
|
+
properties: {
|
|
174
|
+
summary: { type: "string", description: "What happened or was accomplished" },
|
|
175
|
+
agent: { type: "string", description: "Agent identifier capturing this episode" },
|
|
176
|
+
channel: { type: "string", description: "Communication channel (e.g. claude-code, chat)" },
|
|
177
|
+
session_id: { type: "string", description: "Session identifier for grouping episodes" },
|
|
178
|
+
tags: {
|
|
179
|
+
type: "array",
|
|
180
|
+
items: { type: "string" },
|
|
181
|
+
description: "Tags for categorizing the episode"
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
required: ["summary"]
|
|
185
|
+
},
|
|
186
|
+
handler: async (args, plur) => {
|
|
187
|
+
const episode = plur.capture(args.summary, {
|
|
188
|
+
agent: args.agent,
|
|
189
|
+
channel: args.channel,
|
|
190
|
+
session_id: args.session_id,
|
|
191
|
+
tags: args.tags
|
|
192
|
+
});
|
|
193
|
+
return { id: episode.id, summary: episode.summary, timestamp: episode.timestamp };
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: "plur.timeline",
|
|
198
|
+
description: "Query the episodic timeline \u2014 retrieve past episodes filtered by time, agent, or search",
|
|
199
|
+
inputSchema: {
|
|
200
|
+
type: "object",
|
|
201
|
+
properties: {
|
|
202
|
+
since: { type: "string", description: "ISO date string \u2014 only episodes after this time" },
|
|
203
|
+
until: { type: "string", description: "ISO date string \u2014 only episodes before this time" },
|
|
204
|
+
agent: { type: "string", description: "Filter by agent identifier" },
|
|
205
|
+
channel: { type: "string", description: "Filter by channel" },
|
|
206
|
+
search: { type: "string", description: "Full-text search within episode summaries" }
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
handler: async (args, plur) => {
|
|
210
|
+
const query = {};
|
|
211
|
+
if (args.since) query.since = new Date(args.since);
|
|
212
|
+
if (args.until) query.until = new Date(args.until);
|
|
213
|
+
if (args.agent) query.agent = args.agent;
|
|
214
|
+
if (args.channel) query.channel = args.channel;
|
|
215
|
+
if (args.search) query.search = args.search;
|
|
216
|
+
const episodes = plur.timeline(Object.keys(query).length > 0 ? query : void 0);
|
|
217
|
+
return {
|
|
218
|
+
episodes: episodes.map((e) => ({
|
|
219
|
+
id: e.id,
|
|
220
|
+
summary: e.summary,
|
|
221
|
+
timestamp: e.timestamp,
|
|
222
|
+
agent: e.agent,
|
|
223
|
+
channel: e.channel,
|
|
224
|
+
tags: e.tags
|
|
225
|
+
})),
|
|
226
|
+
count: episodes.length
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: "plur.ingest",
|
|
232
|
+
description: "Extract engram candidates from content using pattern matching \u2014 optionally auto-save them",
|
|
233
|
+
inputSchema: {
|
|
234
|
+
type: "object",
|
|
235
|
+
properties: {
|
|
236
|
+
content: { type: "string", description: "Text content to extract learnings from" },
|
|
237
|
+
source: { type: "string", description: "Source attribution for extracted engrams" },
|
|
238
|
+
extract_only: {
|
|
239
|
+
type: "boolean",
|
|
240
|
+
description: "If true, return candidates without saving (default false \u2014 saves automatically)"
|
|
241
|
+
},
|
|
242
|
+
scope: { type: "string", description: "Scope to assign to saved engrams" },
|
|
243
|
+
domain: { type: "string", description: "Domain to assign to saved engrams" }
|
|
244
|
+
},
|
|
245
|
+
required: ["content"]
|
|
246
|
+
},
|
|
247
|
+
handler: async (args, plur) => {
|
|
248
|
+
const candidates = plur.ingest(args.content, {
|
|
249
|
+
source: args.source,
|
|
250
|
+
extract_only: args.extract_only,
|
|
251
|
+
scope: args.scope,
|
|
252
|
+
domain: args.domain
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
candidates: candidates.map((c) => ({
|
|
256
|
+
statement: c.statement,
|
|
257
|
+
type: c.type,
|
|
258
|
+
source: c.source
|
|
259
|
+
})),
|
|
260
|
+
count: candidates.length,
|
|
261
|
+
saved: !(args.extract_only ?? false)
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: "plur.packs.install",
|
|
267
|
+
description: "Install an engram pack from a directory path \u2014 adds curated engrams to the store",
|
|
268
|
+
inputSchema: {
|
|
269
|
+
type: "object",
|
|
270
|
+
properties: {
|
|
271
|
+
source: { type: "string", description: "Path to the pack directory to install" }
|
|
272
|
+
},
|
|
273
|
+
required: ["source"]
|
|
274
|
+
},
|
|
275
|
+
handler: async (args, plur) => {
|
|
276
|
+
const result = plur.installPack(args.source);
|
|
277
|
+
return { installed: result.installed, name: result.name, success: true };
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: "plur.packs.list",
|
|
282
|
+
description: "List all installed engram packs",
|
|
283
|
+
inputSchema: {
|
|
284
|
+
type: "object",
|
|
285
|
+
properties: {}
|
|
286
|
+
},
|
|
287
|
+
handler: async (_args, plur) => {
|
|
288
|
+
const packs = plur.listPacks();
|
|
289
|
+
return {
|
|
290
|
+
packs: packs.map((p) => ({
|
|
291
|
+
name: p.name,
|
|
292
|
+
version: p.version,
|
|
293
|
+
description: p.description,
|
|
294
|
+
engram_count: p.engram_count
|
|
295
|
+
})),
|
|
296
|
+
count: packs.length
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
name: "plur.sync",
|
|
302
|
+
description: "Sync engrams via git \u2014 initializes repo on first call, commits and pushes/pulls on subsequent calls. Provide a remote URL on first call to enable cross-device sync.",
|
|
303
|
+
inputSchema: {
|
|
304
|
+
type: "object",
|
|
305
|
+
properties: {
|
|
306
|
+
remote: {
|
|
307
|
+
type: "string",
|
|
308
|
+
description: "Git remote URL (e.g. git@github.com:user/plur-engrams.git). Only needed on first call to set up remote."
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
handler: async (args, plur) => {
|
|
313
|
+
const result = plur.sync(args.remote);
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
name: "plur.sync.status",
|
|
319
|
+
description: "Check git sync status \u2014 whether repo is initialized, has remote, is dirty, ahead/behind counts",
|
|
320
|
+
inputSchema: {
|
|
321
|
+
type: "object",
|
|
322
|
+
properties: {}
|
|
323
|
+
},
|
|
324
|
+
handler: async (_args, plur) => {
|
|
325
|
+
return plur.syncStatus();
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
name: "plur.status",
|
|
330
|
+
description: "Return system health \u2014 engram count, episode count, pack count, storage root",
|
|
331
|
+
inputSchema: {
|
|
332
|
+
type: "object",
|
|
333
|
+
properties: {}
|
|
334
|
+
},
|
|
335
|
+
handler: async (_args, plur) => {
|
|
336
|
+
const status = plur.status();
|
|
337
|
+
return {
|
|
338
|
+
engram_count: status.engram_count,
|
|
339
|
+
episode_count: status.episode_count,
|
|
340
|
+
pack_count: status.pack_count,
|
|
341
|
+
storage_root: status.storage_root
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// src/server.ts
|
|
349
|
+
var VERSION = "0.2.4";
|
|
350
|
+
async function createServer(plur) {
|
|
351
|
+
const instance = plur ?? new Plur();
|
|
352
|
+
const tools = getToolDefinitions();
|
|
353
|
+
checkForUpdate("@plur-ai/mcp", VERSION, (r) => {
|
|
354
|
+
if (r.updateAvailable) {
|
|
355
|
+
console.error(`[plur] Update available: ${r.current} \u2192 ${r.latest}. Run: npx @plur-ai/mcp@latest`);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
const server = new Server(
|
|
359
|
+
{ name: "plur-mcp", version: VERSION },
|
|
360
|
+
{ capabilities: { tools: {} } }
|
|
361
|
+
);
|
|
362
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
363
|
+
tools: tools.map((t) => ({
|
|
364
|
+
name: t.name,
|
|
365
|
+
description: t.description,
|
|
366
|
+
inputSchema: t.inputSchema
|
|
367
|
+
}))
|
|
368
|
+
}));
|
|
369
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
370
|
+
const tool = tools.find((t) => t.name === request.params.name);
|
|
371
|
+
if (!tool) {
|
|
372
|
+
return {
|
|
373
|
+
content: [{ type: "text", text: `Unknown tool: ${request.params.name}` }],
|
|
374
|
+
isError: true
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
const result = await tool.handler(request.params.arguments ?? {}, instance);
|
|
379
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
380
|
+
} catch (err) {
|
|
381
|
+
return {
|
|
382
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
383
|
+
isError: true
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
return server;
|
|
388
|
+
}
|
|
389
|
+
async function runStdio() {
|
|
390
|
+
const server = await createServer();
|
|
391
|
+
const transport = new StdioServerTransport();
|
|
392
|
+
await server.connect(transport);
|
|
393
|
+
}
|
|
394
|
+
export {
|
|
395
|
+
createServer,
|
|
396
|
+
runStdio
|
|
397
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plur-ai/mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"plur-mcp": "dist/index.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
15
15
|
"zod": "^3.23.0",
|
|
16
|
-
"@plur-ai/core": "0.2.
|
|
16
|
+
"@plur-ai/core": "0.2.4"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/node": "^25.5.0"
|