@plur-ai/mcp 0.9.10 → 0.9.12

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
@@ -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, platform } from "os";
8
- var VERSION = "0.9.10";
8
+ var VERSION = "0.9.12";
9
9
  var HELP = `plur-mcp v${VERSION} \u2014 persistent memory for AI agents
10
10
 
11
11
  Usage:
@@ -279,7 +279,7 @@ if (arg === "init") {
279
279
  process.exit(0);
280
280
  }
281
281
  if (arg === "serve" || arg === void 0) {
282
- const { runStdio } = await import("./server-GRHXPO6E.js");
282
+ const { runStdio } = await import("./server-AZFA22VU.js");
283
283
  runStdio().catch((err) => {
284
284
  console.error("Failed to start PLUR MCP server:", err);
285
285
  process.exit(1);
@@ -14,10 +14,13 @@ 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, getCachedUpdateCheck, minorVersionsBehind, scanForTensions, CapabilityCanary } from "@plur-ai/core";
17
+ import { existsSync, unlinkSync } from "fs";
18
+ import { join } from "path";
19
+ import { homedir } from "os";
20
+ import { extractMetaEngrams, validateMetaEngram, confidenceBand, generateProfile, getProfileForInjection, selectModelForOperation, getCachedUpdateCheck, minorVersionsBehind, scanForTensions, CapabilityCanary, readProjectConfig } from "@plur-ai/core";
18
21
 
19
22
  // src/version.ts
20
- var VERSION = "0.9.10";
23
+ var VERSION = "0.9.12";
21
24
 
22
25
  // src/tools.ts
23
26
  function makeHttpLlm(baseUrl, apiKey, model = "gpt-4o-mini") {
@@ -406,7 +409,7 @@ function getToolDefinitions() {
406
409
  }
407
410
  if (!args.id) throw new Error("Provide id (or list:true to list pinned)");
408
411
  const target = args.pinned ?? true;
409
- const updated = plur.setPinned(args.id, target);
412
+ const updated = await plur.setPinnedAsync(args.id, target);
410
413
  if (!updated) throw new Error(`Engram not found: ${args.id}`);
411
414
  return {
412
415
  id: updated.id,
@@ -970,7 +973,8 @@ function getToolDefinitions() {
970
973
  type: "object",
971
974
  properties: {
972
975
  task: { type: "string", description: "What you are working on (triggers engram injection)" },
973
- tags: { type: "array", items: { type: "string" }, description: "Tags to filter injected engrams" }
976
+ tags: { type: "array", items: { type: "string" }, description: "Tags to filter injected engrams" },
977
+ default_scope: { type: "string", description: "Default scope for plur_learn calls this session when no explicit scope is provided. Only set this if you want ALL engrams to route to a specific store. Usually, leave unset and pass scope per-engram based on relevance." }
974
978
  },
975
979
  required: ["task"]
976
980
  },
@@ -986,12 +990,20 @@ function getToolDefinitions() {
986
990
  outbox_result = await plur.flushOutbox();
987
991
  } catch {
988
992
  }
993
+ const remote_scopes = plur.getWritableRemoteScopes();
994
+ const projectConfig = readProjectConfig();
995
+ const explicit_default_scope = args.default_scope ?? null;
996
+ const default_scope = explicit_default_scope ?? projectConfig.scope ?? null;
997
+ const scope_source = explicit_default_scope ? "caller" : projectConfig.scope ? "project-config" : "none";
998
+ plur.setSessionScope(default_scope);
989
999
  const status = plur.status();
990
1000
  const store_stats = {
991
1001
  engram_count: status.engram_count,
992
1002
  episode_count: status.episode_count,
993
1003
  pack_count: status.pack_count
994
1004
  };
1005
+ await plur.warmRemoteCaches().catch(() => {
1006
+ });
995
1007
  let engrams = null;
996
1008
  try {
997
1009
  const result = await plur.injectHybrid(task, {
@@ -1040,11 +1052,31 @@ ${guide}`;
1040
1052
  version_warning = `Update available: PLUR v${versionCheck.current} \u2192 v${versionCheck.latest}. Run: npx @plur-ai/mcp@latest`;
1041
1053
  }
1042
1054
  }
