@plur-ai/mcp 0.13.0 → 0.14.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 +1 -0
- package/dist/index.js +79 -7
- package/dist/{server-B6OJN4ZX.js → server-LAXRBVKA.js} +224 -37
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -52,6 +52,7 @@ Your agent gets these tools automatically:
|
|
|
52
52
|
|------|-------------|
|
|
53
53
|
| `plur_session_start` | Start a session — injects relevant engrams for your task |
|
|
54
54
|
| `plur_learn` | Store a memory — correction, preference, convention, or decision |
|
|
55
|
+
| `plur_learn_batch` | Store many memories in one call — same dedup + policy as `plur_learn`, with per-item failure isolation |
|
|
55
56
|
| `plur_recall_hybrid` | **Best default** — BM25 + embeddings merged via RRF. Zero cost. |
|
|
56
57
|
| `plur_recall` | Keyword search (BM25 only, instant) |
|
|
57
58
|
| `plur_inject_hybrid` | Load relevant memories for the current task |
|
package/dist/index.js
CHANGED
|
@@ -5,14 +5,17 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSy
|
|
|
5
5
|
import { join } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import { homedir, platform } from "os";
|
|
8
|
-
var VERSION = "0.
|
|
8
|
+
var VERSION = "0.14.0";
|
|
9
9
|
var HELP = `plur-mcp v${VERSION} \u2014 persistent memory for AI agents
|
|
10
10
|
|
|
11
11
|
Usage:
|
|
12
|
-
plur-mcp
|
|
13
|
-
plur-mcp init
|
|
14
|
-
plur-mcp
|
|
15
|
-
plur-mcp
|
|
12
|
+
plur-mcp Start the MCP server (stdio transport)
|
|
13
|
+
plur-mcp init Set up PLUR: storage + MCP config + hooks + CLAUDE.md
|
|
14
|
+
plur-mcp packs install <path> Install a knowledge pack from a local directory
|
|
15
|
+
plur-mcp packs list List installed knowledge packs
|
|
16
|
+
plur-mcp packs uninstall <name> Uninstall a knowledge pack by name
|
|
17
|
+
plur-mcp --help Show this help message
|
|
18
|
+
plur-mcp --version Show version
|
|
16
19
|
|
|
17
20
|
Environment:
|
|
18
21
|
PLUR_PATH Storage location (default: ~/.plur/)
|
|
@@ -65,6 +68,12 @@ var PLUR_HOOKS = {
|
|
|
65
68
|
matcher: "auto|manual",
|
|
66
69
|
hooks: [{ type: "command", command: `${CLI} hook-inject --rehydrate`, timeout: 15 }]
|
|
67
70
|
}],
|
|
71
|
+
// Auto-close the memory lifecycle at session end (Claude Code SessionEnd,
|
|
72
|
+
// shipped v1.0.85) — captures a closing episode and cleans up the session
|
|
73
|
+
// checkpoint even if the agent forgot to call plur_session_end (#217).
|
|
74
|
+
SessionEnd: [{
|
|
75
|
+
hooks: [{ type: "command", command: `${CLI} hook-session-end`, timeout: 5 }]
|
|
76
|
+
}],
|
|
68
77
|
// --- Contextual injection ---
|
|
69
78
|
PreToolUse: [
|
|
70
79
|
{ matcher: "EnterPlanMode", hooks: [{ type: "command", command: `${CLI} hook-inject --event plan_mode`, timeout: 10 }] },
|
|
@@ -98,7 +107,7 @@ Hooks inject engrams automatically on every first message \u2014 you do not need
|
|
|
98
107
|
2. **Learn**: When corrected or discovering something new, call \`plur_learn\` immediately
|
|
99
108
|
3. **Recall**: Before answering factual questions, call \`plur_recall_hybrid\` \u2014 check memory first
|
|
100
109
|
4. **Feedback**: Rate injected engrams with \`plur_feedback\` (positive/negative) \u2014 trains relevance
|
|
101
|
-
5. **End**: Call \`plur_session_end\` with summary + engram_suggestions
|
|
110
|
+
5. **End**: Call \`plur_session_end\` with summary + engram_suggestions \u2014 a SessionEnd hook auto-closes the lifecycle if you forget, but calling it yourself captures higher-quality learnings
|
|
102
111
|
|
|
103
112
|
Do not ask permission to use these tools \u2014 they are your memory system.
|
|
104
113
|
|
|
@@ -264,6 +273,65 @@ async function runInit() {
|
|
|
264
273
|
`);
|
|
265
274
|
}
|
|
266
275
|
}
|
|
276
|
+
async function runPacks() {
|
|
277
|
+
const sub = process.argv[3];
|
|
278
|
+
const plurPath = process.env.PLUR_PATH ?? join(homedir(), ".plur");
|
|
279
|
+
const { Plur } = await import("@plur-ai/core");
|
|
280
|
+
const plur = new Plur({ path: plurPath });
|
|
281
|
+
if (sub === "install") {
|
|
282
|
+
const source = process.argv[4];
|
|
283
|
+
if (!source) {
|
|
284
|
+
process.stderr.write("Usage: plur-mcp packs install <path>\n");
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
const result = plur.installPack(source);
|
|
289
|
+
process.stdout.write(`Installed pack '${result.name}' (${result.installed} engrams)
|
|
290
|
+
`);
|
|
291
|
+
} catch (err) {
|
|
292
|
+
process.stderr.write(`Error: ${err.message}
|
|
293
|
+
`);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
} else if (sub === "list") {
|
|
297
|
+
const packs = plur.listPacks();
|
|
298
|
+
if (packs.length === 0) {
|
|
299
|
+
process.stdout.write("No packs installed.\n");
|
|
300
|
+
} else {
|
|
301
|
+
for (const pack of packs) {
|
|
302
|
+
const version = pack.manifest?.version ? ` v${pack.manifest.version}` : "";
|
|
303
|
+
process.stdout.write(`${pack.name}${version} (${pack.engram_count} engrams)
|
|
304
|
+
`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
} else if (sub === "uninstall") {
|
|
308
|
+
const name = process.argv[4];
|
|
309
|
+
if (!name) {
|
|
310
|
+
process.stderr.write("Usage: plur-mcp packs uninstall <name>\n");
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
try {
|
|
314
|
+
const result = plur.uninstallPack(name);
|
|
315
|
+
if (result.removed) {
|
|
316
|
+
process.stdout.write(`Uninstalled pack '${result.name}' (${result.engram_count} engrams removed)
|
|
317
|
+
`);
|
|
318
|
+
} else {
|
|
319
|
+
process.stderr.write(`Pack '${name}' not found.
|
|
320
|
+
`);
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
} catch (err) {
|
|
324
|
+
process.stderr.write(`Error: ${err.message}
|
|
325
|
+
`);
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
process.stderr.write(`Unknown packs subcommand: ${sub ?? "(none)"}
|
|
330
|
+
Available: install, list, uninstall
|
|
331
|
+
`);
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
267
335
|
var arg = process.argv[2];
|
|
268
336
|
if (arg === "--help" || arg === "-h") {
|
|
269
337
|
process.stdout.write(HELP);
|
|
@@ -278,8 +346,12 @@ if (arg === "init") {
|
|
|
278
346
|
await runInit();
|
|
279
347
|
process.exit(0);
|
|
280
348
|
}
|
|
349
|
+
if (arg === "packs") {
|
|
350
|
+
await runPacks();
|
|
351
|
+
process.exit(0);
|
|
352
|
+
}
|
|
281
353
|
if (arg === "serve" || arg === void 0) {
|
|
282
|
-
const { runStdio } = await import("./server-
|
|
354
|
+
const { runStdio } = await import("./server-LAXRBVKA.js");
|
|
283
355
|
runStdio().catch((err) => {
|
|
284
356
|
console.error("Failed to start PLUR MCP server:", err);
|
|
285
357
|
process.exit(1);
|
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
ErrorCode,
|
|
12
12
|
McpError
|
|
13
13
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
14
|
+
import { existsSync as existsSync2, readFileSync, writeFileSync } from "fs";
|
|
15
|
+
import { join as join2 } from "path";
|
|
16
|
+
import { homedir as homedir2 } from "os";
|
|
14
17
|
import { Plur as Plur2, checkForUpdate } from "@plur-ai/core";
|
|
15
18
|
|
|
16
19
|
// src/tools.ts
|
|
@@ -31,7 +34,7 @@ function recordTelemetry(event) {
|
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
// src/version.ts
|
|
34
|
-
var VERSION = "0.
|
|
37
|
+
var VERSION = "0.14.0";
|
|
35
38
|
|
|
36
39
|
// src/tools.ts
|
|
37
40
|
import { z } from "zod";
|
|
@@ -179,6 +182,24 @@ mcpCanary.expect({
|
|
|
179
182
|
description: "Learning from corrections",
|
|
180
183
|
fix: "Call plur_learn when corrected. If using hooks, verify they are installed."
|
|
181
184
|
});
|
|
185
|
+
var _sessionTelemetry = /* @__PURE__ */ new Map();
|
|
186
|
+
var SESSION_TTL_MS = 8 * 60 * 60 * 1e3;
|
|
187
|
+
function _cleanExpiredSessions() {
|
|
188
|
+
const cutoff = Date.now() - SESSION_TTL_MS;
|
|
189
|
+
for (const [id, state] of _sessionTelemetry) {
|
|
190
|
+
if (new Date(state.started_at).getTime() < cutoff) _sessionTelemetry.delete(id);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
var _activeSessionId;
|
|
194
|
+
function _recordInjectionTelemetry(session_id, injected_packs) {
|
|
195
|
+
if (!session_id || !injected_packs) return;
|
|
196
|
+
const state = _sessionTelemetry.get(session_id);
|
|
197
|
+
if (!state) return;
|
|
198
|
+
state.injection_calls++;
|
|
199
|
+
for (const [pack, count] of Object.entries(injected_packs)) {
|
|
200
|
+
state.pack_counts[pack] = (state.pack_counts[pack] ?? 0) + count;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
182
203
|
var CURSOR_CORE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
183
204
|
"plur_session_start",
|
|
184
205
|
"plur_session_end",
|
|
@@ -347,6 +368,87 @@ function getAllToolDefinitions() {
|
|
|
347
368
|
}
|
|
348
369
|
}
|
|
349
370
|
},
|
|
371
|
+
{
|
|
372
|
+
name: "plur_learn_batch",
|
|
373
|
+
description: "Create many engrams in one call \u2014 the batch form of plur_learn. Accepts an array of engram objects and writes them sequentially through the SAME dedup + policy pipeline as plur_learn (content-hash NOOP \u2192 semantic recall \u2192 LLM ADD/UPDATE/MERGE decision). Dedup also applies WITHIN the batch: a statement duplicating an earlier item in the same array resolves to NOOP against it. Returns `ids` aligned 1:1 with the input array (ids[i] is the engram id for input i, or null if input i failed), the per-item decisions (each carrying its input_index), aggregate stats, and any per-item failures (each with its input index) \u2014 a single bad item does not abort the batch. Use this when an orchestration fans out and wants to persist consolidated findings without N separate calls. LLM dedup calls are capped (default 50, override with max_llm_calls) to bound bulk-import cost. Note: unlike plur_learn, batch items take the LOCAL learn path \u2014 remote-scope auto-routing (learnRouted) is not applied per item, so for shared/remote-store writes prefer plur_learn or pass an explicit local scope. See plur-ai/plur#281.",
|
|
374
|
+
annotations: { title: "Learn (batch)", destructiveHint: false, idempotentHint: false },
|
|
375
|
+
inputSchema: {
|
|
376
|
+
type: "object",
|
|
377
|
+
properties: {
|
|
378
|
+
engrams: {
|
|
379
|
+
type: "array",
|
|
380
|
+
description: "Engram objects to persist. Each requires `statement`; the other fields mirror plur_learn.",
|
|
381
|
+
items: {
|
|
382
|
+
type: "object",
|
|
383
|
+
properties: {
|
|
384
|
+
statement: { type: "string", description: "The knowledge assertion to store" },
|
|
385
|
+
type: { type: "string", enum: ["behavioral", "terminological", "procedural", "architectural"], description: "Category of the engram" },
|
|
386
|
+
scope: { type: "string", description: "Namespace, e.g. global, project:myapp" },
|
|
387
|
+
domain: { type: "string", description: "Domain tag, e.g. software.deployment" },
|
|
388
|
+
tags: { type: "array", items: { type: "string" }, description: "Searchable keyword tags \u2014 contribute to BM25/embedding recall" },
|
|
389
|
+
rationale: { type: "string", description: "Why this knowledge matters \u2014 also enters the search corpus" },
|
|
390
|
+
source: { type: "string", description: "Origin of this knowledge (URL, conversation ref, etc.)" },
|
|
391
|
+
pinned: { type: "boolean", description: "Always-load flag. Use sparingly: meta-rules, safety conventions, core principles." },
|
|
392
|
+
commitment: { type: "string", enum: ["exploring", "leaning", "decided", "locked"], description: "How firmly the user has committed (default: leaning)" },
|
|
393
|
+
valid_from: { type: "string", description: "ISO date (YYYY-MM-DD) the knowledge becomes valid" },
|
|
394
|
+
valid_until: { type: "string", description: "ISO date (YYYY-MM-DD) the knowledge expires" }
|
|
395
|
+
},
|
|
396
|
+
required: ["statement"]
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
max_llm_calls: { type: "number", description: "Max LLM dedup calls across the whole batch (default 50). Once spent, remaining items use the cheap hash/cosine path. Pass a large number to opt out." }
|
|
400
|
+
},
|
|
401
|
+
required: ["engrams"]
|
|
402
|
+
},
|
|
403
|
+
handler: async (args, plur) => {
|
|
404
|
+
const llm = getLlmFunction();
|
|
405
|
+
const raw = Array.isArray(args.engrams) ? args.engrams : [];
|
|
406
|
+
if (raw.length === 0) {
|
|
407
|
+
return { ids: [], results: [], stats: { added: 0, updated: 0, merged: 0, noops: 0, failed: 0 }, failures: [], warning: "No engrams provided \u2014 pass a non-empty `engrams` array." };
|
|
408
|
+
}
|
|
409
|
+
const items = raw.map((e) => ({
|
|
410
|
+
statement: sanitizeStatement(e.statement),
|
|
411
|
+
context: {
|
|
412
|
+
type: e.type,
|
|
413
|
+
scope: e.scope,
|
|
414
|
+
domain: e.domain,
|
|
415
|
+
source: e.source,
|
|
416
|
+
tags: e.tags,
|
|
417
|
+
rationale: e.rationale,
|
|
418
|
+
commitment: e.commitment,
|
|
419
|
+
pinned: e.pinned,
|
|
420
|
+
valid_from: e.valid_from,
|
|
421
|
+
valid_until: e.valid_until
|
|
422
|
+
}
|
|
423
|
+
}));
|
|
424
|
+
const maxLlmCalls = typeof args.max_llm_calls === "number" ? args.max_llm_calls : void 0;
|
|
425
|
+
const { results, stats, failures } = await plur.learnBatch(
|
|
426
|
+
items,
|
|
427
|
+
llm,
|
|
428
|
+
maxLlmCalls !== void 0 ? { maxLlmCalls } : void 0
|
|
429
|
+
);
|
|
430
|
+
mcpCanary.signal("learn_activity");
|
|
431
|
+
recordTelemetry("learn");
|
|
432
|
+
const ids = raw.map(() => null);
|
|
433
|
+
for (const r of results) {
|
|
434
|
+
if (r.input_index !== void 0) ids[r.input_index] = r.engram.id;
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
ids,
|
|
438
|
+
results: results.map((r) => ({
|
|
439
|
+
input_index: r.input_index,
|
|
440
|
+
id: r.engram.id,
|
|
441
|
+
statement: r.engram.statement,
|
|
442
|
+
scope: r.engram.scope,
|
|
443
|
+
type: r.engram.type,
|
|
444
|
+
decision: r.decision,
|
|
445
|
+
...r.existing_id ? { existing_id: r.existing_id } : {}
|
|
446
|
+
})),
|
|
447
|
+
stats,
|
|
448
|
+
...failures.length > 0 ? { failures, warning: `${failures.length} of ${raw.length} engram(s) failed to persist; the rest were written.` } : {}
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
},
|
|
350
452
|
{
|
|
351
453
|
name: "plur_recall",
|
|
352
454
|
description: "Query engrams by BM25 keyword matching \u2014 use plur_recall_hybrid for semantic similarity. Note: a project-scope filter also returns personal-family engrams (local, global, user:*, agent:*); an explicit scope=global recall returns ALL personal-family engrams \u2014 wider than scope=global INJECT, which is targeted to the global namespace only.",
|
|
@@ -370,14 +472,18 @@ function getAllToolDefinitions() {
|
|
|
370
472
|
limit: args.limit
|
|
371
473
|
});
|
|
372
474
|
return {
|
|
373
|
-
results: results.map((e) =>
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
475
|
+
results: results.map((e) => {
|
|
476
|
+
const supersededBy = e.relations?.superseded_by;
|
|
477
|
+
const annotation = supersededBy?.length ? ` [superseded by ${supersededBy.join(", ")}]` : "";
|
|
478
|
+
return {
|
|
479
|
+
id: e.id,
|
|
480
|
+
statement: e.statement + annotation,
|
|
481
|
+
type: e.type,
|
|
482
|
+
scope: e.scope,
|
|
483
|
+
domain: e.domain,
|
|
484
|
+
retrieval_strength: e.activation.retrieval_strength
|
|
485
|
+
};
|
|
486
|
+
}),
|
|
381
487
|
count: results.length
|
|
382
488
|
};
|
|
383
489
|
}
|
|
@@ -433,9 +539,11 @@ function getAllToolDefinitions() {
|
|
|
433
539
|
const response = {
|
|
434
540
|
results: boundedResults.map((e) => {
|
|
435
541
|
const raw = e;
|
|
542
|
+
const supersededBy = e.relations?.superseded_by;
|
|
543
|
+
const annotation = supersededBy?.length ? ` [superseded by ${supersededBy.join(", ")}]` : "";
|
|
436
544
|
const base = {
|
|
437
545
|
id: e.id,
|
|
438
|
-
statement: e.statement,
|
|
546
|
+
statement: e.statement + annotation,
|
|
439
547
|
type: e.type,
|
|
440
548
|
scope: e.scope,
|
|
441
549
|
domain: e.domain,
|
|
@@ -483,6 +591,7 @@ function getAllToolDefinitions() {
|
|
|
483
591
|
budget: args.budget,
|
|
484
592
|
scope: args.scope
|
|
485
593
|
});
|
|
594
|
+
_recordInjectionTelemetry(_activeSessionId, result.injected_packs);
|
|
486
595
|
return {
|
|
487
596
|
directives: result.directives,
|
|
488
597
|
consider: result.consider,
|
|
@@ -512,6 +621,7 @@ function getAllToolDefinitions() {
|
|
|
512
621
|
budget: args.budget,
|
|
513
622
|
scope: args.scope
|
|
514
623
|
});
|
|
624
|
+
_recordInjectionTelemetry(_activeSessionId, result.injected_packs);
|
|
515
625
|
return {
|
|
516
626
|
directives: result.directives,
|
|
517
627
|
consider: result.consider,
|
|
@@ -1050,14 +1160,20 @@ function getAllToolDefinitions() {
|
|
|
1050
1160
|
},
|
|
1051
1161
|
{
|
|
1052
1162
|
name: "plur_status",
|
|
1053
|
-
description: "Return system health \u2014 running version, engram count, episode count, pack count, storage root",
|
|
1163
|
+
description: "Return system health \u2014 running version, engram count, episode count, pack count, storage root. Optionally filter engram counts by domain prefix and/or creation date.",
|
|
1054
1164
|
annotations: { title: "Status", readOnlyHint: true, idempotentHint: true },
|
|
1055
1165
|
inputSchema: {
|
|
1056
1166
|
type: "object",
|
|
1057
|
-
properties: {
|
|
1167
|
+
properties: {
|
|
1168
|
+
domain: { type: "string", description: 'Only count engrams whose domain starts with this prefix (e.g. "meridian")' },
|
|
1169
|
+
created_after: { type: "string", description: "ISO-8601 date (YYYY-MM-DD). Only count engrams learned on or after this date." }
|
|
1170
|
+
}
|
|
1058
1171
|
},
|
|
1059
|
-
handler: async (
|
|
1060
|
-
const status = plur.status(
|
|
1172
|
+
handler: async (args, plur) => {
|
|
1173
|
+
const status = plur.status({
|
|
1174
|
+
domain: args.domain,
|
|
1175
|
+
created_after: args.created_after
|
|
1176
|
+
});
|
|
1061
1177
|
const versionCheck = getCachedUpdateCheck("@plur-ai/mcp");
|
|
1062
1178
|
return {
|
|
1063
1179
|
version: VERSION,
|
|
@@ -1184,6 +1300,23 @@ function getAllToolDefinitions() {
|
|
|
1184
1300
|
}
|
|
1185
1301
|
}
|
|
1186
1302
|
checks.push({ check: "reranker available", ok: rerankerOk, detail: rerankerDetail });
|
|
1303
|
+
if (rerankerOk) {
|
|
1304
|
+
try {
|
|
1305
|
+
const fitResult = await plur.checkRerankerFit({ rerankerName });
|
|
1306
|
+
const sep = fitResult.separability.toFixed(3);
|
|
1307
|
+
checks.push({
|
|
1308
|
+
check: "reranker domain fit",
|
|
1309
|
+
ok: fitResult.fit,
|
|
1310
|
+
detail: fitResult.n_pairs === 0 ? "Not enough engrams to evaluate fit (< 2) \u2014 assuming fit" : fitResult.fit ? `Good fit \u2014 separability ${sep} on ${fitResult.n_pairs} pairs (threshold \u2265 0.05)` : `Poor fit \u2014 separability ${sep} on ${fitResult.n_pairs} pairs (threshold \u2265 0.05). Reranker may be net-negative on this store's domain mix.`
|
|
1311
|
+
});
|
|
1312
|
+
if (!fitResult.fit && fitResult.n_pairs > 0) {
|
|
1313
|
+
remediation.push(
|
|
1314
|
+
`Reranker "${rerankerName}" shows poor separability (${sep}) on this store's engrams \u2014 it may be scoring irrelevant pairs higher than relevant ones. Consider unsetting PLUR_RERANKER or switching to a different tier. The fit check compares same-domain vs cross-domain pair scores; low separability means the model lacks signal on your content.`
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
} catch {
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1187
1320
|
try {
|
|
1188
1321
|
let evalStatus;
|
|
1189
1322
|
let freshlyRun = false;
|
|
@@ -1286,6 +1419,13 @@ function getAllToolDefinitions() {
|
|
|
1286
1419
|
const session_id = crypto.randomUUID();
|
|
1287
1420
|
const task = args.task;
|
|
1288
1421
|
const tags = args.tags;
|
|
1422
|
+
_cleanExpiredSessions();
|
|
1423
|
+
_activeSessionId = session_id;
|
|
1424
|
+
_sessionTelemetry.set(session_id, {
|
|
1425
|
+
pack_counts: {},
|
|
1426
|
+
injection_calls: 0,
|
|
1427
|
+
started_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1428
|
+
});
|
|
1289
1429
|
let outbox_result;
|
|
1290
1430
|
try {
|
|
1291
1431
|
outbox_result = await plur.flushOutbox();
|
|
@@ -1319,6 +1459,7 @@ function getAllToolDefinitions() {
|
|
|
1319
1459
|
session_id
|
|
1320
1460
|
// stamped on the co_injection provenance event (#452)
|
|
1321
1461
|
});
|
|
1462
|
+
_recordInjectionTelemetry(session_id, result.injected_packs);
|
|
1322
1463
|
if (result.count > 0) {
|
|
1323
1464
|
const lines = [];
|
|
1324
1465
|
if (result.directives) lines.push("## DIRECTIVES\n", result.directives);
|
|
@@ -1331,6 +1472,7 @@ function getAllToolDefinitions() {
|
|
|
1331
1472
|
scope: tags?.length ? `tags:${tags.join(",")}` : void 0,
|
|
1332
1473
|
session_id
|
|
1333
1474
|
});
|
|
1475
|
+
_recordInjectionTelemetry(session_id, result.injected_packs);
|
|
1334
1476
|
if (result.count > 0) {
|
|
1335
1477
|
const lines = [];
|
|
1336
1478
|
if (result.directives) lines.push("## DIRECTIVES\n", result.directives);
|
|
@@ -1516,6 +1658,16 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1516
1658
|
session_id,
|
|
1517
1659
|
channel: "mcp"
|
|
1518
1660
|
});
|
|
1661
|
+
const telemetry = session_id ? _sessionTelemetry.get(session_id) : void 0;
|
|
1662
|
+
const injection_summary = telemetry && telemetry.injection_calls > 0 ? {
|
|
1663
|
+
pack_counts: { ...telemetry.pack_counts },
|
|
1664
|
+
total_injections: telemetry.injection_calls,
|
|
1665
|
+
session_duration_ms: Date.now() - new Date(telemetry.started_at).getTime()
|
|
1666
|
+
} : void 0;
|
|
1667
|
+
if (session_id) {
|
|
1668
|
+
_sessionTelemetry.delete(session_id);
|
|
1669
|
+
if (_activeSessionId === session_id) _activeSessionId = void 0;
|
|
1670
|
+
}
|
|
1519
1671
|
try {
|
|
1520
1672
|
const plurDir = process.env.PLUR_PATH ?? join(homedir(), ".plur");
|
|
1521
1673
|
const sessionsDir = join(plurDir, "sessions");
|
|
@@ -1534,6 +1686,7 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1534
1686
|
engrams_created,
|
|
1535
1687
|
episode_id: episode.id,
|
|
1536
1688
|
total_engrams: status.engram_count,
|
|
1689
|
+
...injection_summary ? { injection_summary } : {},
|
|
1537
1690
|
hint: engrams_created === 0 ? "No engrams captured this session. If any corrections, preferences, or patterns came up, consider calling plur_learn before ending." : void 0
|
|
1538
1691
|
};
|
|
1539
1692
|
}
|
|
@@ -1638,11 +1791,27 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1638
1791
|
if (discoveries.length === 0) {
|
|
1639
1792
|
return { discovered: [], note: "No remote stores configured. Register one scope first with plur_stores_add, then discover the rest." };
|
|
1640
1793
|
}
|
|
1794
|
+
const enrich = (d) => {
|
|
1795
|
+
if (!d.ok) return d;
|
|
1796
|
+
const byScope = new Map(d.metadata.map((m) => [m.scope, m]));
|
|
1797
|
+
const registeredSet = new Set(d.registered);
|
|
1798
|
+
const scopes = d.authorized.map((scope) => {
|
|
1799
|
+
const m = byScope.get(scope);
|
|
1800
|
+
return {
|
|
1801
|
+
scope,
|
|
1802
|
+
registered: registeredSet.has(scope),
|
|
1803
|
+
...m?.description ? { description: m.description } : {},
|
|
1804
|
+
...m && m.covers.length ? { covers: m.covers } : {}
|
|
1805
|
+
};
|
|
1806
|
+
});
|
|
1807
|
+
return { ...d, scopes };
|
|
1808
|
+
};
|
|
1809
|
+
const discovered = discoveries.map(enrich);
|
|
1641
1810
|
if (!register) {
|
|
1642
|
-
return { discovered
|
|
1811
|
+
return { discovered };
|
|
1643
1812
|
}
|
|
1644
1813
|
const registered = await plur.registerDiscoveredScopes({ url });
|
|
1645
|
-
return { discovered
|
|
1814
|
+
return { discovered, registered };
|
|
1646
1815
|
}
|
|
1647
1816
|
},
|
|
1648
1817
|
{
|
|
@@ -1953,9 +2122,9 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1953
2122
|
if (filterType) {
|
|
1954
2123
|
engrams = engrams.filter((e) => e.type === filterType);
|
|
1955
2124
|
}
|
|
1956
|
-
const { homedir:
|
|
1957
|
-
const { join:
|
|
1958
|
-
const outputDir = args.output_dir ||
|
|
2125
|
+
const { homedir: homedir3 } = await import("os");
|
|
2126
|
+
const { join: join3 } = await import("path");
|
|
2127
|
+
const outputDir = args.output_dir || join3(homedir3(), "plur-packs", name);
|
|
1959
2128
|
const result = plur.exportPack(engrams, outputDir, {
|
|
1960
2129
|
name,
|
|
1961
2130
|
version: "1.0.0",
|
|
@@ -2005,23 +2174,6 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
2005
2174
|
};
|
|
2006
2175
|
}
|
|
2007
2176
|
},
|
|
2008
|
-
{
|
|
2009
|
-
name: "plur_batch_decay",
|
|
2010
|
-
description: "Apply ACT-R decay to all local engrams. Run weekly. Only decays engrams in the local YAML store \u2014 remote-store engrams are not decayed client-side. Returns status transitions only.",
|
|
2011
|
-
annotations: { title: "Batch decay", destructiveHint: false, idempotentHint: false },
|
|
2012
|
-
inputSchema: {
|
|
2013
|
-
type: "object",
|
|
2014
|
-
properties: {
|
|
2015
|
-
context_scope: { type: "string", description: "Scope to skip during decay (engrams in active scope are not decayed)" }
|
|
2016
|
-
}
|
|
2017
|
-
},
|
|
2018
|
-
handler: async (args, plur) => {
|
|
2019
|
-
const result = plur.batchDecay({
|
|
2020
|
-
contextScope: args.context_scope
|
|
2021
|
-
});
|
|
2022
|
-
return result;
|
|
2023
|
-
}
|
|
2024
|
-
},
|
|
2025
2177
|
{
|
|
2026
2178
|
name: "plur_profile",
|
|
2027
2179
|
description: "Generate or retrieve a cognitive profile \u2014 a narrative summary synthesized from stored engrams. Cached for 24h.",
|
|
@@ -2059,6 +2211,28 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
2059
2211
|
}
|
|
2060
2212
|
|
|
2061
2213
|
// src/server.ts
|
|
2214
|
+
function serverPidPath(baseDir) {
|
|
2215
|
+
return join2(baseDir ?? join2(homedir2(), ".plur"), "server.pid");
|
|
2216
|
+
}
|
|
2217
|
+
function readEnterpriseToken(baseDir) {
|
|
2218
|
+
const configPath = join2(baseDir ?? join2(homedir2(), ".plur"), "config.json");
|
|
2219
|
+
if (!existsSync2(configPath)) return void 0;
|
|
2220
|
+
try {
|
|
2221
|
+
const cfg = JSON.parse(readFileSync(configPath, "utf8"));
|
|
2222
|
+
const ent = cfg?.enterprise;
|
|
2223
|
+
if (!ent || typeof ent.url !== "string" || typeof ent.token !== "string") return void 0;
|
|
2224
|
+
return { url: ent.url, token: ent.token, username: ent.username };
|
|
2225
|
+
} catch {
|
|
2226
|
+
return void 0;
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
var _pendingReload = false;
|
|
2230
|
+
function isPendingReload() {
|
|
2231
|
+
return _pendingReload;
|
|
2232
|
+
}
|
|
2233
|
+
function clearPendingReload() {
|
|
2234
|
+
_pendingReload = false;
|
|
2235
|
+
}
|
|
2062
2236
|
var INSTRUCTIONS = `PLUR is your persistent memory. Corrections, preferences, and conventions persist across sessions as engrams.
|
|
2063
2237
|
|
|
2064
2238
|
PLUR is a GLOBAL tool \u2014 one MCP server, one engram store (~/.plur/), available in every project. Multi-project scoping uses domain/scope fields on engrams, not separate installations.
|
|
@@ -2363,11 +2537,24 @@ async function runStdio() {
|
|
|
2363
2537
|
const profile = process.env.PLUR_TOOL_PROFILE === "cursor" ? "cursor" : "full";
|
|
2364
2538
|
const server = await createServer(void 0, { profile });
|
|
2365
2539
|
registerFlushOnExit({});
|
|
2540
|
+
try {
|
|
2541
|
+
writeFileSync(serverPidPath(), String(process.pid));
|
|
2542
|
+
} catch {
|
|
2543
|
+
}
|
|
2544
|
+
if (process.platform !== "win32") {
|
|
2545
|
+
process.on("SIGUSR1", () => {
|
|
2546
|
+
_pendingReload = true;
|
|
2547
|
+
});
|
|
2548
|
+
}
|
|
2366
2549
|
const transport = new StdioServerTransport();
|
|
2367
2550
|
await server.connect(transport);
|
|
2368
2551
|
}
|
|
2369
2552
|
export {
|
|
2370
2553
|
INSTRUCTIONS,
|
|
2554
|
+
clearPendingReload,
|
|
2371
2555
|
createServer,
|
|
2372
|
-
|
|
2556
|
+
isPendingReload,
|
|
2557
|
+
readEnterpriseToken,
|
|
2558
|
+
runStdio,
|
|
2559
|
+
serverPidPath
|
|
2373
2560
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plur-ai/mcp",
|
|
3
3
|
"mcpName": "io.github.plur-ai/plur",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.14.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"plur-mcp": "dist/index.js"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
16
16
|
"zod": "^3.23.0",
|
|
17
|
-
"@plur-ai/core": "0.
|
|
17
|
+
"@plur-ai/core": "0.14.0"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@types/node": "^25.5.0"
|