@plur-ai/mcp 0.4.2 → 0.5.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/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- var VERSION = "0.4.2";
4
+ var VERSION = "0.5.0";
5
5
  var HELP = `plur-mcp v${VERSION} \u2014 persistent memory for AI agents
6
6
 
7
7
  Usage:
@@ -75,7 +75,7 @@ if (arg === "init") {
75
75
  process.exit(0);
76
76
  }
77
77
  if (arg === "serve" || arg === void 0) {
78
- const { runStdio } = await import("./server-OF3VLIHF.js");
78
+ const { runStdio } = await import("./server-3QMSEZUP.js");
79
79
  runStdio().catch((err) => {
80
80
  console.error("Failed to start PLUR MCP server:", err);
81
81
  process.exit(1);
@@ -36,6 +36,20 @@ function makeHttpLlm(baseUrl, apiKey, model = "gpt-4o-mini") {
36
36
  return data.choices?.[0]?.message?.content ?? "";
37
37
  };
38
38
  }
39
+ var PLUR_GUIDE = `## PLUR Quick Start
40
+
41
+ Persistent memory for AI agents. Corrections, preferences, and conventions stored as engrams.
42
+
43
+ ### Session Workflow
44
+ 1. **plur_session_start** (you just called this) \u2014 get context
45
+ 2. Work on your task
46
+ 3. **plur_feedback** \u2014 rate which injected engrams helped
47
+ 4. **plur_session_end** \u2014 capture summary + suggest new engrams
48
+
49
+ ### Core Tools
50
+ - **plur_learn** \u2014 record patterns, preferences, insights
51
+ - **plur_recall_hybrid** \u2014 search engrams
52
+ - **plur_forget** \u2014 retire an outdated engram`;
39
53
  function getToolDefinitions() {
40
54
  return [
41
55
  {
@@ -53,7 +67,33 @@ function getToolDefinitions() {
53
67
  },
54
68
  scope: { type: "string", description: "Namespace, e.g. global, project:myapp" },
55
69
  domain: { type: "string", description: "Domain tag, e.g. software.deployment" },
56
- source: { type: "string", description: "Origin of this knowledge" }
70
+ source: { type: "string", description: "Origin of this knowledge" },
71
+ tags: { type: "array", items: { type: "string" }, description: "Searchable keyword tags" },
72
+ rationale: { type: "string", description: "Why this knowledge matters" },
73
+ visibility: { type: "string", enum: ["private", "public", "template"], description: "Visibility level" },
74
+ knowledge_anchors: {
75
+ type: "array",
76
+ items: {
77
+ type: "object",
78
+ properties: {
79
+ path: { type: "string", description: "Path to related document" },
80
+ relevance: { type: "string", enum: ["primary", "supporting", "example"] },
81
+ snippet: { type: "string", description: "Short snippet (max 200 chars)" }
82
+ },
83
+ required: ["path"]
84
+ },
85
+ description: "Links to related knowledge documents"
86
+ },
87
+ dual_coding: {
88
+ type: "object",
89
+ properties: {
90
+ example: { type: "string", description: "Concrete example" },
91
+ analogy: { type: "string", description: "Analogy to aid understanding" }
92
+ },
93
+ description: "Dual coding for richer encoding"
94
+ },
95
+ abstract: { type: "string", description: "Abstract engram ID this was derived from" },
96
+ derived_from: { type: "string", description: "Source engram ID this was derived from" }
57
97
  },
58
98
  required: ["statement"]
59
99
  },
@@ -62,7 +102,14 @@ function getToolDefinitions() {
62
102
  type: args.type,
63
103
  scope: args.scope,
64
104
  domain: args.domain,
65
- source: args.source
105
+ source: args.source,
106
+ tags: args.tags,
107
+ rationale: args.rationale,
108
+ visibility: args.visibility,
109
+ knowledge_anchors: args.knowledge_anchors,
110
+ dual_coding: args.dual_coding,
111
+ abstract: args.abstract,
112
+ derived_from: args.derived_from
66
113
  });
67
114
  return { id: engram.id, statement: engram.statement, scope: engram.scope, type: engram.type };
68
115
  }
@@ -160,7 +207,8 @@ function getToolDefinitions() {
160
207
  directives: result.directives,
161
208
  consider: result.consider,
162
209
  count: result.count,
163
- tokens_used: result.tokens_used
210
+ tokens_used: result.tokens_used,
211
+ injected_ids: result.injected_ids
164
212
  };
165
213
  }
166
214
  },
@@ -187,46 +235,91 @@ function getToolDefinitions() {
187
235
  consider: result.consider,
188
236
  count: result.count,
189
237
  tokens_used: result.tokens_used,
238
+ injected_ids: result.injected_ids,
190
239
  mode: "hybrid"
191
240
  };
192
241
  }
193
242
  },
194
243
  {
195
244
  name: "plur_feedback",
196
- description: "Rate an engram's usefulness \u2014 trains injection relevance over time",
245
+ description: "Rate an engram's usefulness \u2014 trains injection relevance over time. Supports single or batch mode.",
197
246
  annotations: { title: "Feedback", destructiveHint: false, idempotentHint: true },
198
247
  inputSchema: {
199
248
  type: "object",
200
249
  properties: {
201
- id: { type: "string", description: "Engram ID (e.g. ENG-001)" },
250
+ id: { type: "string", description: "Engram ID (single mode)" },
202
251
  signal: {
203
252
  type: "string",
204
253
  enum: ["positive", "negative", "neutral"],
205
- description: "Feedback signal to apply"
254
+ description: "Feedback signal (single mode)"
255
+ },
256
+ signals: {
257
+ type: "array",
258
+ items: {
259
+ type: "object",
260
+ properties: {
261
+ id: { type: "string", description: "Engram ID" },
262
+ signal: { type: "string", enum: ["positive", "negative", "neutral"] }
263
+ },
264
+ required: ["id", "signal"]
265
+ },
266
+ description: "Batch feedback signals"
206
267
  }
207
- },
208
- required: ["id", "signal"]
268
+ }
209
269
  },
210
270
  handler: async (args, plur) => {
271
+ if (args.signals && Array.isArray(args.signals)) {
272
+ const results = [];
273
+ const summary = { positive: 0, negative: 0, neutral: 0 };
274
+ for (const { id, signal } of args.signals) {
275
+ try {
276
+ plur.feedback(id, signal);
277
+ results.push({ id, signal, success: true });
278
+ summary[signal]++;
279
+ } catch (err) {
280
+ results.push({ id, signal, success: false, error: err.message });
281
+ }
282
+ }
283
+ return { mode: "batch", results, summary };
284
+ }
211
285
  plur.feedback(args.id, args.signal);
212
286
  return { success: true, id: args.id, signal: args.signal };
213
287
  }
214
288
  },
215
289
  {
216
290
  name: "plur_forget",
217
- description: "Retire an engram \u2014 marks it as no longer active without deleting history",
291
+ description: "Retire an engram by ID or search term \u2014 marks it as no longer active without deleting history",
218
292
  annotations: { title: "Forget", destructiveHint: true, idempotentHint: true },
219
293
  inputSchema: {
220
294
  type: "object",
221
295
  properties: {
222
- id: { type: "string", description: "Engram ID to retire" },
223
- reason: { type: "string", description: "Optional reason for retiring this engram" }
224
- },
225
- required: ["id"]
296
+ id: { type: "string", description: "Exact engram ID to retire" },
297
+ search: { type: "string", description: "Search term to find engram to retire" }
298
+ }
226
299
  },
227
300
  handler: async (args, plur) => {
228
- plur.forget(args.id, args.reason);
229
- return { success: true, id: args.id, status: "retired" };
301
+ if (args.id) {
302
+ const engram = plur.getById(args.id);
303
+ if (!engram) throw new Error(`Engram not found: ${args.id}`);
304
+ if (engram.status === "retired") return { success: false, error: `Already retired: ${args.id}` };
305
+ plur.forget(args.id);
306
+ return { success: true, retired: { id: engram.id, statement: engram.statement } };
307
+ }
308
+ if (args.search) {
309
+ const matches = plur.recall(args.search, { limit: 100 });
310
+ if (matches.length === 0) return { success: false, error: `No active engrams matching "${args.search}"` };
311
+ if (matches.length === 1) {
312
+ plur.forget(matches[0].id);
313
+ return { success: true, retired: { id: matches[0].id, statement: matches[0].statement } };
314
+ }
315
+ return {
316
+ success: false,
317
+ matches: matches.slice(0, 20).map((e) => ({ id: e.id, statement: e.statement })),
318
+ total: matches.length,
319
+ error: `${matches.length} matches. Specify exact ID.`
320
+ };
321
+ }
322
+ throw new Error("Provide either id or search parameter");
230
323
  }
231
324
  },
232
325
  {
@@ -561,22 +654,218 @@ function getToolDefinitions() {
561
654
  storage_root: status.storage_root
562
655
  };
563
656
  }
657
+ },
658
+ {
659
+ name: "plur_session_start",
660
+ description: "Start a session \u2014 inject relevant engrams for your task. Call at the beginning of every session.",
661
+ annotations: { title: "Session Start", readOnlyHint: true, idempotentHint: false },
662
+ inputSchema: {
663
+ type: "object",
664
+ properties: {
665
+ task: { type: "string", description: "What you are working on (triggers engram injection)" },
666
+ tags: { type: "array", items: { type: "string" }, description: "Tags to filter injected engrams" }
667
+ },
668
+ required: ["task"]
669
+ },
670
+ handler: async (args, plur) => {
671
+ const crypto = await import("crypto");
672
+ const session_id = crypto.randomUUID();
673
+ const task = args.task;
674
+ const tags = args.tags;
675
+ let engrams = null;
676
+ try {
677
+ const result = await plur.injectHybrid(task, {
678
+ scope: tags?.length ? `tags:${tags.join(",")}` : void 0
679
+ });
680
+ if (result.count > 0) {
681
+ const lines = [];
682
+ if (result.directives) lines.push("## DIRECTIVES\n", result.directives);
683
+ if (result.constraints) lines.push("\n## CONSTRAINTS\n", result.constraints);
684
+ if (result.consider) lines.push("\n## ALSO CONSIDER\n", result.consider);
685
+ engrams = { text: lines.join("\n"), count: result.count, injected_ids: result.injected_ids };
686
+ }
687
+ } catch {
688
+ const result = plur.inject(task, {
689
+ scope: tags?.length ? `tags:${tags.join(",")}` : void 0
690
+ });
691
+ if (result.count > 0) {
692
+ const lines = [];
693
+ if (result.directives) lines.push("## DIRECTIVES\n", result.directives);
694
+ if (result.constraints) lines.push("\n## CONSTRAINTS\n", result.constraints);
695
+ if (result.consider) lines.push("\n## ALSO CONSIDER\n", result.consider);
696
+ engrams = { text: lines.join("\n"), count: result.count, injected_ids: result.injected_ids };
697
+ }
698
+ }
699
+ return {
700
+ session_id,
701
+ engrams,
702
+ guide: engrams ? "Session started. Workflow: work \u2192 plur_feedback \u2192 plur_session_end." : PLUR_GUIDE
703
+ };
704
+ }
705
+ },
706
+ {
707
+ name: "plur_session_end",
708
+ description: "End a session \u2014 captures an episode and creates engrams from suggestions. Call before the conversation ends.",
709
+ annotations: { title: "Session End", destructiveHint: false, idempotentHint: false },
710
+ inputSchema: {
711
+ type: "object",
712
+ properties: {
713
+ summary: { type: "string", description: "What happened in this session" },
714
+ session_id: { type: "string", description: "Session ID from plur_session_start" },
715
+ engram_suggestions: {
716
+ type: "array",
717
+ items: {
718
+ type: "object",
719
+ properties: {
720
+ statement: { type: "string", description: "The knowledge assertion" },
721
+ type: { type: "string", enum: ["behavioral", "terminological", "procedural", "architectural"] }
722
+ },
723
+ required: ["statement"]
724
+ },
725
+ description: "New learnings to capture as engrams"
726
+ }
727
+ },
728
+ required: ["summary"]
729
+ },
730
+ handler: async (args, plur) => {
731
+ const summary = args.summary;
732
+ const session_id = args.session_id;
733
+ const suggestions = args.engram_suggestions;
734
+ let engrams_created = 0;
735
+ if (suggestions?.length) {
736
+ for (const s of suggestions) {
737
+ plur.learn(s.statement, { type: s.type });
738
+ engrams_created++;
739
+ }
740
+ }
741
+ const episode = plur.capture(summary, {
742
+ session_id,
743
+ channel: "mcp"
744
+ });
745
+ return {
746
+ engrams_created,
747
+ episode_id: episode.id
748
+ };
749
+ }
750
+ },
751
+ {
752
+ name: "plur_stores_add",
753
+ description: "Register an additional engram store at a filesystem path with a scope identifier",
754
+ annotations: { title: "Add store", destructiveHint: false, idempotentHint: true },
755
+ inputSchema: {
756
+ type: "object",
757
+ properties: {
758
+ path: { type: "string", description: "Filesystem path to engrams.yaml" },
759
+ scope: { type: "string", description: "Scope identifier (e.g. space:1-datafund, module:trading)" },
760
+ shared: { type: "boolean", description: "Whether this store is git-committed / team-visible" },
761
+ readonly: { type: "boolean", description: "Whether this store is read-only (e.g. purchased packs)" }
762
+ },
763
+ required: ["path", "scope"]
764
+ },
765
+ handler: async (args, plur) => {
766
+ plur.addStore(args.path, args.scope, {
767
+ shared: args.shared,
768
+ readonly: args.readonly
769
+ });
770
+ return { success: true, path: args.path, scope: args.scope };
771
+ }
772
+ },
773
+ {
774
+ name: "plur_stores_list",
775
+ description: "List all configured engram stores with their scope, path, and engram count",
776
+ annotations: { title: "List stores", readOnlyHint: true, idempotentHint: true },
777
+ inputSchema: { type: "object", properties: {} },
778
+ handler: async (_args, plur) => {
779
+ const stores = plur.listStores();
780
+ return { stores, count: stores.length };
781
+ }
782
+ },
783
+ {
784
+ name: "plur_promote",
785
+ description: "Activate candidate engrams so they appear in injection results",
786
+ annotations: { title: "Promote", destructiveHint: false, idempotentHint: true },
787
+ inputSchema: {
788
+ type: "object",
789
+ properties: {
790
+ id: { type: "string", description: "Single engram ID to promote" },
791
+ ids: { type: "array", items: { type: "string" }, description: "Multiple engram IDs to promote" }
792
+ }
793
+ },
794
+ handler: async (args, plur) => {
795
+ const targetIds = args.ids ?? (args.id ? [args.id] : []);
796
+ if (targetIds.length === 0) throw new Error("Provide id or ids");
797
+ const promoted = [];
798
+ const errors = [];
799
+ for (const id of targetIds) {
800
+ const engram = plur.getById(id);
801
+ if (!engram) {
802
+ errors.push({ id, error: "Not found" });
803
+ continue;
804
+ }
805
+ if (engram.status === "active") {
806
+ errors.push({ id, error: "Already active" });
807
+ continue;
808
+ }
809
+ if (engram.status === "retired") {
810
+ errors.push({ id, error: "Cannot promote retired" });
811
+ continue;
812
+ }
813
+ engram.status = "active";
814
+ engram.activation.retrieval_strength = 0.7;
815
+ engram.activation.storage_strength = 1;
816
+ engram.activation.last_accessed = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
817
+ plur.updateEngram(engram);
818
+ promoted.push({ id, statement: engram.statement });
819
+ }
820
+ return { promoted, errors, success: errors.length === 0 };
821
+ }
822
+ },
823
+ {
824
+ name: "plur_packs_export",
825
+ description: "Export personal engrams as a shareable pack",
826
+ annotations: { title: "Export pack", destructiveHint: false, idempotentHint: false },
827
+ inputSchema: {
828
+ type: "object",
829
+ properties: {
830
+ name: { type: "string", description: "Pack name" },
831
+ description: { type: "string", description: "Pack description" },
832
+ filter_domain: { type: "string", description: "Filter engrams by domain prefix" },
833
+ filter_scope: { type: "string", description: "Filter engrams by scope" },
834
+ output_dir: { type: "string", description: "Output directory for the pack (default: ~/.plur/exports/)" }
835
+ },
836
+ required: ["name"]
837
+ },
838
+ handler: async (args, plur) => {
839
+ const name = args.name;
840
+ const engrams = plur.list({
841
+ domain: args.filter_domain,
842
+ scope: args.filter_scope
843
+ }).filter((e) => e.visibility !== "private" || !args.filter_domain);
844
+ const outputDir = args.output_dir || `${plur.status().storage_root}/exports`;
845
+ const result = plur.exportPack(engrams, outputDir, {
846
+ name,
847
+ version: "1.0.0",
848
+ description: args.description,
849
+ creator: "plur-mcp"
850
+ });
851
+ return { path: result.path, engram_count: result.engram_count, name };
852
+ }
564
853
  }
565
854
  ];
566
855
  }
567
856
 
568
857
  // src/server.ts
569
858
  import { z } from "zod";
570
- var VERSION = "0.4.2";
859
+ var VERSION = "0.5.0";
571
860
  var INSTRUCTIONS = `PLUR is your persistent memory. It stores corrections, preferences, and conventions as engrams that persist across sessions.