1055
+ if (scope_source === "project-config") {
1056
+ guide += `
1057
+
1058
+ Auto-detected project scope: "${default_scope}" (from .plur.yaml in the current project). plur_learn calls without an explicit scope will be tagged with this scope, keeping this project's knowledge separate from your other projects. Pass scope: "global" only for genuinely cross-project knowledge (general coding conventions, language gotchas, tool quirks).`;
1059
+ } else if (scope_source === "none") {
1060
+ guide += `
1061
+
1062
+ \u26A0\uFE0F No project scope detected. plur_learn calls without explicit scope will be tagged "global" and will appear in EVERY project's future sessions. To avoid context bleed across projects, create a .plur.yaml in this project's root with: scope: "project:<your-project-name>"`;
1063
+ }
1064
+ if (remote_scopes.length > 0) {
1065
+ const scopeList = remote_scopes.map((s) => `"${s.scope}"`).join(", ");
1066
+ guide += default_scope ? `
1067
+
1068
+ Session default scope is set to "${default_scope}". To route an engram to a remote enterprise store instead, pass scope explicitly to plur_learn (available remote scopes: ${scopeList}).` : `
1069
+
1070
+ Remote store scopes available: ${scopeList}. When an engram is relevant to the team (engineering patterns, architecture decisions, project conventions), set scope to the matching remote scope in plur_learn. Personal preferences, local project details, and corrections specific to your workflow should stay at default scope (local).`;
1071
+ }
1043
1072
  return {
1044
1073
  session_id,
1045
1074
  engrams: engrams ?? [],
1046
1075
  store_stats,
1047
1076
  guide,
1077
+ // Remote scope routing info (#229)
1078
+ ...remote_scopes.length > 0 ? { remote_scopes } : {},
1079
+ ...default_scope ? { default_scope, scope_source } : {},
1048
1080
  // Ask LLM to check back — MCP can't push, but we can request a follow-up
1049
1081
  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,
1050
1082
  // On fresh install, suggest hook setup for reliable injection
@@ -1082,14 +1114,21 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
1082
1114
  engram_suggestions: {
1083
1115
  type: "array",
1084
1116
  items: {
1085
- type: "object",
1086
- properties: {
1087
- statement: { type: "string", description: "A concise, reusable assertion. Write it as advice to your future self." },
1088
- type: { type: "string", enum: ["behavioral", "terminological", "procedural", "architectural"] }
1089
- },
1090
- required: ["statement"]
1117
+ // Prefer {statement, type} objects. Bare strings are tolerated
1118
+ // and treated as {statement: <string>} (issue #231).
1119
+ anyOf: [
1120
+ { type: "string" },
1121
+ {
1122
+ type: "object",
1123
+ properties: {
1124
+ statement: { type: "string", description: "A concise, reusable assertion. Write it as advice to your future self." },
1125
+ type: { type: "string", enum: ["behavioral", "terminological", "procedural", "architectural"] }
1126
+ },
1127
+ required: ["statement"]
1128
+ }
1129
+ ]
1091
1130
  },
1092
- description: "Learnings from this session. Review the conversation for corrections, preferences, patterns, and technical facts before calling."
1131
+ 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.'
1093
1132
  }
1094
1133
  },
1095
1134
  required: ["summary", "engram_suggestions"]
@@ -1099,9 +1138,23 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
1099
1138
  const session_id = args.session_id;
1100
1139
  const suggestions = args.engram_suggestions;
1101
1140
  let engrams_created = 0;
1102
- if (suggestions?.length) {
1103
- for (const s of suggestions) {
1104
- plur.learn(s.statement, { type: s.type });
1141
+ if (Array.isArray(suggestions) && suggestions.length) {
1142
+ for (let i = 0; i < suggestions.length; i++) {
1143
+ const s = suggestions[i];
1144
+ let statement;
1145
+ let type;
1146
+ if (typeof s === "string") {
1147
+ statement = s;
1148
+ } else if (s && typeof s === "object") {
1149
+ statement = s.statement;
1150
+ type = s.type;
1151
+ }
1152
+ if (typeof statement !== "string" || statement.length === 0) {
1153
+ throw new Error(
1154
+ `engram_suggestions[${i}] must be a string or {statement: string, type?: string}, got ${typeof s}`
1155
+ );
1156
+ }
1157
+ plur.learn(statement, { type });
1105
1158
  engrams_created++;
1106
1159
  }
1107
1160
  }
@@ -1109,6 +1162,19 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
1109
1162
  session_id,
1110
1163
  channel: "mcp"
1111
1164
  });
