@plur-ai/mcp 0.5.3 → 0.7.0

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
@@ -50,31 +50,32 @@ Your agent gets these tools automatically:
50
50
 
51
51
  | Tool | What it does |
52
52
  |------|-------------|
53
- | `plur.learn` | Store a memorycorrection, preference, convention, or decision |
54
- | `plur.recall` | Keyword search (BM25, instant) |
55
- | `plur.recall.hybrid` | **Best default** — BM25 + embeddings merged via RRF. Zero cost. |
56
- | `plur.inject` | Load relevant memories for the current task |
57
- | `plur.feedback` | Rate a memory trains relevance over time |
58
- | `plur.forget` | Retire a memory (history preserved) |
59
- | `plur.sync` | Sync memory across machines via git |
60
- | `plur.sync.status` | Check sync state |
61
- | `plur.capture` | Record a session event |
62
- | `plur.timeline` | Query session history |
63
- | `plur.ingest` | Extract learnings from text |
64
- | `plur.packs.install` | Install a shareable memory pack |
65
- | `plur.packs.list` | List installed packs |
66
- | `plur.status` | System health |
53
+ | `plur_session_start` | Start a sessioninjects relevant engrams for your task |
54
+ | `plur_learn` | Store a memory — correction, preference, convention, or decision |
55
+ | `plur_recall_hybrid` | **Best default** — BM25 + embeddings merged via RRF. Zero cost. |
56
+ | `plur_recall` | Keyword search (BM25 only, instant) |
57
+ | `plur_inject_hybrid` | Load relevant memories for the current task |
58
+ | `plur_feedback` | Rate a memory trains relevance over time |
59
+ | `plur_forget` | Retire a memory (history preserved) |
60
+ | `plur_session_end` | End a session — captures summary and new learnings |
61
+ | `plur_capture` | Record a session event |
62
+ | `plur_timeline` | Query session history |
63
+ | `plur_ingest` | Extract learnings from text |
64
+ | `plur_sync` | Sync memory across machines via git |
65
+ | `plur_packs_install` | Install a shareable memory pack |
66
+ | `plur_packs_list` | List installed packs |
67
+ | `plur_status` | System health |
67
68
 
68
69
  ## Sync across machines
69
70
 
70
71
  Your agent can sync memory to any git remote:
71
72
 
72
73
  ```
73
- Agent: plur.sync({ remote: "git@github.com:you/plur-memory.git" })
74
+ Agent: plur_sync({ remote: "git@github.com:you/plur-memory.git" })
74
75
  → "Initialized and pushed."
75
76
 
76
77
  # On another machine, same remote:
77
- Agent: plur.sync()
78
+ Agent: plur_sync()
78
79
  → "Synced. Pulled 12 remote commits."
79
80
  ```
80
81
 
package/dist/index.js CHANGED
@@ -4,12 +4,12 @@
4
4
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
5
5
  import { join } from "path";
6
6
  import { homedir } from "os";