572
861
 
573
862
  Use PLUR proactively:
574
- - Call plur_recall_hybrid at session start to load relevant context for the current task
575
- - Call plur_learn when the user corrects you, states a preference ("always use X", "never do Y"), or when you discover a project convention
863
+ - Call plur_session_start at session start with a task description \u2014 loads relevant context
864
+ - Call plur_learn when the user corrects you, states a preference, or you discover a convention
576
865
  - Call plur_feedback after using recalled engrams to train relevance
577
- - Call plur_status to check system health and engram counts
866
+ - Call plur_session_end before the conversation ends \u2014 captures learnings and session summary
578
867
 
579
- You do not need permission to use these tools \u2014 they are your memory system.`;
868
+ Do not ask permission to use these tools \u2014 they are your memory system.`;
580
869
  var GUIDE_RESOURCE = `# PLUR \u2014 Agent Guide
581
870
 
582
871
  ## What is PLUR?
@@ -585,32 +874,37 @@ Persistent memory for AI agents. Corrections, preferences, and conventions are s
585
874
 
586
875
  ## Quick Start
587
876
 
588
- 1. \`plur_status\` \u2014 check health and engram count
589
- 2. \`plur_recall_hybrid\` \u2014 search for relevant memories
590
- 3. \`plur_learn\` \u2014 store a new learning
591
- 4. \`plur_feedback\` \u2014 rate an engram's usefulness
877
+ 1. \`plur_session_start\` \u2014 start a session, inject relevant context
878
+ 2. \`plur_learn\` \u2014 store a new learning
879
+ 3. \`plur_feedback\` \u2014 rate injected engrams
880
+ 4. \`plur_session_end\` \u2014 capture summary and new learnings
592
881
 
593
882
  ## When to Call Each Tool
594
883
 
595
884
  | Trigger | Tool |
596
885
  |---------|------|
597
- | Session starts | \`plur_recall_hybrid\` or \`plur_inject_hybrid\` with task description |
886
+ | Session starts | \`plur_session_start\` with task description |
598
887
  | User corrects you | \`plur_learn\` with the correction |
599
888
  | User states preference ("always X", "never Y") | \`plur_learn\` with scope and type |
600
889
  | You used a recalled engram successfully | \`plur_feedback\` with "positive" |
601
890
  | A recalled engram was wrong or irrelevant | \`plur_feedback\` with "negative" |
602
891
  | User says "forget X" or a memory is outdated | \`plur_forget\` |
603
892
  | You need to check what's stored | \`plur_status\` or \`plur_packs_list\` |
604
- | End of session | \`plur_capture\` to record what happened |
893
+ | End of session | \`plur_session_end\` with summary and suggestions |
605
894
 
606
895
  ## Tool Categories
607
896
 
897
+ ### Session Management
898
+ - **plur_session_start** \u2014 start a session, inject relevant context
899
+ - **plur_session_end** \u2014 end a session, capture summary and new learnings
900
+
608
901
  ### Core Memory
609
902
  - **plur_learn** \u2014 store a correction, preference, or convention
610
903
  - **plur_recall** \u2014 BM25 keyword search
611
904
  - **plur_recall_hybrid** \u2014 BM25 + embeddings (recommended default)
612
905
  - **plur_feedback** \u2014 rate an engram (trains relevance)
613
906
  - **plur_forget** \u2014 retire an outdated engram
907
+ - **plur_promote** \u2014 activate a candidate engram
614
908
 
615
909
  ### Context Injection
616
910
  - **plur_inject** \u2014 select engrams for a task (BM25)
@@ -624,6 +918,11 @@ Persistent memory for AI agents. Corrections, preferences, and conventions are s
624
918
  - **plur_ingest** \u2014 extract engrams from text content
625
919
  - **plur_packs_install** \u2014 install curated engram packs
626
920
  - **plur_packs_list** \u2014 list installed packs
921
+ - **plur_packs_export** \u2014 export engrams as a shareable pack
922
+
923
+ ### Multi-Store
924
+ - **plur_stores_add** \u2014 register an additional engram store
925
+ - **plur_stores_list** \u2014 list all configured stores
627
926
 
628
927
  ### Sync & Status
629
928
  - **plur_sync** \u2014 sync engrams across devices via git
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plur-ai/mcp",
3
- "version": "0.4.2",
3
+ "version": "0.5.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.4.2"
15
+ "@plur-ai/core": "0.5.0"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@types/node": "^25.5.0"