@plur-ai/mcp 0.5.2 → 0.6.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
@@ -8,16 +8,14 @@ Part of [PLUR](https://plur.ai) — where **Haiku with memory outperforms Opus w
8
8
 
9
9
  ### Claude Code
10
10
 
11
- Add to `.claude/mcp.json`:
11
+ One command — sets up storage, MCP config, and hooks:
12
12
 
13
- ```json
14
- {
15
- "mcpServers": {
16
- "plur": { "command": "npx", "args": ["-y", "@plur-ai/mcp"] }
17
- }
18
- }
13
+ ```bash
14
+ npx @plur-ai/mcp init
19
15
  ```
20
16
 
17
+ Restart Claude Code. Done. Your agent now has persistent memory with automatic injection.
18
+
21
19
  ### Cursor
22
20
 
23
21
  Add to `.cursor/mcp.json`:
@@ -52,31 +50,32 @@ Your agent gets these tools automatically:
52
50
 
53
51
  | Tool | What it does |
54
52
  |------|-------------|
55
- | `plur.learn` | Store a memorycorrection, preference, convention, or decision |
56
- | `plur.recall` | Keyword search (BM25, instant) |
57
- | `plur.recall.hybrid` | **Best default** — BM25 + embeddings merged via RRF. Zero cost. |
58
- | `plur.inject` | Load relevant memories for the current task |
59
- | `plur.feedback` | Rate a memory trains relevance over time |
60
- | `plur.forget` | Retire a memory (history preserved) |
61
- | `plur.sync` | Sync memory across machines via git |
62
- | `plur.sync.status` | Check sync state |
63
- | `plur.capture` | Record a session event |
64
- | `plur.timeline` | Query session history |
65
- | `plur.ingest` | Extract learnings from text |
66
- | `plur.packs.install` | Install a shareable memory pack |
67
- | `plur.packs.list` | List installed packs |
68
- | `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 |
69
68
 
70
69
  ## Sync across machines
71
70
 
72
71
  Your agent can sync memory to any git remote:
73
72
 
74
73
  ```
75
- Agent: plur.sync({ remote: "git@github.com:you/plur-memory.git" })
74
+ Agent: plur_sync({ remote: "git@github.com:you/plur-memory.git" })
76
75
  → "Initialized and pushed."
77
76
 
78
77
  # On another machine, same remote:
79
- Agent: plur.sync()
78
+ Agent: plur_sync()
80
79
  → "Synced. Pulled 12 remote commits."
81
80
  ```
82
81
 
package/dist/index.js CHANGED
@@ -1,81 +1,149 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- var VERSION = "0.5.2";
4
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
5
+ import { join } from "path";
6
+ import { homedir } from "os";
7
+ var VERSION = "0.6.0";
5
8
  var HELP = `plur-mcp v${VERSION} \u2014 persistent memory for AI agents
6
9
 
7
10
  Usage:
8
11
  plur-mcp Start the MCP server (stdio transport)
9
- plur-mcp init Initialize storage and print setup instructions
12
+ plur-mcp init Set up PLUR: storage + MCP config + Claude Code hooks
10
13
  plur-mcp --help Show this help message
11
14
  plur-mcp --version Show version
12
15
 
13
16
  Environment:
14
17
  PLUR_PATH Storage location (default: ~/.plur/)
15
18
 
16
- Setup:
17
- Add to .claude/mcp.json (Claude Code) or .cursor/mcp.json (Cursor):
18
-
19
- {
20
- "mcpServers": {
21
- "plur": { "command": "npx", "args": ["-y", "@plur-ai/mcp@latest"] }
22
- }
23
- }
19
+ Quick start:
20
+ npx @plur-ai/mcp init
24
21
 
25
22
  Docs: https://plur.ai \xB7 https://github.com/plur-ai/plur
26
23
  `;
27
- var arg = process.argv[2];
28
- if (arg === "--help" || arg === "-h") {
29
- process.stdout.write(HELP);
30
- process.exit(0);
24
+ var MCP_SERVER_CONFIG = {
25
+ command: "npx",
26
+ args: ["-y", "@plur-ai/mcp@latest"]
27
+ };
28
+ var PLUR_HOOKS = {
29
+ UserPromptSubmit: [{
30
+ hooks: [{ type: "command", command: "npx @plur-ai/cli hook-inject", timeout: 15 }]
31
+ }],
32
+ PostCompact: [{
33
+ matcher: "auto|manual",
34
+ hooks: [{ type: "command", command: "npx @plur-ai/cli hook-inject --rehydrate", timeout: 15 }]
35
+ }]
36
+ };
37
+ function findMcpConfig() {
38
+ const projectMcp = join(process.cwd(), ".mcp.json");
39
+ if (existsSync(projectMcp)) return projectMcp;
40
+ const globalMcp = join(homedir(), ".claude", "mcp.json");
41
+ if (existsSync(globalMcp)) return globalMcp;
42
+ return projectMcp;
31
43
  }
32
- if (arg === "--version" || arg === "-v") {
33
- process.stdout.write(`${VERSION}
34
- `);
35
- process.exit(0);
44
+ function writeMcpConfig(configPath) {
45
+ let config = {};
46
+ if (existsSync(configPath)) {
47
+ try {
48
+ config = JSON.parse(readFileSync(configPath, "utf8"));
49
+ } catch {
50
+ }
51
+ }
52
+ const servers = config.mcpServers ?? {};
53
+ if (servers.plur) {
54
+ return `already configured in ${configPath}`;
55
+ }
56
+ servers.plur = MCP_SERVER_CONFIG;
57
+ config.mcpServers = servers;
58
+ const dir = join(configPath, "..");
59
+ mkdirSync(dir, { recursive: true });
60
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
61
+ return `added to ${configPath}`;
36
62
  }
37
- if (arg === "init") {
63
+ function installHooks() {
64
+ const projectSettings = join(process.cwd(), ".claude", "settings.json");
65
+ const globalSettings = join(homedir(), ".claude", "settings.json");
66
+ const settingsPath = existsSync(join(process.cwd(), ".claude")) ? projectSettings : globalSettings;
67
+ let settings = {};
68
+ if (existsSync(settingsPath)) {
69
+ try {
70
+ settings = JSON.parse(readFileSync(settingsPath, "utf8"));
71
+ } catch {
72
+ }
73
+ }
74
+ const hooks = settings.hooks ?? {};
75
+ for (const entries of Object.values(hooks)) {
76
+ for (const entry of entries) {
77
+ for (const h of entry.hooks ?? []) {
78
+ if (h.command.includes("@plur-ai/cli")) {
79
+ return `already installed in ${settingsPath}`;
80
+ }
81
+ }
82
+ }
83
+ }
84
+ const existing = settings.hooks ?? {};
85
+ const merged = { ...existing };
86
+ for (const [event, newEntries] of Object.entries(PLUR_HOOKS)) {
87
+ merged[event] = [...merged[event] ?? [], ...newEntries];
88
+ }
89
+ settings.hooks = merged;
90
+ const dir = join(settingsPath, "..");
91
+ mkdirSync(dir, { recursive: true });
92
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
93
+ return `installed in ${settingsPath}`;
94
+ }
95
+ async function runInit() {
96
+ const results = [];
38
97
  const { detectPlurStorage } = await import("@plur-ai/core");
39
98
  const paths = detectPlurStorage();
40
- let embeddingsAvailable = false;
99
+ results.push(`Storage: ${paths.root}`);
100
+ let searchMode = "BM25 keyword search";
41
101
  try {
42
102
  const mod = "@huggingface/transformers";
43
103
  await import(
44
104
  /* @vite-ignore */
45
105
  mod
46
106
  );
47
- embeddingsAvailable = true;
107
+ searchMode = "hybrid (BM25 + embeddings)";
48
108
  } catch {
49
109
  }
50
- const searchMode = embeddingsAvailable ? "hybrid (BM25 + embeddings)" : "BM25 keyword search (embeddings not installed \u2014 install @huggingface/transformers for hybrid search)";
110
+ results.push(`Search: ${searchMode}`);
111
+ const mcpConfigPath = findMcpConfig();
112
+ const mcpStatus = writeMcpConfig(mcpConfigPath);
113
+ results.push(`MCP: ${mcpStatus}`);
114
+ const hooksStatus = installHooks();
115
+ results.push(`Hooks: ${hooksStatus}`);
51
116
  process.stdout.write(`PLUR initialized.
52
117
 
53
- Storage: ${paths.root}
54
- Search: ${searchMode}
118
+ ${results.join("\n ")}
55
119
 
56
- Next step \u2014 add to your MCP config:
120
+ `);
121
+ if (mcpStatus.includes("added") || hooksStatus.includes("installed")) {
122
+ process.stdout.write(` Restart Claude Code to activate.
57
123
 
58
- Claude Code (.claude/mcp.json):
59
- {
60
- "mcpServers": {
61
- "plur": { "command": "npx", "args": ["-y", "@plur-ai/mcp@latest"] }
62
- }
63
- }
124
+ `);
125
+ } else {
126
+ process.stdout.write(` Everything is set up. Start a new conversation to use PLUR.
64
127
 
65
- Cursor (.cursor/mcp.json):
66
- {
67
- "mcpServers": {
68
- "plur": { "command": "npx", "args": ["-y", "@plur-ai/mcp@latest"] }
69
- }
128
+ `);
70
129
  }
71
-
72
- Then restart your editor. Your agent now has persistent memory.
73
- Learn more: https://plur.ai
130
+ }
131
+ var arg = process.argv[2];
132
+ if (arg === "--help" || arg === "-h") {
133
+ process.stdout.write(HELP);
134
+ process.exit(0);
135
+ }
136
+ if (arg === "--version" || arg === "-v") {
137
+ process.stdout.write(`${VERSION}
74
138
  `);
75
139
  process.exit(0);
76
140
  }
141
+ if (arg === "init") {
142
+ await runInit();
143
+ process.exit(0);
144
+ }
77
145
  if (arg === "serve" || arg === void 0) {
78
- const { runStdio } = await import("./server-4WAIYC37.js");
146
+ const { runStdio } = await import("./server-5BXYBMHX.js");
79
147
  runStdio().catch((err) => {
80
148
  console.error("Failed to start PLUR MCP server:", err);
81
149
  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
  {
@@ -742,27 +749,35 @@ You have ${store_stats.engram_count} engrams but none matched this task. Call pl
742
749
  },
743
750
  {
744
751
  name: "plur_session_end",
745
- description: "End a session \u2014 captures an episode and creates engrams from suggestions. Call before the conversation ends.",
752
+ description: `End a session. BEFORE calling this tool, review the conversation and extract learnings:
753
+
754
+ 1. Corrections the user made ("no, use X not Y") \u2192 type: behavioral
755
+ 2. Preferences stated ("always X", "never Y") \u2192 type: behavioral
756
+ 3. Codebase patterns discovered (naming, structure, conventions) \u2192 type: architectural
757
+ 4. Technical facts learned (API quirks, config, gotchas) \u2192 type: procedural
758
+ 5. Terminology defined or clarified \u2192 type: terminological
759
+
760
+ 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
761
  annotations: { title: "Session End", destructiveHint: false, idempotentHint: false },
747
762
  inputSchema: {
748
763
  type: "object",
749
764
  properties: {
750
- summary: { type: "string", description: "What happened in this session" },
765
+ summary: { type: "string", description: "What happened in this session (1-3 sentences)" },
751
766
  session_id: { type: "string", description: "Session ID from plur_session_start" },
752
767
  engram_suggestions: {
753
768
  type: "array",
754
769
  items: {
755
770
  type: "object",
756
771
  properties: {
757
- statement: { type: "string", description: "The knowledge assertion" },
772
+ statement: { type: "string", description: "A concise, reusable assertion. Write it as advice to your future self." },
758
773
  type: { type: "string", enum: ["behavioral", "terminological", "procedural", "architectural"] }
759
774
  },
760
775
  required: ["statement"]
761
776
  },
762
- description: "New learnings to capture as engrams"
777
+ description: "Learnings from this session. Review the conversation for corrections, preferences, patterns, and technical facts before calling."
763
778
  }
764
779
  },
765
- required: ["summary"]
780
+ required: ["summary", "engram_suggestions"]
766
781
  },
767
782
  handler: async (args, plur) => {
768
783
  const summary = args.summary;
@@ -896,7 +911,7 @@ You have ${store_stats.engram_count} engrams but none matched this task. Call pl
896
911
 
897
912
  // src/server.ts
898
913
  import { z } from "zod";
899
- var VERSION = "0.5.2";
914
+ var VERSION = "0.6.0";
900
915
  var INSTRUCTIONS = `PLUR is your persistent memory. Corrections, preferences, and conventions persist across sessions as engrams.
901
916
 
902
917
  REQUIRED at session boundaries:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plur-ai/mcp",
3
- "version": "0.5.2",
3
+ "version": "0.6.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.6.0"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@types/node": "^25.5.0"