@plur-ai/mcp 0.9.8 → 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.8";
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-4IHAOMXN.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,20 +121,14 @@ 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 engram = await plur.learnRouted(args.statement, context);
131
+ const engram = await plur.learnRouted(statement, context);
150
132
  return {
151
133
  id: engram.id,
152
134
  statement: engram.statement,
@@ -156,7 +138,7 @@ function getToolDefinitions() {
156
138
  decision: "ADD"
157
139
  };
158
140
  } catch (err) {
159
- const engram = plur.learn(args.statement, context);
141
+ const engram = plur.learn(statement, context);
160
142
  return {
161
143
  id: engram.id,
162
144
  statement: engram.statement,
@@ -179,7 +161,6 @@ function getToolDefinitions() {
179
161
  scope: { type: "string", description: "Filter by scope (also includes global)" },
180
162
  domain: { type: "string", description: "Filter by domain prefix" },
181
163
  limit: { type: "number", description: "Max results to return (default 20)" },
182
- min_strength: { type: "number", description: "Minimum retrieval strength (0-1)" },
183
164
  budget: { type: "object", description: "Budget constraints for sub-agents", properties: { max_tokens: { type: "number" }, max_results: { type: "number" } } },
184
165
  caller_session_id: { type: "string", description: "Caller session ID for budget enforcement" }
185
166
  },
@@ -189,8 +170,7 @@ function getToolDefinitions() {
189
170
  const results = plur.recall(args.query, {
190
171
  scope: args.scope,
191
172
  domain: args.domain,
192
- limit: args.limit,
193
- min_strength: args.min_strength
173
+ limit: args.limit
194
174
  });
195
175
  return {
196
176
  results: results.map((e) => ({
@@ -216,7 +196,6 @@ function getToolDefinitions() {
216
196
  scope: { type: "string", description: "Filter by scope (also includes global)" },
217
197
  domain: { type: "string", description: "Filter by domain prefix" },
218
198
  limit: { type: "number", description: "Max results to return (default 20)" },
219
- min_strength: { type: "number", description: "Minimum retrieval strength (0-1)" },
220
199
  budget: { type: "object", description: "Budget constraints for sub-agents", properties: { max_tokens: { type: "number" }, max_results: { type: "number" }, ttl_seconds: { type: "number" } } },
221
200
  caller_session_id: { type: "string", description: "Session ID of calling agent for budget enforcement" },
222
201
  include_episodes: { type: "boolean", description: "If true, include linked episode summaries for each engram (SP2 episodic anchoring)" }
@@ -229,8 +208,7 @@ function getToolDefinitions() {
229
208
  const meta = await plur.recallHybridWithMeta(args.query, {
230
209
  scope: args.scope,
231
210
  domain: args.domain,
232
- limit: effectiveLimit,
233
- min_strength: args.min_strength
211
+ limit: effectiveLimit
234
212
  });
235
213
  const results = meta.engrams;
236
214
  let truncated = false;
@@ -369,7 +347,7 @@ function getToolDefinitions() {
369
347
  const summary = { positive: 0, negative: 0, neutral: 0 };
370
348
  for (const { id, signal } of args.signals) {
371
349
  try {
372
- plur.feedback(id, signal);
350
+ await plur.feedback(id, signal);
373
351
  results.push({ id, signal, success: true });
374
352
  summary[signal]++;
375
353
  } catch (err) {
@@ -379,7 +357,7 @@ function getToolDefinitions() {
379
357
  return { mode: "batch", results, summary };
380
358
  }
381
359
  try {
382
- plur.feedback(args.id, args.signal);
360
+ await plur.feedback(args.id, args.signal);
383
361
  return { success: true, id: args.id, signal: args.signal };
384
362
  } catch (err) {
385
363
  if (err.message?.includes("readonly store")) {
@@ -436,14 +414,14 @@ function getToolDefinitions() {
436
414
  const engram = plur.getById(args.id);
437
415
  if (!engram) throw new Error(`Engram not found: ${args.id}`);
438
416
  if (engram.status === "retired") return { success: false, error: `Already retired: ${args.id}` };
439
- plur.forget(args.id);
417
+ await plur.forget(args.id);
440
418
  return { success: true, retired: { id: engram.id, statement: engram.statement } };
441
419
  }
442
420
  if (args.search) {
443
421
  const matches = plur.recall(args.search, { limit: 100 });
444
422
  if (matches.length === 0) return { success: false, error: `No active engrams matching "${args.search}"` };
445
423
  if (matches.length === 1) {
446
- plur.forget(matches[0].id);
424
+ await plur.forget(matches[0].id);
447
425
  return { success: true, retired: { id: matches[0].id, statement: matches[0].statement } };
448
426
  }
449
427
  return {
@@ -835,7 +813,7 @@ function getToolDefinitions() {
835
813
  },
836
814
  {
837
815
  name: "plur_status",
838
- 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",
839
817
  annotations: { title: "Status", readOnlyHint: true, idempotentHint: true },
840
818
  inputSchema: {
841
819
  type: "object",
@@ -844,6 +822,7 @@ function getToolDefinitions() {
844
822
  handler: async (_args, plur) => {
845
823
  const status = plur.status();
846
824
  return {
825
+ version: VERSION,
847
826
  engram_count: status.engram_count,
848
827
  episode_count: status.episode_count,
849
828
  pack_count: status.pack_count,
@@ -1178,6 +1157,20 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
1178
1157
  return { tensions, count: tensions.length };
1179
1158
  }
1180
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
+ },
1181
1174
  {
1182
1175
  name: "plur_episode_to_engram",
1183
1176
  description: "Promote an episode to a persistent episodic engram \u2014 useful when a session event deserves long-term memory",
@@ -1424,7 +1417,6 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
1424
1417
 
1425
1418
  // src/server.ts
1426
1419
  import { z } from "zod";
1427
- var VERSION = "0.9.8";
1428
1420
  var INSTRUCTIONS = `PLUR is your persistent memory. Corrections, preferences, and conventions persist across sessions as engrams.
1429
1421
 
1430
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.8",
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.8"
16
+ "@plur-ai/core": "0.9.9"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/node": "^25.5.0"