@plur-ai/mcp 0.9.9 → 0.9.11
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.11";
|
|
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-FHZHOMM5.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.11";
|
|
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
|
},
|
|
@@ -999,14 +1082,21 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
999
1082
|
engram_suggestions: {
|
|
1000
1083
|
type: "array",
|
|
1001
1084
|
items: {
|
|
1002
|
-
type
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1085
|
+
// Prefer {statement, type} objects. Bare strings are tolerated
|
|
1086
|
+
// and treated as {statement: <string>} (issue #231).
|
|
1087
|
+
anyOf: [
|
|
1088
|
+
{ type: "string" },
|
|
1089
|
+
{
|
|
1090
|
+
type: "object",
|
|
1091
|
+
properties: {
|
|
1092
|
+
statement: { type: "string", description: "A concise, reusable assertion. Write it as advice to your future self." },
|
|
1093
|
+
type: { type: "string", enum: ["behavioral", "terminological", "procedural", "architectural"] }
|
|
1094
|
+
},
|
|
1095
|
+
required: ["statement"]
|
|
1096
|
+
}
|
|
1097
|
+
]
|
|
1008
1098
|
},
|
|
1009
|
-
description:
|
|
1099
|
+
description: 'Learnings from this session. Preferred shape is {statement: "...", type?: "..."}; bare strings are also accepted and treated as the statement. Review the conversation for corrections, preferences, patterns, and technical facts before calling.'
|
|
1010
1100
|
}
|
|
1011
1101
|
},
|
|
1012
1102
|
required: ["summary", "engram_suggestions"]
|
|
@@ -1016,9 +1106,23 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1016
1106
|
const session_id = args.session_id;
|
|
1017
1107
|
const suggestions = args.engram_suggestions;
|
|
1018
1108
|
let engrams_created = 0;
|
|
1019
|
-
if (suggestions
|
|
1020
|
-
for (
|
|
1021
|
-
|
|
1109
|
+
if (Array.isArray(suggestions) && suggestions.length) {
|
|
1110
|
+
for (let i = 0; i < suggestions.length; i++) {
|
|
1111
|
+
const s = suggestions[i];
|
|
1112
|
+
let statement;
|
|
1113
|
+
let type;
|
|
1114
|
+
if (typeof s === "string") {
|
|
1115
|
+
statement = s;
|
|
1116
|
+
} else if (s && typeof s === "object") {
|
|
1117
|
+
statement = s.statement;
|
|
1118
|
+
type = s.type;
|
|
1119
|
+
}
|
|
1120
|
+
if (typeof statement !== "string" || statement.length === 0) {
|
|
1121
|
+
throw new Error(
|
|
1122
|
+
`engram_suggestions[${i}] must be a string or {statement: string, type?: string}, got ${typeof s}`
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
plur.learn(statement, { type });
|
|
1022
1126
|
engrams_created++;
|
|
1023
1127
|
}
|
|
1024
1128
|
}
|
|
@@ -1077,8 +1181,13 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1077
1181
|
annotations: { title: "List stores", readOnlyHint: true, idempotentHint: true },
|
|
1078
1182
|
inputSchema: { type: "object", properties: {} },
|
|
1079
1183
|
handler: async (_args, plur) => {
|
|
1080
|
-
const stores = plur.
|
|
1081
|
-
|
|
1184
|
+
const stores = await plur.listStoresAsync();
|
|
1185
|
+
const outboxCount = plur.outboxCount();
|
|
1186
|
+
return {
|
|
1187
|
+
stores,
|
|
1188
|
+
count: stores.length,
|
|
1189
|
+
...outboxCount > 0 ? { outbox_pending: outboxCount } : {}
|
|
1190
|
+
};
|
|
1082
1191
|
}
|
|
1083
1192
|
},
|
|
1084
1193
|
{
|
|
@@ -1123,13 +1232,19 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1123
1232
|
},
|
|
1124
1233
|
{
|
|
1125
1234
|
name: "plur_tensions",
|
|
1126
|
-
description: "List engram pairs that have conflicting knowledge
|
|
1235
|
+
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
1236
|
annotations: { title: "Tensions", readOnlyHint: true, idempotentHint: true },
|
|
1128
1237
|
inputSchema: {
|
|
1129
1238
|
type: "object",
|
|
1130
1239
|
properties: {
|
|
1131
1240
|
scope: { type: "string", description: "Filter by scope" },
|
|
1132
|
-
domain: { type: "string", description: "Filter by domain prefix" }
|
|
1241
|
+
domain: { type: "string", description: "Filter by domain prefix" },
|
|
1242
|
+
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." },
|
|
1243
|
+
llm_base_url: { type: "string", description: "OpenAI-compatible API base URL for scan mode (e.g. https://api.openai.com/v1)" },
|
|
1244
|
+
llm_api_key: { type: "string", description: "API key for the LLM (scan mode)" },
|
|
1245
|
+
llm_model: { type: "string", description: "Model name for scan mode (default: gpt-4o-mini)" },
|
|
1246
|
+
min_confidence: { type: "number", description: "Minimum confidence threshold for scan mode (0\u20131, default: 0.7)" },
|
|
1247
|
+
max_pairs: { type: "number", description: "Maximum candidate pairs to evaluate in scan mode (default: 50)" }
|
|
1133
1248
|
}
|
|
1134
1249
|
},
|
|
1135
1250
|
handler: async (args, plur) => {
|
|
@@ -1137,6 +1252,30 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1137
1252
|
scope: args.scope,
|
|
1138
1253
|
domain: args.domain
|
|
1139
1254
|
});
|
|
1255
|
+
if (args.scan) {
|
|
1256
|
+
const llm = args.llm_base_url && args.llm_api_key ? makeHttpLlm(args.llm_base_url, args.llm_api_key, args.llm_model) : getLlmFunction();
|
|
1257
|
+
if (!llm) {
|
|
1258
|
+
return {
|
|
1259
|
+
error: "scan mode requires an LLM. Set OPENAI_API_KEY or OPENROUTER_API_KEY, or pass llm_base_url + llm_api_key.",
|
|
1260
|
+
tensions: [],
|
|
1261
|
+
count: 0
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
const result = await scanForTensions(engrams, llm, {
|
|
1265
|
+
min_confidence: args.min_confidence,
|
|
1266
|
+
max_pairs: args.max_pairs
|
|
1267
|
+
});
|
|
1268
|
+
return {
|
|
1269
|
+
pairs_checked: result.pairs_checked,
|
|
1270
|
+
count: result.new_tensions,
|
|
1271
|
+
tensions: result.tensions.map((t) => ({
|
|
1272
|
+
engram_a: { id: t.id_a, statement: t.statement_a },
|
|
1273
|
+
engram_b: { id: t.id_b, statement: t.statement_b },
|
|
1274
|
+
confidence: t.confidence,
|
|
1275
|
+
reason: t.reason
|
|
1276
|
+
}))
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1140
1279
|
const tensions = [];
|
|
1141
1280
|
const seen = /* @__PURE__ */ new Set();
|
|
1142
1281
|
for (const engram of engrams) {
|
|
@@ -1150,11 +1289,13 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1150
1289
|
tensions.push({
|
|
1151
1290
|
engram_a: { id: engram.id, statement: engram.statement, type: engram.type },
|
|
1152
1291
|
engram_b: { id: other.id, statement: other.statement, type: other.type },
|
|
1153
|
-
detected_at: engram.activation.last_accessed
|
|
1292
|
+
detected_at: engram.activation.last_accessed,
|
|
1293
|
+
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
1294
|
});
|
|
1155
1295
|
}
|
|
1156
1296
|
}
|
|
1157
|
-
|
|
1297
|
+
const purge_hint = tensions.length > 0 ? "These are legacy conflict relations. Run plur_tensions_purge to clear them." : void 0;
|
|
1298
|
+
return { tensions, count: tensions.length, ...purge_hint ? { purge_hint } : {} };
|
|
1158
1299
|
}
|
|
1159
1300
|
},
|
|
1160
1301
|
{
|
|
@@ -1364,7 +1505,7 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1364
1505
|
},
|
|
1365
1506
|
{
|
|
1366
1507
|
name: "plur_batch_decay",
|
|
1367
|
-
description: "Apply ACT-R decay to all engrams. Run weekly. Returns status transitions only.",
|
|
1508
|
+
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
1509
|
annotations: { title: "Batch decay", destructiveHint: false, idempotentHint: false },
|
|
1369
1510
|
inputSchema: {
|
|
1370
1511
|
type: "object",
|
|
@@ -1521,6 +1662,31 @@ Use \`scope\` to namespace engrams per project:
|
|
|
1521
1662
|
|
|
1522
1663
|
Override with \`PLUR_PATH\` environment variable.
|
|
1523
1664
|
`;
|
|
1665
|
+
function jsonSchemaPropToZod(prop) {
|
|
1666
|
+
if (!prop || typeof prop !== "object") return z.unknown();
|
|
1667
|
+
const variants = prop.anyOf ?? prop.oneOf;
|
|
1668
|
+
if (Array.isArray(variants) && variants.length > 0) {
|
|
1669
|
+
const zodVariants = variants.map(jsonSchemaPropToZod);
|
|
1670
|
+
if (zodVariants.length === 1) return zodVariants[0];
|
|
1671
|
+
return z.union(zodVariants);
|
|
1672
|
+
}
|
|
1673
|
+
if (prop.type === "string") return prop.enum ? z.enum(prop.enum) : z.string();
|
|
1674
|
+
if (prop.type === "number" || prop.type === "integer") return z.number();
|
|
1675
|
+
if (prop.type === "boolean") return z.boolean();
|
|
1676
|
+
if (prop.type === "array") {
|
|
1677
|
+
const itemSchema = prop.items ? jsonSchemaPropToZod(prop.items) : z.unknown();
|
|
1678
|
+
return z.array(itemSchema);
|
|
1679
|
+
}
|
|
1680
|
+
if (prop.type === "object" && prop.properties) {
|
|
1681
|
+
const shape = {};
|
|
1682
|
+
for (const [k, p] of Object.entries(prop.properties)) {
|
|
1683
|
+
const field = jsonSchemaPropToZod(p);
|
|
1684
|
+
shape[k] = prop.required?.includes(k) ? field : field.optional();
|
|
1685
|
+
}
|
|
1686
|
+
return z.object(shape).passthrough();
|
|
1687
|
+
}
|
|
1688
|
+
return z.unknown();
|
|
1689
|
+
}
|
|
1524
1690
|
async function createServer(plur) {
|
|
1525
1691
|
const instance = plur ?? new Plur2();
|
|
1526
1692
|
const tools = getToolDefinitions();
|
|
@@ -1553,7 +1719,7 @@ async function createServer(plur) {
|
|
|
1553
1719
|
const tool = tools.find((t) => t.name === request.params.name);
|
|
1554
1720
|
if (!tool) {
|
|
1555
1721
|
return {
|
|
1556
|
-
content: [{ type: "text", text: `Unknown tool: ${request.params.name}
|
|
1722
|
+
content: [{ type: "text", text: JSON.stringify({ error: `Unknown tool: ${request.params.name}`, success: false }) }],
|
|
1557
1723
|
isError: true
|
|
1558
1724
|
};
|
|
1559
1725
|
}
|
|
@@ -1563,18 +1729,14 @@ async function createServer(plur) {
|
|
|
1563
1729
|
if (schema?.properties) {
|
|
1564
1730
|
const shape = {};
|
|
1565
1731
|
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
1566
|
-
|
|
1567
|
-
if (prop.type === "string") field = prop.enum ? z.enum(prop.enum) : z.string();
|
|
1568
|
-
else if (prop.type === "number") field = z.number();
|
|
1569
|
-
else if (prop.type === "boolean") field = z.boolean();
|
|
1570
|
-
else if (prop.type === "array") field = z.array(z.unknown());
|
|
1571
|
-
else field = z.unknown();
|
|
1732
|
+
const field = jsonSchemaPropToZod(prop);
|
|
1572
1733
|
shape[key] = schema.required?.includes(key) ? field : field.optional();
|
|
1573
1734
|
}
|
|
1574
1735
|
const parsed = z.object(shape).passthrough().safeParse(args);
|
|
1575
1736
|
if (!parsed.success) {
|
|
1737
|
+
const details = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
|
|
1576
1738
|
return {
|
|
1577
|
-
content: [{ type: "text", text: `Invalid arguments: ${
|
|
1739
|
+
content: [{ type: "text", text: JSON.stringify({ error: `Invalid arguments: ${details}`, success: false }) }],
|
|
1578
1740
|
isError: true
|
|
1579
1741
|
};
|
|
1580
1742
|
}
|
|
@@ -1585,7 +1747,7 @@ async function createServer(plur) {
|
|
|
1585
1747
|
const message = err?.message ?? String(err);
|
|
1586
1748
|
server.sendLoggingMessage({ level: "error", data: `Tool ${request.params.name} failed: ${message}` });
|
|
1587
1749
|
return {
|
|
1588
|
-
content: [{ type: "text", text:
|
|
1750
|
+
content: [{ type: "text", text: JSON.stringify({ error: message, success: false }) }],
|
|
1589
1751
|
isError: true
|
|
1590
1752
|
};
|
|
1591
1753
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plur-ai/mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.11",
|
|
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.11"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/node": "^25.5.0"
|