@plur-ai/mcp 0.9.7 → 0.9.9

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Give your AI agent persistent memory. One line in your MCP config — corrections, preferences, and conventions persist across sessions. No workflow changes, no cloud, no API costs for search.
4
4
 
5
- Part of [PLUR](https://plur.ai) — where **Haiku with memory outperforms Opus without it** at 10x less cost.
5
+ Part of [PLUR](https://plur.ai) — where, in our tool-routing and local-knowledge benchmark, **Haiku with memory outperformed Opus without it** at 10x less cost.
6
6
 
7
7
  ## Setup (30 seconds)
8
8
 
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSy
5
5
  import { join } from "path";
6
6
  import { fileURLToPath } from "url";
7
7
  import { homedir } from "os";
8
- var VERSION = "0.9.7";
8
+ var VERSION = "0.9.9";
9
9
  var HELP = `plur-mcp v${VERSION} \u2014 persistent memory for AI agents
10
10
 
11
11
  Usage:
@@ -277,7 +277,7 @@ if (arg === "init") {
277
277
  process.exit(0);
278
278
  }
279
279
  if (arg === "serve" || arg === void 0) {
280
- const { runStdio } = await import("./server-Y2KI6BN5.js");
280
+ const { runStdio } = await import("./server-X5ZMO46X.js");
281
281
  runStdio().catch((err) => {
282
282
  console.error("Failed to start PLUR MCP server:", err);
283
283
  process.exit(1);
@@ -15,6 +15,11 @@ import { Plur as Plur2, checkForUpdate } from "@plur-ai/core";
15
15
 
16
16
  // src/tools.ts
17
17
  import { extractMetaEngrams, validateMetaEngram, confidenceBand, generateProfile, getProfileForInjection, selectModelForOperation } from "@plur-ai/core";
18
+
19
+ // src/version.ts
20
+ var VERSION = "0.9.9";
21
+
22
+ // src/tools.ts
18
23
  function makeHttpLlm(baseUrl, apiKey, model = "gpt-4o-mini") {
19
24
  return async (prompt) => {
20
25
  const response = await fetch(`${baseUrl.replace(/\/$/, "")}/chat/completions`, {
@@ -72,6 +77,15 @@ function getLlmFunction() {
72
77
  if (openaiKey) return makeHttpLlm("https://api.openai.com/v1", openaiKey, "gpt-4o-mini");
73
78
  return void 0;
74
79
  }
80
+ function sanitizeStatement(raw) {
81
+ const markers = ["</statement>", "<parameter name="];
82
+ let cut = raw.length;
83
+ for (const m of markers) {
84
+ const pos = raw.indexOf(m);
85
+ if (pos !== -1 && pos < cut) cut = pos;
86
+ }
87
+ return raw.slice(0, cut).trimEnd();
88
+ }
75
89
  function getToolDefinitions() {
76
90
  return [
77
91
  {
@@ -89,38 +103,12 @@ function getToolDefinitions() {
89
103
  },
90
104
  scope: { type: "string", description: "Namespace, e.g. global, project:myapp" },
91
105
  domain: { type: "string", description: "Domain tag, e.g. software.deployment" },
92
- source: { type: "string", description: "Origin of this knowledge" },
93
- tags: { type: "array", items: { type: "string" }, description: "Searchable keyword tags" },
94
- rationale: { type: "string", description: "Why this knowledge matters" },
95
- visibility: { type: "string", enum: ["private", "public", "template"], description: "Visibility level" },
96
- knowledge_anchors: {
97
- type: "array",
98
- items: {
99
- type: "object",
100
- properties: {
101
- path: { type: "string", description: "Path to related document" },
102
- relevance: { type: "string", enum: ["primary", "supporting", "example"] },
103
- snippet: { type: "string", description: "Short snippet (max 200 chars)" }
104
- },
105
- required: ["path"]
106
- },
107
- description: "Links to related knowledge documents"
108
- },
109
- dual_coding: {
110
- type: "object",
111
- properties: {
112
- example: { type: "string", description: "Concrete example" },
113
- analogy: { type: "string", description: "Analogy to aid understanding" }
114
- },
115
- description: "Dual coding for richer encoding"
116
- },
117
- abstract: { type: "string", description: "Abstract engram ID this was derived from" },
118
- derived_from: { type: "string", description: "Source engram ID this was derived from" },
119
- commitment: { type: "string", enum: ["exploring", "leaning", "decided", "locked"], description: "Commitment level (default: leaning)" },
120
- locked_reason: { type: "string", description: "Reason for locking (when commitment=locked)" },
121
- memory_class: { type: "string", enum: ["semantic", "episodic", "procedural", "metacognitive"], description: "Memory classification (auto-set from type if omitted)" },
122
- session_episode_id: { type: "string", description: "Link to current session episode for episodic anchoring" },
123
- pinned: { type: "boolean", description: "Always-load flag. If true, this engram bypasses the keyword-relevance gate and is eligible for injection on every session, regardless of overlap with the user task. Use sparingly: meta-rules, safety conventions, core operating principles only." }
106
+ tags: { type: "array", items: { type: "string" }, description: "Searchable keyword tags \u2014 contribute to BM25/embedding recall, so concrete keywords pay off" },
107
+ rationale: { type: "string", description: "Why this knowledge matters \u2014 also enters the search corpus, helps recall by intent not just statement" },
108
+ source: { type: "string", description: "Origin of this knowledge (URL, conversation ref, etc.)" },
109
+ pinned: { type: "boolean", description: "Always-load flag. If true, this engram bypasses the keyword-relevance gate at injection time. Use sparingly: meta-rules, safety conventions, core operating principles only." },
110
+ commitment: { type: "string", enum: ["exploring", "leaning", "decided", "locked"], description: "How firmly the user has committed to this belief (default: leaning)" },
111
+ locked_reason: { type: "string", description: "Why this engram is locked (only meaningful when commitment=locked)" }
124
112
  },
125
113
  required: ["statement"]
126
114
  },
@@ -133,33 +121,32 @@ function getToolDefinitions() {
133
121
  source: args.source,
134
122
  tags: args.tags,
135
123
  rationale: args.rationale,
136
- visibility: args.visibility,
137
- knowledge_anchors: args.knowledge_anchors,
138
- dual_coding: args.dual_coding,
139
- abstract: args.abstract,
140
- derived_from: args.derived_from,
141
124
  commitment: args.commitment,
142
125
  locked_reason: args.locked_reason,
143
- memory_class: args.memory_class,
144
- session_episode_id: args.session_episode_id,
145
126
  pinned: args.pinned,
146
127
  llm
147
128
  };
129
+ const statement = sanitizeStatement(args.statement);
148
130
  try {
149
- const result = await plur.learnAsync(args.statement, context);
131
+ const engram = await plur.learnRouted(statement, context);
150
132
  return {
151
- id: result.engram.id,
152
- statement: result.engram.statement,
153
- scope: result.engram.scope,
154
- type: result.engram.type,
155
- pinned: result.engram.pinned === true,
156
- decision: result.decision,
157
- existing_id: result.existing_id,
158
- tensions: result.tensions
133
+ id: engram.id,
134
+ statement: engram.statement,
135
+ scope: engram.scope,
136
+ type: engram.type,
137
+ pinned: engram.pinned === true,
138
+ decision: "ADD"
139
+ };
140
+ } catch (err) {
141
+ const engram = plur.learn(statement, context);
142
+ return {
143
+ id: engram.id,
144
+ statement: engram.statement,
145
+ scope: engram.scope,
146
+ type: engram.type,
147
+ decision: "ADD",
148
+ warning: `Remote write failed (${err.message}); fell back to local. The id above is the local placeholder \u2014 the canonical engram is NOT on the server.`
159
149
  };
160
- } catch {
161
- const engram = plur.learn(args.statement, context);
162
- return { id: engram.id, statement: engram.statement, scope: engram.scope, type: engram.type, decision: "ADD" };
163
150
  }
164
151
  }
165
152
  },
@@ -174,7 +161,6 @@ function getToolDefinitions() {
174
161
  scope: { type: "string", description: "Filter by scope (also includes global)" },
175
162
  domain: { type: "string", description: "Filter by domain prefix" },
176
163
  limit: { type: "number", description: "Max results to return (default 20)" },
177
- min_strength: { type: "number", description: "Minimum retrieval strength (0-1)" },
178
164
  budget: { type: "object", description: "Budget constraints for sub-agents", properties: { max_tokens: { type: "number" }, max_results: { type: "number" } } },
179
165
  caller_session_id: { type: "string", description: "Caller session ID for budget enforcement" }
180
166
  },
@@ -184,8 +170,7 @@ function getToolDefinitions() {
184
170
  const results = plur.recall(args.query, {
185
171
  scope: args.scope,
186
172
  domain: args.domain,
187
- limit: args.limit,
188
- min_strength: args.min_strength
173
+ limit: args.limit
189
174
  });
190
175
  return {
191
176
  results: results.map((e) => ({
@@ -211,7 +196,6 @@ function getToolDefinitions() {
211
196
  scope: { type: "string", description: "Filter by scope (also includes global)" },
212
197
  domain: { type: "string", description: "Filter by domain prefix" },
213
198
  limit: { type: "number", description: "Max results to return (default 20)" },
214
- min_strength: { type: "number", description: "Minimum retrieval strength (0-1)" },
215
199
  budget: { type: "object", description: "Budget constraints for sub-agents", properties: { max_tokens: { type: "number" }, max_results: { type: "number" }, ttl_seconds: { type: "number" } } },
216
200
  caller_session_id: { type: "string", description: "Session ID of calling agent for budget enforcement" },
217
201
  include_episodes: { type: "boolean", description: "If true, include linked episode summaries for each engram (SP2 episodic anchoring)" }
@@ -224,8 +208,7 @@ function getToolDefinitions() {
224
208
  const meta = await plur.recallHybridWithMeta(args.query, {
225
209
  scope: args.scope,
226
210
  domain: args.domain,
227
- limit: effectiveLimit,
228
- min_strength: args.min_strength
211
+ limit: effectiveLimit
229
212
  });
230
213
  const results = meta.engrams;
231
214
  let truncated = false;
@@ -364,7 +347,7 @@ function getToolDefinitions() {
364
347
  const summary = { positive: 0, negative: 0, neutral: 0 };
365
348
  for (const { id, signal } of args.signals) {
366
349
  try {
367
- plur.feedback(id, signal);
350
+ await plur.feedback(id, signal);
368
351
  results.push({ id, signal, success: true });
369
352
  summary[signal]++;
370
353
  } catch (err) {
@@ -374,7 +357,7 @@ function getToolDefinitions() {
374
357
  return { mode: "batch", results, summary };
375
358
  }
376
359
  try {
377
- plur.feedback(args.id, args.signal);
360
+ await plur.feedback(args.id, args.signal);
378
361
  return { success: true, id: args.id, signal: args.signal };
379
362
  } catch (err) {
380
363
  if (err.message?.includes("readonly store")) {
@@ -431,14 +414,14 @@ function getToolDefinitions() {
431
414
  const engram = plur.getById(args.id);
432
415
  if (!engram) throw new Error(`Engram not found: ${args.id}`);
433
416
  if (engram.status === "retired") return { success: false, error: `Already retired: ${args.id}` };
434
- plur.forget(args.id);
417
+ await plur.forget(args.id);
435
418
  return { success: true, retired: { id: engram.id, statement: engram.statement } };
436
419
  }
437
420
  if (args.search) {
438
421
  const matches = plur.recall(args.search, { limit: 100 });
439
422
  if (matches.length === 0) return { success: false, error: `No active engrams matching "${args.search}"` };
440
423
  if (matches.length === 1) {
441
- plur.forget(matches[0].id);
424
+ await plur.forget(matches[0].id);
442
425
  return { success: true, retired: { id: matches[0].id, statement: matches[0].statement } };
443
426
  }
444
427
  return {
@@ -830,7 +813,7 @@ function getToolDefinitions() {
830
813
  },
831
814
  {
832
815
  name: "plur_status",
833
- description: "Return system health \u2014 engram count, episode count, pack count, storage root",
816
+ description: "Return system health \u2014 running version, engram count, episode count, pack count, storage root",
834
817
  annotations: { title: "Status", readOnlyHint: true, idempotentHint: true },
835
818
  inputSchema: {
836
819
  type: "object",
@@ -839,6 +822,7 @@ function getToolDefinitions() {
839
822
  handler: async (_args, plur) => {
840
823
  const status = plur.status();
841
824
  return {
825
+ version: VERSION,
842
826
  engram_count: status.engram_count,
843
827
  episode_count: status.episode_count,
844
828
  pack_count: status.pack_count,
@@ -1173,6 +1157,20 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
1173
1157
  return { tensions, count: tensions.length };
1174
1158
  }
1175
1159
  },
1160
+ {
1161
+ name: "plur_tensions_purge",
1162
+ description: "Purge all conflict relations from local engrams \u2014 removes accumulated false positives from the legacy tension-detection system",
1163
+ annotations: { title: "Purge Tensions", destructiveHint: true, idempotentHint: true },
1164
+ inputSchema: { type: "object", properties: {} },
1165
+ handler: async (_args, plur) => {
1166
+ const result = plur.purgeTensions();
1167
+ return {
1168
+ purged_conflict_refs: result.purged_count,
1169
+ engrams_modified: result.engrams_modified,
1170
+ message: `Purged ${result.purged_count} conflict references from ${result.engrams_modified} engrams.`
1171
+ };
1172
+ }
1173
+ },
1176
1174
  {
1177
1175
  name: "plur_episode_to_engram",
1178
1176
  description: "Promote an episode to a persistent episodic engram \u2014 useful when a session event deserves long-term memory",
@@ -1419,7 +1417,6 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
1419
1417
 
1420
1418
  // src/server.ts
1421
1419
  import { z } from "zod";
1422
- var VERSION = "0.9.7";
1423
1420
  var INSTRUCTIONS = `PLUR is your persistent memory. Corrections, preferences, and conventions persist across sessions as engrams.
1424
1421
 
1425
1422
  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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plur-ai/mcp",
3
- "version": "0.9.7",
3
+ "version": "0.9.9",
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.7"
16
+ "@plur-ai/core": "0.9.9"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/node": "^25.5.0"