7
- var VERSION = "0.5.3";
7
+ var VERSION = "0.7.0";
8
8
  var HELP = `plur-mcp v${VERSION} \u2014 persistent memory for AI agents
9
9
 
10
10
  Usage:
11
11
  plur-mcp Start the MCP server (stdio transport)
12
- plur-mcp init Set up PLUR: storage + MCP config + Claude Code hooks
12
+ plur-mcp init Set up PLUR: storage + MCP config + hooks + CLAUDE.md
13
13
  plur-mcp --help Show this help message
14
14
  plur-mcp --version Show version
15
15
 
@@ -25,15 +25,93 @@ var MCP_SERVER_CONFIG = {
25
25
  command: "npx",
26
26
  args: ["-y", "@plur-ai/mcp@latest"]
27
27
  };
28
+ var CLI = "npx @plur-ai/cli";
28
29
  var PLUR_HOOKS = {
30
+ // --- Session lifecycle ---
29
31
  UserPromptSubmit: [{
30
- hooks: [{ type: "command", command: "npx @plur-ai/cli hook-inject", timeout: 15 }]
32
+ hooks: [{ type: "command", command: `${CLI} hook-inject`, timeout: 15 }]
31
33
  }],
32
34
  PostCompact: [{
33
35
  matcher: "auto|manual",
34
- hooks: [{ type: "command", command: "npx @plur-ai/cli hook-inject --rehydrate", timeout: 15 }]
35
- }]
36
+ hooks: [{ type: "command", command: `${CLI} hook-inject --rehydrate`, timeout: 15 }]
37
+ }],
38
+ // --- Contextual injection ---
39
+ PreToolUse: [
40
+ { matcher: "EnterPlanMode", hooks: [{ type: "command", command: `${CLI} hook-inject --event plan_mode`, timeout: 10 }] },
41
+ { matcher: "Skill", hooks: [{ type: "command", command: `${CLI} hook-inject --event skill`, timeout: 10 }] },
42
+ { matcher: "Agent", hooks: [{ type: "command", command: `${CLI} hook-inject --event agent`, timeout: 10 }] },
43
+ { matcher: "Bash|Edit|Write|Agent", hooks: [{ type: "command", command: `${CLI} hook-observe`, timeout: 3 }] }
44
+ ],
45
+ PostToolUse: [
46
+ { matcher: "Bash|Edit|Write|Agent", hooks: [{ type: "command", command: `${CLI} hook-observe --post`, timeout: 3 }] }
47
+ ],
48
+ SubagentStart: [
49
+ { matcher: ".*", hooks: [{ type: "command", command: `${CLI} hook-inject --event subagent`, timeout: 10 }] }
50
+ ]
36
51
  };
52
+ var CLAUDE_MD_SECTION = `## PLUR Memory
53
+
54
+ You have persistent memory via PLUR. Corrections, preferences, and conventions persist across sessions as engrams.
55
+
56
+ ### Session Workflow
57
+
58
+ 1. **Start**: Call \`plur_session_start\` with task description \u2014 injects relevant engrams
59
+ 2. **Learn**: When corrected or discovering something new, call \`plur_learn\` immediately
60
+ 3. **Recall**: Before answering factual questions, call \`plur_recall_hybrid\` \u2014 check memory first
61
+ 4. **Feedback**: Rate injected engrams with \`plur_feedback\` (positive/negative) \u2014 trains relevance
62
+ 5. **End**: Call \`plur_session_end\` with summary + engram_suggestions
63
+
64
+ Do not ask permission to use these tools \u2014 they are your memory system.
65
+
66
+ ### When to check memory
67
+
68
+ Before reaching for web search, file reads, or guessing \u2014 apply this priority:
69
+ 1. Is the answer already in engrams? \u2192 \`plur_recall_hybrid\`
70
+ 2. Is the answer in the local filesystem? \u2192 Read/Grep/Glob
71
+ 3. Is the answer derivable from context already loaded? \u2192 Just answer
72
+ 4. Only if 1-3 fail \u2192 Use external tools
73
+
74
+ | Domain | When to recall |
75
+ |--------|----------------|
76
+ | Decisions | Past design choices, architecture rationale |
77
+ | Corrections | API quirks, bugs, wrong assumptions |
78
+ | Preferences | Formatting, tone, workflow, tool choices |
79
+ | Conventions | Tag formats, file routing, naming rules |
80
+ | Infrastructure | Server IPs, SSH configs, deployment targets |
81
+
82
+ ### When corrected
83
+
84
+ When the user corrects you ("no, use X not Y", "that's wrong"):
85
+ 1. Call \`plur_learn\` immediately \u2014 before continuing the task
86
+ 2. Call \`plur_feedback\` with negative signal on the wrong engram if one was injected
87
+ 3. Then continue with the corrected approach
88
+
89
+ ### Verification
90
+
91
+ When recalling facts that will drive actions:
92
+ 1. State the recalled fact explicitly before acting on it
93
+ 2. Include the engram ID or search that produced it
94
+ 3. If no engram matches, say so and verify from the filesystem
95
+ 4. Never interpolate between two engrams to produce a "probably correct" composite
96
+ `;
97
+ function installClaudeMd() {
98
+ const marker = "## PLUR Memory";
99
+ const projectClaudeMd = join(process.cwd(), "CLAUDE.md");
100
+ const globalClaudeMd = join(homedir(), "CLAUDE.md");
101
+ const claudeMdPath = existsSync(projectClaudeMd) ? projectClaudeMd : existsSync(globalClaudeMd) ? globalClaudeMd : projectClaudeMd;
102
+ if (existsSync(claudeMdPath)) {
103
+ const content = readFileSync(claudeMdPath, "utf8");
104
+ if (content.includes(marker)) {
105
+ return `already in ${claudeMdPath}`;
106
+ }
107
+ writeFileSync(claudeMdPath, content.trimEnd() + "\n\n" + CLAUDE_MD_SECTION);
108
+ return `added to ${claudeMdPath}`;
109
+ }
110
+ writeFileSync(claudeMdPath, `# CLAUDE.md
111
+
112
+ ${CLAUDE_MD_SECTION}`);
113
+ return `created ${claudeMdPath}`;
114
+ }
37
115
  function findMcpConfig() {
38
116
  const projectMcp = join(process.cwd(), ".mcp.json");
39
117
  if (existsSync(projectMcp)) return projectMcp;
@@ -113,6 +191,8 @@ async function runInit() {
113
191
  results.push(`MCP: ${mcpStatus}`);
114
192
  const hooksStatus = installHooks();
115
193
  results.push(`Hooks: ${hooksStatus}`);
194
+ const claudeMdStatus = installClaudeMd();
195
+ results.push(`CLAUDE.md: ${claudeMdStatus}`);
116
196
  process.stdout.write(`PLUR initialized.
117
197
 
118
198
  ${results.join("\n ")}
@@ -143,7 +223,7 @@ if (arg === "init") {
143
223
  process.exit(0);
144
224
  }
145
225
  if (arg === "serve" || arg === void 0) {
146
- const { runStdio } = await import("./server-VFKTDY7O.js");
226
+ const { runStdio } = await import("./server-2UMO2VDB.js");
147
227
  runStdio().catch((err) => {
148
228
  console.error("Failed to start PLUR MCP server:", err);
149
229
  process.exit(1);
@@ -297,8 +297,15 @@ function getToolDefinitions() {
297
297
  }
298
298
  return { mode: "batch", results, summary };
299
299
  }
300
- plur.feedback(args.id, args.signal);
301
- return { success: true, id: args.id, signal: args.signal };
300
+ try {
301
+ plur.feedback(args.id, args.signal);
302
+ return { success: true, id: args.id, signal: args.signal };
303
+ } catch (err) {
304
+ if (err.message?.includes("readonly store")) {
305
+ return { success: false, id: args.id, signal: args.signal, note: "Engram is in a readonly store. Feedback noted for this session but not persisted." };
306
+ }
307
+ throw err;
308
+ }
302
309
  }
303
310
  },
304
311
  {
@@ -439,7 +446,7 @@ function getToolDefinitions() {
439
446
  },
440
447
  {
441
448
  name: "plur_packs_install",
442
- description: "Install an engram pack from a directory path \u2014 adds curated engrams to the store",
449
+ description: "Install an engram pack from a directory path \u2014 adds curated engrams to the store. Reports conflicts with existing engrams.",
443
450
  annotations: { title: "Install pack", destructiveHint: false, idempotentHint: true },
444
451
  inputSchema: {
445
452
  type: "object",
@@ -450,12 +457,32 @@ function getToolDefinitions() {
450
457
  },
451
458
  handler: async (args, plur) => {
452
459
  const result = plur.installPack(args.source);
453
- return { installed: result.installed, name: result.name, success: true };
460
+ return {
461
+ installed: result.installed,
462
+ name: result.name,
463
+ conflicts: result.conflicts,
464
+ success: true
465
+ };
466
+ }
467
+ },
468
+ {
469
+ name: "plur_packs_uninstall",
470
+ description: "Uninstall an engram pack by name \u2014 removes the pack and all its engrams",
471
+ annotations: { title: "Uninstall pack", destructiveHint: true, idempotentHint: false },
472
+ inputSchema: {
473
+ type: "object",
474
+ properties: {
475
+ name: { type: "string", description: "Pack name to uninstall (use plur_packs_list to see names)" }
476
+ },
477
+ required: ["name"]
478
+ },
479
+ handler: async (args, plur) => {
480
+ return plur.uninstallPack(args.name);
454
481
  }
455
482
  },
456
483
  {
457
484
  name: "plur_packs_list",
458
- description: "List all installed engram packs",
485
+ description: "List all installed engram packs with integrity hashes",
459
486
  annotations: { title: "List packs", readOnlyHint: true, idempotentHint: true },
460
487
  inputSchema: {
461
488
  type: "object",
@@ -466,14 +493,35 @@ function getToolDefinitions() {
466
493
  return {
467
494
  packs: packs.map((p) => ({
468
495
  name: p.name,
469
- version: p.version,
470
- description: p.description,
471
- engram_count: p.engram_count
496
+ version: p.manifest?.version,
497
+ description: p.manifest?.description,
498
+ engram_count: p.engram_count,
499
+ integrity: p.integrity
472
500
  })),
473
501
  count: packs.length
474
502
  };
475
503
  }
476
504
  },
505
+ {
506
+ name: "plur_packs_discover",
507
+ description: "Browse available engram packs from the registry \u2014 discover curated expertise packs to install",
508
+ annotations: { title: "Discover packs", readOnlyHint: true, idempotentHint: true, openWorldHint: true },
509
+ inputSchema: {
510
+ type: "object",
511
+ properties: {
512
+ query: { type: "string", description: "Search query to filter packs by name or description" },
513
+ tags: { type: "array", items: { type: "string" }, description: "Filter by tags" },
514
+ category: { type: "string", description: "Filter by category (e.g., devops, trading, writing)" }
515
+ }
516
+ },
517
+ handler: async (args, plur) => {
518
+ return {
519
+ packs: [],
520
+ count: 0,
521
+ message: "Pack discovery coming soon. For now, install packs from local paths via plur_packs_install."
522
+ };
523
+ }
524
+ },
477
525
  {
478
526
  name: "plur_sync",
479
527
  description: "Sync engrams via git \u2014 initializes repo on first call, commits and pushes/pulls on subsequent calls. Provide a remote URL on first call to enable cross-device sync.",
@@ -742,27 +790,35 @@ You have ${store_stats.engram_count} engrams but none matched this task. Call pl
742
790
  },
743
791
  {
744
792
  name: "plur_session_end",
745
- description: "End a session \u2014 captures an episode and creates engrams from suggestions. Call before the conversation ends.",
793
+ description: `End a session. BEFORE calling this tool, review the conversation and extract learnings:
794
+
795
+ 1. Corrections the user made ("no, use X not Y") \u2192 type: behavioral
796
+ 2. Preferences stated ("always X", "never Y") \u2192 type: behavioral
797
+ 3. Codebase patterns discovered (naming, structure, conventions) \u2192 type: architectural
798
+ 4. Technical facts learned (API quirks, config, gotchas) \u2192 type: procedural
799
+ 5. Terminology defined or clarified \u2192 type: terminological
800
+
801
+ Include at least one engram_suggestion if ANYTHING was learned. An empty suggestions array means nothing worth remembering happened \u2014 this should be rare.`,
746
802
  annotations: { title: "Session End", destructiveHint: false, idempotentHint: false },
747
803
  inputSchema: {
748
804
  type: "object",
749
805
  properties: {
750
- summary: { type: "string", description: "What happened in this session" },
806
+ summary: { type: "string", description: "What happened in this session (1-3 sentences)" },
751
807
  session_id: { type: "string", description: "Session ID from plur_session_start" },
752
808
  engram_suggestions: {
753
809
  type: "array",
754
810
  items: {
755
811
  type: "object",
756
812
  properties: {
757
- statement: { type: "string", description: "The knowledge assertion" },
813
+ statement: { type: "string", description: "A concise, reusable assertion. Write it as advice to your future self." },
758
814
  type: { type: "string", enum: ["behavioral", "terminological", "procedural", "architectural"] }
759
815
  },
760
816
  required: ["statement"]
761
817
  },
762
- description: "New learnings to capture as engrams"
818
+ description: "Learnings from this session. Review the conversation for corrections, preferences, patterns, and technical facts before calling."
763
819
  }
764
820
  },
765
- required: ["summary"]
821
+ required: ["summary", "engram_suggestions"]
766
822
  },
767
823
  handler: async (args, plur) => {
768
824
  const summary = args.summary;
@@ -862,33 +918,56 @@ You have ${store_stats.engram_count} engrams but none matched this task. Call pl
862
918
  },
863
919
  {
864
920
  name: "plur_packs_export",
865
- description: "Export personal engrams as a shareable pack",
921
+ description: "Export engrams as a shareable thematic pack with privacy scanning and integrity hash. Filters out private and secret-containing engrams automatically. Output goes to ~/plur-packs/<name> by default.",
866
922
  annotations: { title: "Export pack", destructiveHint: false, idempotentHint: false },
867
923
  inputSchema: {
868
924
  type: "object",
869
925
  properties: {
870
- name: { type: "string", description: "Pack name" },
926
+ name: { type: "string", description: 'Pack name (e.g. "react-patterns", "mcp-design")' },
871
927
  description: { type: "string", description: "Pack description" },
872
- filter_domain: { type: "string", description: "Filter engrams by domain prefix" },
873
- filter_scope: { type: "string", description: "Filter engrams by scope" },
874
- output_dir: { type: "string", description: "Output directory for the pack (default: ~/.plur/exports/)" }
928
+ filter_domain: { type: "string", description: 'Filter engrams by domain prefix (e.g. "mcp", "trading")' },
929
+ filter_scope: { type: "string", description: 'Filter engrams by scope (e.g. "global", "project:myapp")' },
930
+ filter_tags: { type: "array", items: { type: "string" }, description: "Filter by tags" },
931
+ filter_type: { type: "string", enum: ["behavioral", "procedural", "architectural", "terminological"], description: "Filter by engram type" },
932
+ output_dir: { type: "string", description: "Output directory (default: ~/plur-packs/<name>)" },
933
+ creator: { type: "string", description: "Creator name" }
875
934
  },
876
935
  required: ["name"]
877
936
  },
878
937
  handler: async (args, plur) => {
879
938
  const name = args.name;
880
- const engrams = plur.list({
939
+ let engrams = plur.list({
881
940
  domain: args.filter_domain,
882
941
  scope: args.filter_scope
883
- }).filter((e) => e.visibility !== "private" || !args.filter_domain);
884
- const outputDir = args.output_dir || `${plur.status().storage_root}/exports`;
942
+ });
943
+ const filterTags = args.filter_tags;
944
+ if (filterTags) {
945
+ engrams = engrams.filter(
946
+ (e) => e.tags && filterTags.some((t) => e.tags.includes(t))
947
+ );
948
+ }
949
+ const filterType = args.filter_type;
950
+ if (filterType) {
951
+ engrams = engrams.filter((e) => e.type === filterType);
952
+ }
953
+ const { homedir } = await import("os");
954
+ const { join } = await import("path");
955
+ const outputDir = args.output_dir || join(homedir(), "plur-packs", name);
885
956
  const result = plur.exportPack(engrams, outputDir, {
886
957
  name,
887
958
  version: "1.0.0",
888
959
  description: args.description,
889
- creator: "plur-mcp"
960
+ creator: args.creator || void 0
890
961
  });
891
- return { path: result.path, engram_count: result.engram_count, name };
962
+ return {
963
+ path: result.path,
964
+ engram_count: result.engram_count,
965
+ integrity: result.integrity,
966
+ match_terms: result.match_terms,
967
+ privacy_clean: result.privacy.clean,
968
+ privacy_issues: result.privacy.issues.length,
969
+ name
970
+ };
892
971
  }
893
972
  }
894
973
  ];
@@ -896,7 +975,7 @@ You have ${store_stats.engram_count} engrams but none matched this task. Call pl
896
975
 
897
976
  // src/server.ts
898
977
  import { z } from "zod";
899
- var VERSION = "0.5.3";
978
+ var VERSION = "0.7.0";
900
979
  var INSTRUCTIONS = `PLUR is your persistent memory. Corrections, preferences, and conventions persist across sessions as engrams.
901
980
 
902
981
  REQUIRED at session boundaries:
@@ -912,6 +991,8 @@ OPTIONAL but improves quality:
912
991
  - Call plur_feedback to rate which injected engrams helped (positive/negative)
913
992
  - Call plur_recall_hybrid before answering factual questions \u2014 the answer may be in memory
914
993
 
994
+ For combined search (engrams + files): call plur_recall_hybrid for memories, then datacore.search for journal/knowledge files.
995
+
915
996
  Do not ask permission to use these tools \u2014 they are your memory system.
916
997
 
917
998
  Setup: If this is a fresh install, suggest the user run: npx @plur-ai/cli init
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plur-ai/mcp",
3
- "version": "0.5.3",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "plur-mcp": "dist/index.js"
@@ -12,7 +12,7 @@
12
12
  "dependencies": {
13
13
  "@modelcontextprotocol/sdk": "^1.12.0",
14
14
  "zod": "^3.23.0",
15
- "@plur-ai/core": "0.5.2"
15
+ "@plur-ai/core": "0.7.0"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@types/node": "^25.5.0"