@plur-ai/mcp 0.9.9 → 0.9.10
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
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
5
5
|
import { join } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
|
-
import { homedir } from "os";
|
|
8
|
-
var VERSION = "0.9.
|
|
7
|
+
import { homedir, platform } from "os";
|
|
8
|
+
var VERSION = "0.9.10";
|
|
9
9
|
var HELP = `plur-mcp v${VERSION} \u2014 persistent memory for AI agents
|
|
10
10
|
|
|
11
11
|
Usage:
|
|
@@ -53,7 +53,9 @@ function extractManifestVersion(skillMdPath) {
|
|
|
53
53
|
return null;
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
-
var
|
|
56
|
+
var _shimName = platform() === "win32" ? "plur-hook.cmd" : "plur-hook";
|
|
57
|
+
var _shimCandidate = join(homedir(), ".plur", "bin", _shimName);
|
|
58
|
+
var CLI = existsSync(_shimCandidate) ? _shimCandidate : "npx @plur-ai/cli";
|
|
57
59
|
var PLUR_HOOKS = {
|
|
58
60
|
// --- Session lifecycle ---
|
|
59
61
|
UserPromptSubmit: [{
|
|
@@ -277,7 +279,7 @@ if (arg === "init") {
|
|
|
277
279
|
process.exit(0);
|
|
278
280
|
}
|
|
279
281
|
if (arg === "serve" || arg === void 0) {
|
|
280
|
-
const { runStdio } = await import("./server-
|
|
282
|
+
const { runStdio } = await import("./server-GRHXPO6E.js");
|
|
281
283
|
runStdio().catch((err) => {
|
|
282
284
|
console.error("Failed to start PLUR MCP server:", err);
|
|
283
285
|
process.exit(1);
|
|
@@ -14,10 +14,10 @@ import {
|
|
|
14
14
|
import { Plur as Plur2, checkForUpdate } from "@plur-ai/core";
|
|
15
15
|
|
|
16
16
|
// src/tools.ts
|
|
17
|
-
import { extractMetaEngrams, validateMetaEngram, confidenceBand, generateProfile, getProfileForInjection, selectModelForOperation } from "@plur-ai/core";
|
|
17
|
+
import { extractMetaEngrams, validateMetaEngram, confidenceBand, generateProfile, getProfileForInjection, selectModelForOperation, getCachedUpdateCheck, minorVersionsBehind, scanForTensions, CapabilityCanary } from "@plur-ai/core";
|
|
18
18
|
|
|
19
19
|
// src/version.ts
|
|
20
|
-
var VERSION = "0.9.
|
|
20
|
+
var VERSION = "0.9.10";
|
|
21
21
|
|
|
22
22
|
// src/tools.ts
|
|
23
23
|
function makeHttpLlm(baseUrl, apiKey, model = "gpt-4o-mini") {
|
|
@@ -86,6 +86,17 @@ function sanitizeStatement(raw) {
|
|
|
86
86
|
}
|
|
87
87
|
return raw.slice(0, cut).trimEnd();
|
|
88
88
|
}
|
|
89
|
+
var mcpCanary = new CapabilityCanary({ threshold: 10 });
|
|
90
|
+
mcpCanary.expect({
|
|
91
|
+
id: "session_start_hook",
|
|
92
|
+
description: "Automatic memory injection via hooks",
|
|
93
|
+
fix: "Run: npx @plur-ai/mcp init"
|
|
94
|
+
});
|
|
95
|
+
mcpCanary.expect({
|
|
96
|
+
id: "learn_activity",
|
|
97
|
+
description: "Learning from corrections",
|
|
98
|
+
fix: "Call plur_learn when corrected. If using hooks, verify they are installed."
|
|
99
|
+
});
|
|
89
100
|
function getToolDefinitions() {
|
|
90
101
|
return [
|
|
91
102
|
{
|
|
@@ -129,23 +140,29 @@ function getToolDefinitions() {
|
|
|
129
140
|
const statement = sanitizeStatement(args.statement);
|
|
130
141
|
try {
|
|
131
142
|
const engram = await plur.learnRouted(statement, context);
|
|
143
|
+
const isOutbox = !!engram.structured_data?._outbox;
|
|
144
|
+
mcpCanary.signal("learn_activity");
|
|
132
145
|
return {
|
|
133
146
|
id: engram.id,
|
|
134
147
|
statement: engram.statement,
|
|
135
148
|
scope: engram.scope,
|
|
136
149
|
type: engram.type,
|
|
137
150
|
pinned: engram.pinned === true,
|
|
138
|
-
decision: "ADD"
|
|
151
|
+
decision: "ADD",
|
|
152
|
+
...isOutbox ? { outbox: true, warning: "Remote write failed; engram queued locally for retry on next session start or plur_sync." } : {}
|
|
139
153
|
};
|
|
140
154
|
} catch (err) {
|
|
141
155
|
const engram = plur.learn(statement, context);
|
|
156
|
+
const isOutbox = !!engram.structured_data?._outbox;
|
|
157
|
+
mcpCanary.signal("learn_activity");
|
|
142
158
|
return {
|
|
143
159
|
id: engram.id,
|
|
144
160
|
statement: engram.statement,
|
|
145
161
|
scope: engram.scope,
|
|
146
162
|
type: engram.type,
|
|
147
163
|
decision: "ADD",
|
|
148
|
-
|
|
164
|
+
...isOutbox ? { outbox: true } : {},
|
|
165
|
+
warning: `Remote write failed (${err.message}); engram queued for retry.`
|
|
149
166
|
};
|
|
150
167
|
}
|
|
151
168
|
}
|
|
@@ -412,10 +429,13 @@ function getToolDefinitions() {
|
|
|
412
429
|
handler: async (args, plur) => {
|
|
413
430
|
if (args.id) {
|
|
414
431
|
const engram = plur.getById(args.id);
|
|
415
|
-
if (
|
|
416
|
-
|
|
432
|
+
if (engram) {
|
|
433
|
+
if (engram.status === "retired") return { success: false, error: `Already retired: ${args.id}` };
|
|
434
|
+
await plur.forget(args.id);
|
|
435
|
+
return { success: true, retired: { id: engram.id, statement: engram.statement } };
|
|
436
|
+
}
|
|
417
437
|
await plur.forget(args.id);
|
|
418
|
-
return { success: true, retired: { id:
|
|
438
|
+
return { success: true, retired: { id: args.id } };
|
|
419
439
|
}
|
|
420
440
|
if (args.search) {
|
|
421
441
|
const matches = plur.recall(args.search, { limit: 100 });
|
|
@@ -648,7 +668,21 @@ function getToolDefinitions() {
|
|
|
648
668
|
},
|
|
649
669
|
handler: async (args, plur) => {
|
|
650
670
|
const result = plur.sync(args.remote);
|
|
651
|
-
|
|
671
|
+
let outbox_result;
|
|
672
|
+
try {
|
|
673
|
+
outbox_result = await plur.flushOutbox();
|
|
674
|
+
} catch {
|
|
675
|
+
}
|
|
676
|
+
return {
|
|
677
|
+
...result,
|
|
678
|
+
...outbox_result && (outbox_result.flushed > 0 || outbox_result.failed > 0) ? {
|
|
679
|
+
outbox: {
|
|
680
|
+
flushed: outbox_result.flushed,
|
|
681
|
+
pending: outbox_result.failed,
|
|
682
|
+
warnings: outbox_result.expired_warnings
|
|
683
|
+
}
|
|
684
|
+
} : {}
|
|
685
|
+
};
|
|
652
686
|
}
|
|
653
687
|
},
|
|
654
688
|
{
|
|
@@ -821,6 +855,7 @@ function getToolDefinitions() {
|
|
|
821
855
|
},
|
|
822
856
|
handler: async (_args, plur) => {
|
|
823
857
|
const status = plur.status();
|
|
858
|
+
const versionCheck = getCachedUpdateCheck("@plur-ai/mcp");
|
|
824
859
|
return {
|
|
825
860
|
version: VERSION,
|
|
826
861
|
engram_count: status.engram_count,
|
|
@@ -829,7 +864,17 @@ function getToolDefinitions() {
|
|
|
829
864
|
storage_root: status.storage_root,
|
|
830
865
|
locked_count: status.locked_count,
|
|
831
866
|
tension_count: status.tension_count,
|
|
832
|
-
versioned_engram_count: status.versioned_engram_count ?? 0
|
|
867
|
+
versioned_engram_count: status.versioned_engram_count ?? 0,
|
|
868
|
+
outbox_count: status.outbox_count ?? 0,
|
|
869
|
+
// Version check (issue #151)
|
|
870
|
+
...versionCheck?.updateAvailable && versionCheck.latest ? {
|
|
871
|
+
update_available: {
|
|
872
|
+
current: versionCheck.current,
|
|
873
|
+
latest: versionCheck.latest,
|
|
874
|
+
behind: minorVersionsBehind(versionCheck.current, versionCheck.latest)
|
|
875
|
+
}
|
|
876
|
+
} : {},
|
|
877
|
+
capabilities: mcpCanary.status()
|
|
833
878
|
};
|
|
834
879
|
}
|
|
835
880
|
},
|
|
@@ -898,6 +943,13 @@ function getToolDefinitions() {
|
|
|
898
943
|
" \u2022 Or opt out: set PLUR_DISABLE_EMBEDDINGS=1, or write `embeddings: { enabled: false }` to ~/.plur/config.yaml \u2014 hybrid search will run BM25-only"
|
|
899
944
|
);
|
|
900
945
|
}
|
|
946
|
+
const canaryStatuses = mcpCanary.status();
|
|
947
|
+
for (const cs of canaryStatuses) {
|
|
948
|
+
if (!cs.healthy) {
|
|
949
|
+
checks.push({ check: `capability: ${cs.capability}`, ok: false, detail: cs.warning });
|
|
950
|
+
if (cs.warning) remediation.push(cs.warning);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
901
953
|
return {
|
|
902
954
|
ok: checks.every((c) => c.ok),
|
|
903
955
|
checks,
|
|
@@ -905,6 +957,7 @@ function getToolDefinitions() {
|
|
|
905
957
|
before_probe: before,
|
|
906
958
|
after_probe: after
|
|
907
959
|
},
|
|
960
|
+
capabilities: canaryStatuses,
|
|
908
961
|
remediation: remediation.length > 0 ? remediation : ["All checks passed \u2014 PLUR is healthy."]
|
|
909
962
|
};
|
|
910
963
|
}
|
|
@@ -922,10 +975,17 @@ function getToolDefinitions() {
|
|
|
922
975
|
required: ["task"]
|
|
923
976
|
},
|
|
924
977
|
handler: async (args, plur) => {
|
|
978
|
+
mcpCanary.tick();
|
|
979
|
+
mcpCanary.signal("session_start_hook");
|
|
925
980
|
const crypto = await import("crypto");
|
|
926
981
|
const session_id = crypto.randomUUID();
|
|
927
982
|
const task = args.task;
|
|
928
983
|
const tags = args.tags;
|
|
984
|
+
let outbox_result;
|
|
985
|
+
try {
|
|
986
|
+
outbox_result = await plur.flushOutbox();
|
|
987
|
+
} catch {
|
|
988
|
+
}
|
|
929
989
|
const status = plur.status();
|
|
930
990
|
const store_stats = {
|
|
931
991
|
engram_count: status.engram_count,
|
|
@@ -967,6 +1027,19 @@ function getToolDefinitions() {
|
|
|
967
1027
|
You have ${store_stats.engram_count} engrams but none matched this task. Call plur_learn to capture new learnings from this session.`;
|
|
968
1028
|
}
|
|
969
1029
|
const isFreshInstall = store_stats.engram_count === 0 && store_stats.episode_count === 0;
|
|
1030
|
+
const versionCheck = getCachedUpdateCheck("@plur-ai/mcp");
|
|
1031
|
+
let version_warning;
|
|
1032
|
+
if (versionCheck?.updateAvailable && versionCheck.latest) {
|
|
1033
|
+
const behind = minorVersionsBehind(versionCheck.current, versionCheck.latest);
|
|
1034
|
+
if (behind > 2) {
|
|
1035
|
+
version_warning = `CRITICAL: Running PLUR v${versionCheck.current} \u2014 latest is v${versionCheck.latest} (${behind} minor versions behind). Known bugs may be present. Update immediately: npx @plur-ai/mcp@latest`;
|
|
1036
|
+
guide = `\u26A0\uFE0F ${version_warning}
|
|
1037
|
+
|
|
1038
|
+
${guide}`;
|
|
1039
|
+
} else {
|
|
1040
|
+
version_warning = `Update available: PLUR v${versionCheck.current} \u2192 v${versionCheck.latest}. Run: npx @plur-ai/mcp@latest`;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
970
1043
|
return {
|
|
971
1044
|
session_id,
|
|
972
1045
|
engrams: engrams ?? [],
|
|
@@ -975,7 +1048,17 @@ You have ${store_stats.engram_count} engrams but none matched this task. Call pl
|
|
|
975
1048
|
// Ask LLM to check back — MCP can't push, but we can request a follow-up
|
|
976
1049
|
follow_up: store_stats.engram_count === 0 ? "This is a fresh store with 0 engrams. After your first exchange with the user, review what you learned and call plur_learn for any corrections, preferences, or patterns. Build the memory from this session." : void 0,
|
|
977
1050
|
// On fresh install, suggest hook setup for reliable injection
|
|
978
|
-
setup_hint: isFreshInstall ? "IMPORTANT: For reliable memory injection, ask the user to run: npx @plur-ai/cli init \u2014 this installs Claude Code hooks that automatically inject engrams at conversation start and after context compaction. Without hooks, memory injection depends on you remembering to call plur_session_start." : void 0
|
|
1051
|
+
setup_hint: isFreshInstall ? "IMPORTANT: For reliable memory injection, ask the user to run: npx @plur-ai/cli init \u2014 this installs Claude Code hooks that automatically inject engrams at conversation start and after context compaction. Without hooks, memory injection depends on you remembering to call plur_session_start." : void 0,
|
|
1052
|
+
// Outbox flush results (issue #26)
|
|
1053
|
+
...outbox_result && (outbox_result.flushed > 0 || outbox_result.failed > 0) ? {
|
|
1054
|
+
outbox: {
|
|
1055
|
+
flushed: outbox_result.flushed,
|
|
1056
|
+
pending: outbox_result.failed,
|
|
1057
|
+
warnings: outbox_result.expired_warnings
|
|
1058
|
+
}
|
|
1059
|
+
} : {},
|
|
1060
|
+
// Version staleness warning (issue #151)
|
|
1061
|
+
...version_warning ? { version_warning, version: VERSION } : {}
|
|
979
1062
|
};
|
|
980
1063
|
}
|
|
981
1064
|
},
|
|
@@ -1078,7 +1161,12 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1078
1161
|
inputSchema: { type: "object", properties: {} },
|
|
1079
1162
|
handler: async (_args, plur) => {
|
|
1080
1163
|
const stores = plur.listStores();
|
|
1081
|
-
|
|
1164
|
+
const outboxCount = plur.outboxCount();
|
|
1165
|
+
return {
|
|
1166
|
+
stores,
|
|
1167
|
+
count: stores.length,
|
|
1168
|
+
...outboxCount > 0 ? { outbox_pending: outboxCount } : {}
|
|
1169
|
+
};
|
|
1082
1170
|
}
|
|
1083
1171
|
},
|
|
1084
1172
|
{
|
|
@@ -1123,13 +1211,19 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1123
1211
|
},
|
|
1124
1212
|
{
|
|
1125
1213
|
name: "plur_tensions",
|
|
1126
|
-
description: "List engram pairs that have conflicting knowledge
|
|
1214
|
+
description: "List or scan for engram pairs that have conflicting knowledge. Without scan mode, shows previously detected conflicts. With scan:true, runs an active LLM-powered contradiction scan and returns only high-confidence tensions.",
|
|
1127
1215
|
annotations: { title: "Tensions", readOnlyHint: true, idempotentHint: true },
|
|
1128
1216
|
inputSchema: {
|
|
1129
1217
|
type: "object",
|
|
1130
1218
|
properties: {
|
|
1131
1219
|
scope: { type: "string", description: "Filter by scope" },
|
|
1132
|
-
domain: { type: "string", description: "Filter by domain prefix" }
|
|
1220
|
+
domain: { type: "string", description: "Filter by domain prefix" },
|
|
1221
|
+
scan: { type: "boolean", description: "Run an active contradiction scan using an LLM judge. Requires OPENAI_API_KEY or OPENROUTER_API_KEY env var, or explicit llm_base_url + llm_api_key args." },
|
|
1222
|
+
llm_base_url: { type: "string", description: "OpenAI-compatible API base URL for scan mode (e.g. https://api.openai.com/v1)" },
|
|
1223
|
+
llm_api_key: { type: "string", description: "API key for the LLM (scan mode)" },
|
|
1224
|
+
llm_model: { type: "string", description: "Model name for scan mode (default: gpt-4o-mini)" },
|
|
1225
|
+
min_confidence: { type: "number", description: "Minimum confidence threshold for scan mode (0\u20131, default: 0.7)" },
|
|
1226
|
+
max_pairs: { type: "number", description: "Maximum candidate pairs to evaluate in scan mode (default: 50)" }
|
|
1133
1227
|
}
|
|
1134
1228
|
},
|
|
1135
1229
|
handler: async (args, plur) => {
|
|
@@ -1137,6 +1231,30 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1137
1231
|
scope: args.scope,
|
|
1138
1232
|
domain: args.domain
|
|
1139
1233
|
});
|
|
1234
|
+
if (args.scan) {
|
|
1235
|
+
const llm = args.llm_base_url && args.llm_api_key ? makeHttpLlm(args.llm_base_url, args.llm_api_key, args.llm_model) : getLlmFunction();
|
|
1236
|
+
if (!llm) {
|
|
1237
|
+
return {
|
|
1238
|
+
error: "scan mode requires an LLM. Set OPENAI_API_KEY or OPENROUTER_API_KEY, or pass llm_base_url + llm_api_key.",
|
|
1239
|
+
tensions: [],
|
|
1240
|
+
count: 0
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
const result = await scanForTensions(engrams, llm, {
|
|
1244
|
+
min_confidence: args.min_confidence,
|
|
1245
|
+
max_pairs: args.max_pairs
|
|
1246
|
+
});
|
|
1247
|
+
return {
|
|
1248
|
+
pairs_checked: result.pairs_checked,
|
|
1249
|
+
count: result.new_tensions,
|
|
1250
|
+
tensions: result.tensions.map((t) => ({
|
|
1251
|
+
engram_a: { id: t.id_a, statement: t.statement_a },
|
|
1252
|
+
engram_b: { id: t.id_b, statement: t.statement_b },
|
|
1253
|
+
confidence: t.confidence,
|
|
1254
|
+
reason: t.reason
|
|
1255
|
+
}))
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1140
1258
|
const tensions = [];
|
|
1141
1259
|
const seen = /* @__PURE__ */ new Set();
|
|
1142
1260
|
for (const engram of engrams) {
|
|
@@ -1150,11 +1268,13 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1150
1268
|
tensions.push({
|
|
1151
1269
|
engram_a: { id: engram.id, statement: engram.statement, type: engram.type },
|
|
1152
1270
|
engram_b: { id: other.id, statement: other.statement, type: other.type },
|
|
1153
|
-
detected_at: engram.activation.last_accessed
|
|
1271
|
+
detected_at: engram.activation.last_accessed,
|
|
1272
|
+
purge_hint: "These conflicts are from the legacy detection system. Run plur_tensions_purge to clear them, then use scan:true for active contradiction detection."
|
|
1154
1273
|
});
|
|
1155
1274
|
}
|
|
1156
1275
|
}
|
|
1157
|
-
|
|
1276
|
+
const purge_hint = tensions.length > 0 ? "These are legacy conflict relations. Run plur_tensions_purge to clear them." : void 0;
|
|
1277
|
+
return { tensions, count: tensions.length, ...purge_hint ? { purge_hint } : {} };
|
|
1158
1278
|
}
|
|
1159
1279
|
},
|
|
1160
1280
|
{
|
|
@@ -1364,7 +1484,7 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1364
1484
|
},
|
|
1365
1485
|
{
|
|
1366
1486
|
name: "plur_batch_decay",
|
|
1367
|
-
description: "Apply ACT-R decay to all engrams. Run weekly. Returns status transitions only.",
|
|
1487
|
+
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.",
|
|
1368
1488
|
annotations: { title: "Batch decay", destructiveHint: false, idempotentHint: false },
|
|
1369
1489
|
inputSchema: {
|
|
1370
1490
|
type: "object",
|
|
@@ -1553,7 +1673,7 @@ async function createServer(plur) {
|
|
|
1553
1673
|
const tool = tools.find((t) => t.name === request.params.name);
|
|
1554
1674
|
if (!tool) {
|
|
1555
1675
|
return {
|
|
1556
|
-
content: [{ type: "text", text: `Unknown tool: ${request.params.name}
|
|
1676
|
+
content: [{ type: "text", text: JSON.stringify({ error: `Unknown tool: ${request.params.name}`, success: false }) }],
|
|
1557
1677
|
isError: true
|
|
1558
1678
|
};
|
|
1559
1679
|
}
|
|
@@ -1573,8 +1693,9 @@ async function createServer(plur) {
|
|
|
1573
1693
|
}
|
|
1574
1694
|
const parsed = z.object(shape).passthrough().safeParse(args);
|
|
1575
1695
|
if (!parsed.success) {
|
|
1696
|
+
const details = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
|
|
1576
1697
|
return {
|
|
1577
|
-
content: [{ type: "text", text: `Invalid arguments: ${
|
|
1698
|
+
content: [{ type: "text", text: JSON.stringify({ error: `Invalid arguments: ${details}`, success: false }) }],
|
|
1578
1699
|
isError: true
|
|
1579
1700
|
};
|
|
1580
1701
|
}
|
|
@@ -1585,7 +1706,7 @@ async function createServer(plur) {
|
|
|
1585
1706
|
const message = err?.message ?? String(err);
|
|
1586
1707
|
server.sendLoggingMessage({ level: "error", data: `Tool ${request.params.name} failed: ${message}` });
|
|
1587
1708
|
return {
|
|
1588
|
-
content: [{ type: "text", text:
|
|
1709
|
+
content: [{ type: "text", text: JSON.stringify({ error: message, success: false }) }],
|
|
1589
1710
|
isError: true
|
|
1590
1711
|
};
|
|
1591
1712
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plur-ai/mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.10",
|
|
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.9.
|
|
16
|
+
"@plur-ai/core": "0.9.10"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/node": "^25.5.0"
|