@plur-ai/mcp 0.2.9 → 0.3.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.2.8";
4
+ var VERSION = "0.2.9";
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-QXWHRZ4G.js");
78
+ const { runStdio } = await import("./server-I5WX7CVH.js");
79
79
  runStdio().catch((err) => {
80
80
  console.error("Failed to start PLUR MCP server:", err);
81
81
  process.exit(1);
@@ -1,15 +1,25 @@
1
1
  // src/server.ts
2
2
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
4
+ import {
5
+ ListToolsRequestSchema,
6
+ CallToolRequestSchema,
7
+ ListResourcesRequestSchema,
8
+ ReadResourceRequestSchema,
9
+ ListPromptsRequestSchema,
10
+ GetPromptRequestSchema,
11
+ ErrorCode,
12
+ McpError
13
+ } from "@modelcontextprotocol/sdk/types.js";
5
14
  import { Plur, checkForUpdate } from "@plur-ai/core";
6
15
 
7
16
  // src/tools.ts
8
17
  function getToolDefinitions() {
9
18
  return [
10
19
  {
11
- name: "plur.learn",
20
+ name: "plur_learn",
12
21
  description: "Create an engram \u2014 record a reusable learning, preference, or correction",
22
+ annotations: { title: "Learn", destructiveHint: false, idempotentHint: false },
13
23
  inputSchema: {
14
24
  type: "object",
15
25
  properties: {
@@ -36,8 +46,9 @@ function getToolDefinitions() {
36
46
  }
37
47
  },
38
48
  {
39
- name: "plur.recall",
40
- description: "Query engrams by semantic similarity \u2014 retrieve relevant learned knowledge",
49
+ name: "plur_recall",
50
+ description: "Query engrams by BM25 keyword matching \u2014 use plur_recall_hybrid for semantic similarity",
51
+ annotations: { title: "Recall (BM25)", readOnlyHint: true, idempotentHint: true },
41
52
  inputSchema: {
42
53
  type: "object",
43
54
  properties: {
@@ -70,8 +81,9 @@ function getToolDefinitions() {
70
81
  }
71
82
  },
72
83
  {
73
- name: "plur.recall.hybrid",
84
+ name: "plur_recall_hybrid",
74
85
  description: "Hybrid search \u2014 BM25 + local embeddings merged via Reciprocal Rank Fusion. No API calls, fully local. Best default for most use cases.",
86
+ annotations: { title: "Recall (hybrid)", readOnlyHint: true, idempotentHint: true },
75
87
  inputSchema: {
76
88
  type: "object",
77
89
  properties: {
@@ -105,8 +117,9 @@ function getToolDefinitions() {
105
117
  }
106
118
  },
107
119
  {
108
- name: "plur.inject",
120
+ name: "plur_inject",
109
121
  description: "Get a scored context injection for a task \u2014 returns directives and considerations within token budget",
122
+ annotations: { title: "Inject (BM25)", readOnlyHint: true, idempotentHint: true },
110
123
  inputSchema: {
111
124
  type: "object",
112
125
  properties: {
@@ -130,8 +143,9 @@ function getToolDefinitions() {
130
143
  }
131
144
  },
132
145
  {
133
- name: "plur.inject.hybrid",
146
+ name: "plur_inject_hybrid",
134
147
  description: "Hybrid injection \u2014 BM25 + embeddings for better context selection. Falls back to BM25 if embeddings unavailable. Best default for injection.",
148
+ annotations: { title: "Inject (hybrid)", readOnlyHint: true, idempotentHint: true },
135
149
  inputSchema: {
136
150
  type: "object",
137
151
  properties: {
@@ -156,8 +170,9 @@ function getToolDefinitions() {
156
170
  }
157
171
  },
158
172
  {
159
- name: "plur.feedback",
173
+ name: "plur_feedback",
160
174
  description: "Rate an engram's usefulness \u2014 trains injection relevance over time",
175
+ annotations: { title: "Feedback", destructiveHint: false, idempotentHint: true },
161
176
  inputSchema: {
162
177
  type: "object",
163
178
  properties: {
@@ -176,8 +191,9 @@ function getToolDefinitions() {
176
191
  }
177
192
  },
178
193
  {
179
- name: "plur.forget",
194
+ name: "plur_forget",
180
195
  description: "Retire an engram \u2014 marks it as no longer active without deleting history",
196
+ annotations: { title: "Forget", destructiveHint: true, idempotentHint: true },
181
197
  inputSchema: {
182
198
  type: "object",
183
199
  properties: {
@@ -192,8 +208,9 @@ function getToolDefinitions() {
192
208
  }
193
209
  },
194
210
  {
195
- name: "plur.capture",
211
+ name: "plur_capture",
196
212
  description: "Append an episode to the episodic timeline \u2014 records what happened in a session",
213
+ annotations: { title: "Capture episode", destructiveHint: false, idempotentHint: false },
197
214
  inputSchema: {
198
215
  type: "object",
199
216
  properties: {
@@ -220,8 +237,9 @@ function getToolDefinitions() {
220
237
  }
221
238
  },
222
239
  {
223
- name: "plur.timeline",
240
+ name: "plur_timeline",
224
241
  description: "Query the episodic timeline \u2014 retrieve past episodes filtered by time, agent, or search",
242
+ annotations: { title: "Timeline", readOnlyHint: true, idempotentHint: true },
225
243
  inputSchema: {
226
244
  type: "object",
227
245
  properties: {
@@ -254,8 +272,9 @@ function getToolDefinitions() {
254
272
  }
255
273
  },
256
274
  {
257
- name: "plur.ingest",
275
+ name: "plur_ingest",
258
276
  description: "Extract engram candidates from content using pattern matching \u2014 optionally auto-save them",
277
+ annotations: { title: "Ingest", destructiveHint: false, idempotentHint: false },
259
278
  inputSchema: {
260
279
  type: "object",
261
280
  properties: {
@@ -289,8 +308,9 @@ function getToolDefinitions() {
289
308
  }
290
309
  },
291
310
  {
292
- name: "plur.packs.install",
311
+ name: "plur_packs_install",
293
312
  description: "Install an engram pack from a directory path \u2014 adds curated engrams to the store",
313
+ annotations: { title: "Install pack", destructiveHint: false, idempotentHint: true },
294
314
  inputSchema: {
295
315
  type: "object",
296
316
  properties: {
@@ -304,8 +324,9 @@ function getToolDefinitions() {
304
324
  }
305
325
  },
306
326
  {
307
- name: "plur.packs.list",
327
+ name: "plur_packs_list",
308
328
  description: "List all installed engram packs",
329
+ annotations: { title: "List packs", readOnlyHint: true, idempotentHint: true },
309
330
  inputSchema: {
310
331
  type: "object",
311
332
  properties: {}
@@ -324,8 +345,9 @@ function getToolDefinitions() {
324
345
  }
325
346
  },
326
347
  {
327
- name: "plur.sync",
348
+ name: "plur_sync",
328
349
  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.",
350
+ annotations: { title: "Sync", openWorldHint: true, destructiveHint: false, idempotentHint: true },
329
351
  inputSchema: {
330
352
  type: "object",
331
353
  properties: {
@@ -341,8 +363,9 @@ function getToolDefinitions() {
341
363
  }
342
364
  },
343
365
  {
344
- name: "plur.sync.status",
366
+ name: "plur_sync_status",
345
367
  description: "Check git sync status \u2014 whether repo is initialized, has remote, is dirty, ahead/behind counts",
368
+ annotations: { title: "Sync status", readOnlyHint: true, idempotentHint: true },
346
369
  inputSchema: {
347
370
  type: "object",
348
371
  properties: {}
@@ -352,8 +375,9 @@ function getToolDefinitions() {
352
375
  }
353
376
  },
354
377
  {
355
- name: "plur.status",
378
+ name: "plur_status",
356
379
  description: "Return system health \u2014 engram count, episode count, pack count, storage root",
380
+ annotations: { title: "Status", readOnlyHint: true, idempotentHint: true },
357
381
  inputSchema: {
358
382
  type: "object",
359
383
  properties: {}
@@ -372,7 +396,88 @@ function getToolDefinitions() {
372
396
  }
373
397
 
374
398
  // src/server.ts
375
- var VERSION = "0.2.8";
399
+ import { z } from "zod";
400
+ var VERSION = "0.3.0";
401
+ var INSTRUCTIONS = `PLUR is your persistent memory. It stores corrections, preferences, and conventions as engrams that persist across sessions.
402
+
403
+ Use PLUR proactively:
404
+ - Call plur_recall_hybrid at session start to load relevant context for the current task
405
+ - Call plur_learn when the user corrects you, states a preference ("always use X", "never do Y"), or when you discover a project convention
406
+ - Call plur_feedback after using recalled engrams to train relevance
407
+ - Call plur_status to check system health and engram counts
408
+
409
+ You do not need permission to use these tools \u2014 they are your memory system.`;
410
+ var GUIDE_RESOURCE = `# PLUR \u2014 Agent Guide
411
+
412
+ ## What is PLUR?
413
+
414
+ Persistent memory for AI agents. Corrections, preferences, and conventions are stored as **engrams** \u2014 small assertions that strengthen with use and decay when irrelevant (ACT-R model). Storage is plain YAML on disk. Search is fully local (BM25 + embeddings). Zero API calls.
415
+
416
+ ## Quick Start
417
+
418
+ 1. \`plur_status\` \u2014 check health and engram count
419
+ 2. \`plur_recall_hybrid\` \u2014 search for relevant memories
420
+ 3. \`plur_learn\` \u2014 store a new learning
421
+ 4. \`plur_feedback\` \u2014 rate an engram's usefulness
422
+
423
+ ## When to Call Each Tool
424
+
425
+ | Trigger | Tool |
426
+ |---------|------|
427
+ | Session starts | \`plur_recall_hybrid\` or \`plur_inject_hybrid\` with task description |
428
+ | User corrects you | \`plur_learn\` with the correction |
429
+ | User states preference ("always X", "never Y") | \`plur_learn\` with scope and type |
430
+ | You used a recalled engram successfully | \`plur_feedback\` with "positive" |
431
+ | A recalled engram was wrong or irrelevant | \`plur_feedback\` with "negative" |
432
+ | User says "forget X" or a memory is outdated | \`plur_forget\` |
433
+ | You need to check what's stored | \`plur_status\` or \`plur_packs_list\` |
434
+ | End of session | \`plur_capture\` to record what happened |
435
+
436
+ ## Tool Categories
437
+
438
+ ### Core Memory
439
+ - **plur_learn** \u2014 store a correction, preference, or convention
440
+ - **plur_recall** \u2014 BM25 keyword search
441
+ - **plur_recall_hybrid** \u2014 BM25 + embeddings (recommended default)
442
+ - **plur_feedback** \u2014 rate an engram (trains relevance)
443
+ - **plur_forget** \u2014 retire an outdated engram
444
+
445
+ ### Context Injection
446
+ - **plur_inject** \u2014 select engrams for a task (BM25)
447
+ - **plur_inject_hybrid** \u2014 select engrams for a task (BM25 + embeddings, recommended)
448
+
449
+ ### Episodic Timeline
450
+ - **plur_capture** \u2014 record what happened in a session
451
+ - **plur_timeline** \u2014 query past episodes
452
+
453
+ ### Knowledge Management
454
+ - **plur_ingest** \u2014 extract engrams from text content
455
+ - **plur_packs_install** \u2014 install curated engram packs
456
+ - **plur_packs_list** \u2014 list installed packs
457
+
458
+ ### Sync & Status
459
+ - **plur_sync** \u2014 sync engrams across devices via git
460
+ - **plur_sync_status** \u2014 check sync state
461
+ - **plur_status** \u2014 system health
462
+
463
+ ## Scoping
464
+
465
+ Use \`scope\` to namespace engrams per project:
466
+ - \`scope: "global"\` \u2014 applies everywhere (default)
467
+ - \`scope: "project:my-app"\` \u2014 applies only to my-app
468
+ - Scoped recall automatically includes global engrams
469
+
470
+ ## Storage
471
+
472
+ \`\`\`
473
+ ~/.plur/
474
+ \u251C\u2500\u2500 engrams.yaml # learned knowledge
475
+ \u251C\u2500\u2500 episodes.yaml # session timeline
476
+ \u2514\u2500\u2500 config.yaml # settings
477
+ \`\`\`
478
+
479
+ Override with \`PLUR_PATH\` environment variable.
480
+ `;
376
481
  async function createServer(plur) {
377
482
  const instance = plur ?? new Plur();
378
483
  const tools = getToolDefinitions();
@@ -383,13 +488,22 @@ async function createServer(plur) {
383
488
  });
384
489
  const server = new Server(
385
490
  { name: "plur-mcp", version: VERSION },
386
- { capabilities: { tools: {} } }
491
+ {
492
+ capabilities: {
493
+ tools: {},
494
+ resources: {},
495
+ prompts: {},
496
+ logging: {}
497
+ },
498
+ instructions: INSTRUCTIONS
499
+ }
387
500
  );
388
501
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
389
502
  tools: tools.map((t) => ({
390
503
  name: t.name,
391
504
  description: t.description,
392
- inputSchema: t.inputSchema
505
+ inputSchema: t.inputSchema,
506
+ ...t.annotations && { annotations: t.annotations }
393
507
  }))
394
508
  }));
395
509
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
@@ -401,15 +515,146 @@ async function createServer(plur) {
401
515
  };
402
516
  }
403
517
  try {
404
- const result = await tool.handler(request.params.arguments ?? {}, instance);
518
+ const args = request.params.arguments ?? {};
519
+ const schema = tool.inputSchema;
520
+ if (schema?.properties) {
521
+ const shape = {};
522
+ for (const [key, prop] of Object.entries(schema.properties)) {
523
+ let field;
524
+ if (prop.type === "string") field = prop.enum ? z.enum(prop.enum) : z.string();
525
+ else if (prop.type === "number") field = z.number();
526
+ else if (prop.type === "boolean") field = z.boolean();
527
+ else if (prop.type === "array") field = z.array(z.unknown());
528
+ else field = z.unknown();
529
+ shape[key] = schema.required?.includes(key) ? field : field.optional();
530
+ }
531
+ const parsed = z.object(shape).passthrough().safeParse(args);
532
+ if (!parsed.success) {
533
+ return {
534
+ content: [{ type: "text", text: `Invalid arguments: ${parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ")}` }],
535
+ isError: true
536
+ };
537
+ }
538
+ }
539
+ const result = await tool.handler(args, instance);
405
540
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
406
541
  } catch (err) {
542
+ const message = err?.message ?? String(err);
543
+ server.sendLoggingMessage({ level: "error", data: `Tool ${request.params.name} failed: ${message}` });
407
544
  return {
408
- content: [{ type: "text", text: `Error: ${err.message}` }],
545
+ content: [{ type: "text", text: `Error: ${message}` }],
409
546
  isError: true
410
547
  };
411
548
  }
412
549
  });
550
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
551
+ resources: [
552
+ {
553
+ uri: "plur://guide",
554
+ name: "PLUR Agent Guide",
555
+ description: "Complete reference for all PLUR tools, when to use them, scoping, and storage",
556
+ mimeType: "text/markdown"
557
+ },
558
+ {
559
+ uri: "plur://status",
560
+ name: "PLUR Status",
561
+ description: "Live system health \u2014 engram count, episode count, pack count, storage path",
562
+ mimeType: "application/json"
563
+ }
564
+ ]
565
+ }));
566
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
567
+ const uri = request.params.uri;
568
+ if (uri === "plur://guide") {
569
+ return {
570
+ contents: [{
571
+ uri: "plur://guide",
572
+ mimeType: "text/markdown",
573
+ text: GUIDE_RESOURCE
574
+ }]
575
+ };
576
+ }
577
+ if (uri === "plur://status") {
578
+ const status = instance.status();
579
+ return {
580
+ contents: [{
581
+ uri: "plur://status",
582
+ mimeType: "application/json",
583
+ text: JSON.stringify({
584
+ engram_count: status.engram_count,
585
+ episode_count: status.episode_count,
586
+ pack_count: status.pack_count,
587
+ storage_root: status.storage_root,
588
+ version: VERSION
589
+ }, null, 2)
590
+ }]
591
+ };
592
+ }
593
+ throw new McpError(ErrorCode.InvalidRequest, `Unknown resource: ${uri}`);
594
+ });
595
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
596
+ prompts: [
597
+ {
598
+ name: "plur-getting-started",
599
+ description: "Step-by-step guide to set up and start using PLUR memory"
600
+ },
601
+ {
602
+ name: "plur-session-start",
603
+ description: "Load relevant context for a task \u2014 call at the start of each session",
604
+ arguments: [
605
+ { name: "task", description: "Brief description of the task or goal", required: true },
606
+ { name: "scope", description: "Project scope (e.g. project:my-app)", required: false }
607
+ ]
608
+ }
609
+ ]
610
+ }));
611
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
612
+ const name = request.params.name;
613
+ if (name === "plur-getting-started") {
614
+ const status = instance.status();
615
+ return {
616
+ description: "Get started with PLUR memory",
617
+ messages: [{
618
+ role: "user",
619
+ content: {
620
+ type: "text",
621
+ text: `I just set up PLUR. Here's my current status:
622
+
623
+ - Engrams stored: ${status.engram_count}
624
+ - Episodes recorded: ${status.episode_count}
625
+ - Packs installed: ${status.pack_count}
626
+ - Storage: ${status.storage_root}
627
+
628
+ ${status.engram_count === 0 ? `I have no memories yet. Help me get started by:
629
+ 1. Teaching me a coding preference or convention (I'll use plur_learn)
630
+ 2. Then recalling it to verify it works (I'll use plur_recall_hybrid)
631
+ 3. Rating the recall quality (I'll use plur_feedback)` : `I have ${status.engram_count} engrams stored. Try asking me something related to your project \u2014 I'll check my memory first.`}`
632
+ }
633
+ }]
634
+ };
635
+ }
636
+ if (name === "plur-session-start") {
637
+ const task = request.params.arguments?.task ?? "general work";
638
+ const scope = request.params.arguments?.scope;
639
+ return {
640
+ description: "Load relevant context for this session",
641
+ messages: [{
642
+ role: "user",
643
+ content: {
644
+ type: "text",
645
+ text: `Starting a new session. Task: ${task}${scope ? ` (scope: ${scope})` : ""}
646
+
647
+ Please:
648
+ 1. Call plur_recall_hybrid with query "${task}"${scope ? ` and scope "${scope}"` : ""} to load relevant memories
649
+ 2. Review the recalled engrams and apply any relevant conventions or preferences
650
+ 3. If any recalled engrams are helpful, call plur_feedback with "positive"
651
+ 4. If any are irrelevant, call plur_feedback with "negative"`
652
+ }
653
+ }]
654
+ };
655
+ }
656
+ throw new McpError(ErrorCode.InvalidRequest, `Unknown prompt: ${name}`);
657
+ });
413
658
  return server;
414
659
  }
415
660
  async function runStdio() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plur-ai/mcp",
3
- "version": "0.2.9",
3
+ "version": "0.3.0",
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.2.9"
16
+ "@plur-ai/core": "0.3.0"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/node": "^25.5.0"
@@ -38,9 +38,8 @@
38
38
  "author": "PLUR <info@plur.ai>",
39
39
  "exports": {
40
40
  ".": {
41
- "import": "./dist/index.js",
42
- "require": "./dist/index.js",
43
- "types": "./dist/index.d.ts"
41
+ "types": "./dist/index.d.ts",
42
+ "import": "./dist/index.js"
44
43
  }
45
44
  },
46
45
  "scripts": {