1165
+ try {
1166
+ const plurDir = process.env.PLUR_PATH ?? join(homedir(), ".plur");
1167
+ const sessionsDir = join(plurDir, "sessions");
1168
+ const keys = [session_id, process.env.CLAUDE_SESSION_ID, String(process.ppid)].filter(Boolean).map((k) => k.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 64));
1169
+ for (const key of keys) {
1170
+ const cp = join(sessionsDir, `${key}.checkpoint.json`);
1171
+ if (existsSync(cp)) {
1172
+ unlinkSync(cp);
1173
+ break;
1174
+ }
1175
+ }
1176
+ } catch {
1177
+ }
1112
1178
  const status = plur.status();
1113
1179
  return {
1114
1180
  engrams_created,
@@ -1160,7 +1226,7 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
1160
1226
  annotations: { title: "List stores", readOnlyHint: true, idempotentHint: true },
1161
1227
  inputSchema: { type: "object", properties: {} },
1162
1228
  handler: async (_args, plur) => {
1163
- const stores = plur.listStores();
1229
+ const stores = await plur.listStoresAsync();
1164
1230
  const outboxCount = plur.outboxCount();
1165
1231
  return {
1166
1232
  stores,
@@ -1430,9 +1496,9 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
1430
1496
  if (filterType) {
1431
1497
  engrams = engrams.filter((e) => e.type === filterType);
1432
1498
  }
1433
- const { homedir } = await import("os");
1434
- const { join } = await import("path");
1435
- const outputDir = args.output_dir || join(homedir(), "plur-packs", name);
1499
+ const { homedir: homedir2 } = await import("os");
1500
+ const { join: join2 } = await import("path");
1501
+ const outputDir = args.output_dir || join2(homedir2(), "plur-packs", name);
1436
1502
  const result = plur.exportPack(engrams, outputDir, {
1437
1503
  name,
1438
1504
  version: "1.0.0",
@@ -1641,6 +1707,31 @@ Use \`scope\` to namespace engrams per project:
1641
1707
 
1642
1708
  Override with \`PLUR_PATH\` environment variable.
1643
1709
  `;
1710
+ function jsonSchemaPropToZod(prop) {
1711
+ if (!prop || typeof prop !== "object") return z.unknown();
1712
+ const variants = prop.anyOf ?? prop.oneOf;
1713
+ if (Array.isArray(variants) && variants.length > 0) {
1714
+ const zodVariants = variants.map(jsonSchemaPropToZod);
1715
+ if (zodVariants.length === 1) return zodVariants[0];
1716
+ return z.union(zodVariants);
1717
+ }
1718
+ if (prop.type === "string") return prop.enum ? z.enum(prop.enum) : z.string();
1719
+ if (prop.type === "number" || prop.type === "integer") return z.number();
1720
+ if (prop.type === "boolean") return z.boolean();
1721
+ if (prop.type === "array") {
1722
+ const itemSchema = prop.items ? jsonSchemaPropToZod(prop.items) : z.unknown();
1723
+ return z.array(itemSchema);
1724
+ }
1725
+ if (prop.type === "object" && prop.properties) {
1726
+ const shape = {};
1727
+ for (const [k, p] of Object.entries(prop.properties)) {
1728
+ const field = jsonSchemaPropToZod(p);
1729
+ shape[k] = prop.required?.includes(k) ? field : field.optional();
1730
+ }
1731
+ return z.object(shape).passthrough();
1732
+ }
1733
+ return z.unknown();
1734
+ }
1644
1735
  async function createServer(plur) {
1645
1736
  const instance = plur ?? new Plur2();
1646
1737
  const tools = getToolDefinitions();
@@ -1683,12 +1774,7 @@ async function createServer(plur) {
1683
1774
  if (schema?.properties) {
1684
1775
  const shape = {};
1685
1776
  for (const [key, prop] of Object.entries(schema.properties)) {
1686
- let field;
1687
- if (prop.type === "string") field = prop.enum ? z.enum(prop.enum) : z.string();
1688
- else if (prop.type === "number") field = z.number();
1689
- else if (prop.type === "boolean") field = z.boolean();
1690
- else if (prop.type === "array") field = z.array(z.unknown());
1691
- else field = z.unknown();
1777
+ const field = jsonSchemaPropToZod(prop);
1692
1778
  shape[key] = schema.required?.includes(key) ? field : field.optional();
1693
1779
  }
1694
1780
  const parsed = z.object(shape).passthrough().safeParse(args);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plur-ai/mcp",
3
- "version": "0.9.10",
3
+ "version": "0.9.12",
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.10"
16
+ "@plur-ai/core": "0.9.12"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/node": "^25.5.0"