@plur-ai/mcp 0.2.3 → 0.2.4

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,403 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/server.ts
4
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
- import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
7
- import { Plur, checkForUpdate } from "@plur-ai/core";
8
-
9
- // src/tools.ts
10
- function getToolDefinitions() {
11
- return [
12
- {
13
- name: "plur.learn",
14
- description: "Create an engram \u2014 record a reusable learning, preference, or correction",
15
- inputSchema: {
16
- type: "object",
17
- properties: {
18
- statement: { type: "string", description: "The knowledge assertion to store" },
19
- type: {
20
- type: "string",
21
- enum: ["behavioral", "terminological", "procedural", "architectural"],
22
- description: "Category of the engram"
23
- },
24
- scope: { type: "string", description: "Namespace, e.g. global, project:myapp" },
25
- domain: { type: "string", description: "Domain tag, e.g. software.deployment" },
26
- source: { type: "string", description: "Origin of this knowledge" }
27
- },
28
- required: ["statement"]
29
- },
30
- handler: async (args, plur) => {
31
- const engram = plur.learn(args.statement, {
32
- type: args.type,
33
- scope: args.scope,
34
- domain: args.domain,
35
- source: args.source
36
- });
37
- return { id: engram.id, statement: engram.statement, scope: engram.scope, type: engram.type };
38
- }
39
- },
40
- {
41
- name: "plur.recall",
42
- description: "Query engrams by semantic similarity \u2014 retrieve relevant learned knowledge",
43
- inputSchema: {
44
- type: "object",
45
- properties: {
46
- query: { type: "string", description: "Search query to find relevant engrams" },
47
- scope: { type: "string", description: "Filter by scope (also includes global)" },
48
- domain: { type: "string", description: "Filter by domain prefix" },
49
- limit: { type: "number", description: "Max results to return (default 20)" },
50
- min_strength: { type: "number", description: "Minimum retrieval strength (0-1)" }
51
- },
52
- required: ["query"]
53
- },
54
- handler: async (args, plur) => {
55
- const results = plur.recall(args.query, {
56
- scope: args.scope,
57
- domain: args.domain,
58
- limit: args.limit,
59
- min_strength: args.min_strength
60
- });
61
- return {
62
- results: results.map((e) => ({
63
- id: e.id,
64
- statement: e.statement,
65
- type: e.type,
66
- scope: e.scope,
67
- domain: e.domain,
68
- retrieval_strength: e.activation.retrieval_strength
69
- })),
70
- count: results.length
71
- };
72
- }
73
- },
74
- {
75
- name: "plur.recall.hybrid",
76
- description: "Hybrid search \u2014 BM25 + local embeddings merged via Reciprocal Rank Fusion. No API calls, fully local. Best default for most use cases.",
77
- inputSchema: {
78
- type: "object",
79
- properties: {
80
- query: { type: "string", description: "Search query to find relevant engrams" },
81
- scope: { type: "string", description: "Filter by scope (also includes global)" },
82
- domain: { type: "string", description: "Filter by domain prefix" },
83
- limit: { type: "number", description: "Max results to return (default 20)" },
84
- min_strength: { type: "number", description: "Minimum retrieval strength (0-1)" }
85
- },
86
- required: ["query"]
87
- },
88
- handler: async (args, plur) => {
89
- const results = await plur.recallHybrid(args.query, {
90
- scope: args.scope,
91
- domain: args.domain,
92
- limit: args.limit,
93
- min_strength: args.min_strength
94
- });
95
- return {
96
- results: results.map((e) => ({
97
- id: e.id,
98
- statement: e.statement,
99
- type: e.type,
100
- scope: e.scope,
101
- domain: e.domain,
102
- retrieval_strength: e.activation.retrieval_strength
103
- })),
104
- count: results.length,
105
- mode: "hybrid"
106
- };
107
- }
108
- },
109
- {
110
- name: "plur.inject",
111
- description: "Get a scored context injection for a task \u2014 returns directives and considerations within token budget",
112
- inputSchema: {
113
- type: "object",
114
- properties: {
115
- task: { type: "string", description: "The task description to inject context for" },
116
- budget: { type: "number", description: "Token budget for injection (default 2000)" },
117
- scope: { type: "string", description: "Scope filter for engram selection" }
118
- },
119
- required: ["task"]
120
- },
121
- handler: async (args, plur) => {
122
- const result = plur.inject(args.task, {
123
- budget: args.budget,
124
- scope: args.scope
125
- });
126
- return {
127
- directives: result.directives,
128
- consider: result.consider,
129
- count: result.count,
130
- tokens_used: result.tokens_used
131
- };
132
- }
133
- },
134
- {
135
- name: "plur.feedback",
136
- description: "Rate an engram's usefulness \u2014 trains injection relevance over time",
137
- inputSchema: {
138
- type: "object",
139
- properties: {
140
- id: { type: "string", description: "Engram ID (e.g. ENG-001)" },
141
- signal: {
142
- type: "string",
143
- enum: ["positive", "negative", "neutral"],
144
- description: "Feedback signal to apply"
145
- }
146
- },
147
- required: ["id", "signal"]
148
- },
149
- handler: async (args, plur) => {
150
- plur.feedback(args.id, args.signal);
151
- return { success: true, id: args.id, signal: args.signal };
152
- }
153
- },
154
- {
155
- name: "plur.forget",
156
- description: "Retire an engram \u2014 marks it as no longer active without deleting history",
157
- inputSchema: {
158
- type: "object",
159
- properties: {
160
- id: { type: "string", description: "Engram ID to retire" },
161
- reason: { type: "string", description: "Optional reason for retiring this engram" }
162
- },
163
- required: ["id"]
164
- },
165
- handler: async (args, plur) => {
166
- plur.forget(args.id, args.reason);
167
- return { success: true, id: args.id, status: "retired" };
168
- }
169
- },
170
- {
171
- name: "plur.capture",
172
- description: "Append an episode to the episodic timeline \u2014 records what happened in a session",
173
- inputSchema: {
174
- type: "object",
175
- properties: {
176
- summary: { type: "string", description: "What happened or was accomplished" },
177
- agent: { type: "string", description: "Agent identifier capturing this episode" },
178
- channel: { type: "string", description: "Communication channel (e.g. claude-code, chat)" },
179
- session_id: { type: "string", description: "Session identifier for grouping episodes" },
180
- tags: {
181
- type: "array",
182
- items: { type: "string" },
183
- description: "Tags for categorizing the episode"
184
- }
185
- },
186
- required: ["summary"]
187
- },
188
- handler: async (args, plur) => {
189
- const episode = plur.capture(args.summary, {
190
- agent: args.agent,
191
- channel: args.channel,
192
- session_id: args.session_id,
193
- tags: args.tags
194
- });
195
- return { id: episode.id, summary: episode.summary, timestamp: episode.timestamp };
196
- }
197
- },
198
- {
199
- name: "plur.timeline",
200
- description: "Query the episodic timeline \u2014 retrieve past episodes filtered by time, agent, or search",
201
- inputSchema: {
202
- type: "object",
203
- properties: {
204
- since: { type: "string", description: "ISO date string \u2014 only episodes after this time" },
205
- until: { type: "string", description: "ISO date string \u2014 only episodes before this time" },
206
- agent: { type: "string", description: "Filter by agent identifier" },
207
- channel: { type: "string", description: "Filter by channel" },
208
- search: { type: "string", description: "Full-text search within episode summaries" }
209
- }
210
- },
211
- handler: async (args, plur) => {
212
- const query = {};
213
- if (args.since) query.since = new Date(args.since);
214
- if (args.until) query.until = new Date(args.until);
215
- if (args.agent) query.agent = args.agent;
216
- if (args.channel) query.channel = args.channel;
217
- if (args.search) query.search = args.search;
218
- const episodes = plur.timeline(Object.keys(query).length > 0 ? query : void 0);
219
- return {
220
- episodes: episodes.map((e) => ({
221
- id: e.id,
222
- summary: e.summary,
223
- timestamp: e.timestamp,
224
- agent: e.agent,
225
- channel: e.channel,
226
- tags: e.tags
227
- })),
228
- count: episodes.length
229
- };
230
- }
231
- },
232
- {
233
- name: "plur.ingest",
234
- description: "Extract engram candidates from content using pattern matching \u2014 optionally auto-save them",
235
- inputSchema: {
236
- type: "object",
237
- properties: {
238
- content: { type: "string", description: "Text content to extract learnings from" },
239
- source: { type: "string", description: "Source attribution for extracted engrams" },
240
- extract_only: {
241
- type: "boolean",
242
- description: "If true, return candidates without saving (default false \u2014 saves automatically)"
243
- },
244
- scope: { type: "string", description: "Scope to assign to saved engrams" },
245
- domain: { type: "string", description: "Domain to assign to saved engrams" }
246
- },
247
- required: ["content"]
248
- },
249
- handler: async (args, plur) => {
250
- const candidates = plur.ingest(args.content, {
251
- source: args.source,
252
- extract_only: args.extract_only,
253
- scope: args.scope,
254
- domain: args.domain
255
- });
256
- return {
257
- candidates: candidates.map((c) => ({
258
- statement: c.statement,
259
- type: c.type,
260
- source: c.source
261
- })),
262
- count: candidates.length,
263
- saved: !(args.extract_only ?? false)
264
- };
265
- }
266
- },
267
- {
268
- name: "plur.packs.install",
269
- description: "Install an engram pack from a directory path \u2014 adds curated engrams to the store",
270
- inputSchema: {
271
- type: "object",
272
- properties: {
273
- source: { type: "string", description: "Path to the pack directory to install" }
274
- },
275
- required: ["source"]
276
- },
277
- handler: async (args, plur) => {
278
- const result = plur.installPack(args.source);
279
- return { installed: result.installed, name: result.name, success: true };
280
- }
281
- },
282
- {
283
- name: "plur.packs.list",
284
- description: "List all installed engram packs",
285
- inputSchema: {
286
- type: "object",
287
- properties: {}
288
- },
289
- handler: async (_args, plur) => {
290
- const packs = plur.listPacks();
291
- return {
292
- packs: packs.map((p) => ({
293
- name: p.name,
294
- version: p.version,
295
- description: p.description,
296
- engram_count: p.engram_count
297
- })),
298
- count: packs.length
299
- };
300
- }
301
- },
302
- {
303
- name: "plur.sync",
304
- 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.",
305
- inputSchema: {
306
- type: "object",
307
- properties: {
308
- remote: {
309
- type: "string",
310
- description: "Git remote URL (e.g. git@github.com:user/plur-engrams.git). Only needed on first call to set up remote."
311
- }
312
- }
313
- },
314
- handler: async (args, plur) => {
315
- const result = plur.sync(args.remote);
316
- return result;
317
- }
318
- },
319
- {
320
- name: "plur.sync.status",
321
- description: "Check git sync status \u2014 whether repo is initialized, has remote, is dirty, ahead/behind counts",
322
- inputSchema: {
323
- type: "object",
324
- properties: {}
325
- },
326
- handler: async (_args, plur) => {
327
- return plur.syncStatus();
328
- }
329
- },
330
- {
331
- name: "plur.status",
332
- description: "Return system health \u2014 engram count, episode count, pack count, storage root",
333
- inputSchema: {
334
- type: "object",
335
- properties: {}
336
- },
337
- handler: async (_args, plur) => {
338
- const status = plur.status();
339
- return {
340
- engram_count: status.engram_count,
341
- episode_count: status.episode_count,
342
- pack_count: status.pack_count,
343
- storage_root: status.storage_root
344
- };
345
- }
346
- }
347
- ];
348
- }
349
-
350
- // src/server.ts
351
- var VERSION = "0.2.3";
352
- async function createServer(plur) {
353
- const instance = plur ?? new Plur();
354
- const tools = getToolDefinitions();
355
- checkForUpdate("@plur-ai/mcp", VERSION, (r) => {
356
- if (r.updateAvailable) {
357
- console.error(`[plur] Update available: ${r.current} \u2192 ${r.latest}. Run: npx @plur-ai/mcp@latest`);
358
- }
359
- });
360
- const server = new Server(
361
- { name: "plur-mcp", version: VERSION },
362
- { capabilities: { tools: {} } }
363
- );
364
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
365
- tools: tools.map((t) => ({
366
- name: t.name,
367
- description: t.description,
368
- inputSchema: t.inputSchema
369
- }))
370
- }));
371
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
372
- const tool = tools.find((t) => t.name === request.params.name);
373
- if (!tool) {
374
- return {
375
- content: [{ type: "text", text: `Unknown tool: ${request.params.name}` }],
376
- isError: true
377
- };
378
- }
379
- try {
380
- const result = await tool.handler(request.params.arguments ?? {}, instance);
381
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
382
- } catch (err) {
383
- return {
384
- content: [{ type: "text", text: `Error: ${err.message}` }],
385
- isError: true
386
- };
387
- }
388
- });
389
- return server;
390
- }
391
- async function runStdio() {
392
- const server = await createServer();
393
- const transport = new StdioServerTransport();
394
- await server.connect(transport);
395
- }
396
-
397
3
  // src/index.ts
398
- import { detectPlurStorage } from "@plur-ai/core";
399
- var VERSION2 = "0.2.3";
400
- var HELP = `plur-mcp v${VERSION2} \u2014 persistent memory for AI agents
4
+ var VERSION = "0.2.4";
5
+ var HELP = `plur-mcp v${VERSION} \u2014 persistent memory for AI agents
401
6
 
402
7
  Usage:
403
8
  plur-mcp Start the MCP server (stdio transport)
@@ -425,11 +30,12 @@ if (arg === "--help" || arg === "-h") {
425
30
  process.exit(0);
426
31
  }
427
32
  if (arg === "--version" || arg === "-v") {
428
- process.stdout.write(`${VERSION2}
33
+ process.stdout.write(`${VERSION}
429
34
  `);
430
35
  process.exit(0);
431
36
  }
432
37
  if (arg === "init") {
38
+ const { detectPlurStorage } = await import("@plur-ai/core");
433
39
  const paths = detectPlurStorage();
434
40
  process.stdout.write(`PLUR initialized.
435
41
 
@@ -463,6 +69,7 @@ if (arg === "init") {
463
69
  process.exit(0);
464
70
  }
465
71
  if (arg === "serve" || arg === void 0) {
72
+ const { runStdio } = await import("./server-ZKOBQAZR.js");
466
73
  runStdio().catch((err) => {
467
74
  console.error("Failed to start PLUR MCP server:", err);
468
75
  process.exit(1);
@@ -0,0 +1,397 @@
1
+ // src/server.ts
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
5
+ import { Plur, checkForUpdate } from "@plur-ai/core";
6
+
7
+ // src/tools.ts
8
+ function getToolDefinitions() {
9
+ return [
10
+ {
11
+ name: "plur.learn",
12
+ description: "Create an engram \u2014 record a reusable learning, preference, or correction",
13
+ inputSchema: {
14
+ type: "object",
15
+ properties: {
16
+ statement: { type: "string", description: "The knowledge assertion to store" },
17
+ type: {
18
+ type: "string",
19
+ enum: ["behavioral", "terminological", "procedural", "architectural"],
20
+ description: "Category of the engram"
21
+ },
22
+ scope: { type: "string", description: "Namespace, e.g. global, project:myapp" },
23
+ domain: { type: "string", description: "Domain tag, e.g. software.deployment" },
24
+ source: { type: "string", description: "Origin of this knowledge" }
25
+ },
26
+ required: ["statement"]
27
+ },
28
+ handler: async (args, plur) => {
29
+ const engram = plur.learn(args.statement, {
30
+ type: args.type,
31
+ scope: args.scope,
32
+ domain: args.domain,
33
+ source: args.source
34
+ });
35
+ return { id: engram.id, statement: engram.statement, scope: engram.scope, type: engram.type };
36
+ }
37
+ },
38
+ {
39
+ name: "plur.recall",
40
+ description: "Query engrams by semantic similarity \u2014 retrieve relevant learned knowledge",
41
+ inputSchema: {
42
+ type: "object",
43
+ properties: {
44
+ query: { type: "string", description: "Search query to find relevant engrams" },
45
+ scope: { type: "string", description: "Filter by scope (also includes global)" },
46
+ domain: { type: "string", description: "Filter by domain prefix" },
47
+ limit: { type: "number", description: "Max results to return (default 20)" },
48
+ min_strength: { type: "number", description: "Minimum retrieval strength (0-1)" }
49
+ },
50
+ required: ["query"]
51
+ },
52
+ handler: async (args, plur) => {
53
+ const results = plur.recall(args.query, {
54
+ scope: args.scope,
55
+ domain: args.domain,
56
+ limit: args.limit,
57
+ min_strength: args.min_strength
58
+ });
59
+ return {
60
+ results: results.map((e) => ({
61
+ id: e.id,
62
+ statement: e.statement,
63
+ type: e.type,
64
+ scope: e.scope,
65
+ domain: e.domain,
66
+ retrieval_strength: e.activation.retrieval_strength
67
+ })),
68
+ count: results.length
69
+ };
70
+ }
71
+ },
72
+ {
73
+ name: "plur.recall.hybrid",
74
+ description: "Hybrid search \u2014 BM25 + local embeddings merged via Reciprocal Rank Fusion. No API calls, fully local. Best default for most use cases.",
75
+ inputSchema: {
76
+ type: "object",
77
+ properties: {
78
+ query: { type: "string", description: "Search query to find relevant engrams" },
79
+ scope: { type: "string", description: "Filter by scope (also includes global)" },
80
+ domain: { type: "string", description: "Filter by domain prefix" },
81
+ limit: { type: "number", description: "Max results to return (default 20)" },
82
+ min_strength: { type: "number", description: "Minimum retrieval strength (0-1)" }
83
+ },
84
+ required: ["query"]
85
+ },
86
+ handler: async (args, plur) => {
87
+ const results = await plur.recallHybrid(args.query, {
88
+ scope: args.scope,
89
+ domain: args.domain,
90
+ limit: args.limit,
91
+ min_strength: args.min_strength
92
+ });
93
+ return {
94
+ results: results.map((e) => ({
95
+ id: e.id,
96
+ statement: e.statement,
97
+ type: e.type,
98
+ scope: e.scope,
99
+ domain: e.domain,
100
+ retrieval_strength: e.activation.retrieval_strength
101
+ })),
102
+ count: results.length,
103
+ mode: "hybrid"
104
+ };
105
+ }
106
+ },
107
+ {
108
+ name: "plur.inject",
109
+ description: "Get a scored context injection for a task \u2014 returns directives and considerations within token budget",
110
+ inputSchema: {
111
+ type: "object",
112
+ properties: {
113
+ task: { type: "string", description: "The task description to inject context for" },
114
+ budget: { type: "number", description: "Token budget for injection (default 2000)" },
115
+ scope: { type: "string", description: "Scope filter for engram selection" }
116
+ },
117
+ required: ["task"]
118
+ },
119
+ handler: async (args, plur) => {
120
+ const result = plur.inject(args.task, {
121
+ budget: args.budget,
122
+ scope: args.scope
123
+ });
124
+ return {
125
+ directives: result.directives,
126
+ consider: result.consider,
127
+ count: result.count,
128
+ tokens_used: result.tokens_used
129
+ };
130
+ }
131
+ },
132
+ {
133
+ name: "plur.feedback",
134
+ description: "Rate an engram's usefulness \u2014 trains injection relevance over time",
135
+ inputSchema: {
136
+ type: "object",
137
+ properties: {
138
+ id: { type: "string", description: "Engram ID (e.g. ENG-001)" },
139
+ signal: {
140
+ type: "string",
141
+ enum: ["positive", "negative", "neutral"],
142
+ description: "Feedback signal to apply"
143
+ }
144
+ },
145
+ required: ["id", "signal"]
146
+ },
147
+ handler: async (args, plur) => {
148
+ plur.feedback(args.id, args.signal);
149
+ return { success: true, id: args.id, signal: args.signal };
150
+ }
151
+ },
152
+ {
153
+ name: "plur.forget",
154
+ description: "Retire an engram \u2014 marks it as no longer active without deleting history",
155
+ inputSchema: {
156
+ type: "object",
157
+ properties: {
158
+ id: { type: "string", description: "Engram ID to retire" },
159
+ reason: { type: "string", description: "Optional reason for retiring this engram" }
160
+ },
161
+ required: ["id"]
162
+ },
163
+ handler: async (args, plur) => {
164
+ plur.forget(args.id, args.reason);
165
+ return { success: true, id: args.id, status: "retired" };
166
+ }
167
+ },
168
+ {
169
+ name: "plur.capture",
170
+ description: "Append an episode to the episodic timeline \u2014 records what happened in a session",
171
+ inputSchema: {
172
+ type: "object",
173
+ properties: {
174
+ summary: { type: "string", description: "What happened or was accomplished" },
175
+ agent: { type: "string", description: "Agent identifier capturing this episode" },
176
+ channel: { type: "string", description: "Communication channel (e.g. claude-code, chat)" },
177
+ session_id: { type: "string", description: "Session identifier for grouping episodes" },
178
+ tags: {
179
+ type: "array",
180
+ items: { type: "string" },
181
+ description: "Tags for categorizing the episode"
182
+ }
183
+ },
184
+ required: ["summary"]
185
+ },
186
+ handler: async (args, plur) => {
187
+ const episode = plur.capture(args.summary, {
188
+ agent: args.agent,
189
+ channel: args.channel,
190
+ session_id: args.session_id,
191
+ tags: args.tags
192
+ });
193
+ return { id: episode.id, summary: episode.summary, timestamp: episode.timestamp };
194
+ }
195
+ },
196
+ {
197
+ name: "plur.timeline",
198
+ description: "Query the episodic timeline \u2014 retrieve past episodes filtered by time, agent, or search",
199
+ inputSchema: {
200
+ type: "object",
201
+ properties: {
202
+ since: { type: "string", description: "ISO date string \u2014 only episodes after this time" },
203
+ until: { type: "string", description: "ISO date string \u2014 only episodes before this time" },
204
+ agent: { type: "string", description: "Filter by agent identifier" },
205
+ channel: { type: "string", description: "Filter by channel" },
206
+ search: { type: "string", description: "Full-text search within episode summaries" }
207
+ }
208
+ },
209
+ handler: async (args, plur) => {
210
+ const query = {};
211
+ if (args.since) query.since = new Date(args.since);
212
+ if (args.until) query.until = new Date(args.until);
213
+ if (args.agent) query.agent = args.agent;
214
+ if (args.channel) query.channel = args.channel;
215
+ if (args.search) query.search = args.search;
216
+ const episodes = plur.timeline(Object.keys(query).length > 0 ? query : void 0);
217
+ return {
218
+ episodes: episodes.map((e) => ({
219
+ id: e.id,
220
+ summary: e.summary,
221
+ timestamp: e.timestamp,
222
+ agent: e.agent,
223
+ channel: e.channel,
224
+ tags: e.tags
225
+ })),
226
+ count: episodes.length
227
+ };
228
+ }
229
+ },
230
+ {
231
+ name: "plur.ingest",
232
+ description: "Extract engram candidates from content using pattern matching \u2014 optionally auto-save them",
233
+ inputSchema: {
234
+ type: "object",
235
+ properties: {
236
+ content: { type: "string", description: "Text content to extract learnings from" },
237
+ source: { type: "string", description: "Source attribution for extracted engrams" },
238
+ extract_only: {
239
+ type: "boolean",
240
+ description: "If true, return candidates without saving (default false \u2014 saves automatically)"
241
+ },
242
+ scope: { type: "string", description: "Scope to assign to saved engrams" },
243
+ domain: { type: "string", description: "Domain to assign to saved engrams" }
244
+ },
245
+ required: ["content"]
246
+ },
247
+ handler: async (args, plur) => {
248
+ const candidates = plur.ingest(args.content, {
249
+ source: args.source,
250
+ extract_only: args.extract_only,
251
+ scope: args.scope,
252
+ domain: args.domain
253
+ });
254
+ return {
255
+ candidates: candidates.map((c) => ({
256
+ statement: c.statement,
257
+ type: c.type,
258
+ source: c.source
259
+ })),
260
+ count: candidates.length,
261
+ saved: !(args.extract_only ?? false)
262
+ };
263
+ }
264
+ },
265
+ {
266
+ name: "plur.packs.install",
267
+ description: "Install an engram pack from a directory path \u2014 adds curated engrams to the store",
268
+ inputSchema: {
269
+ type: "object",
270
+ properties: {
271
+ source: { type: "string", description: "Path to the pack directory to install" }
272
+ },
273
+ required: ["source"]
274
+ },
275
+ handler: async (args, plur) => {
276
+ const result = plur.installPack(args.source);
277
+ return { installed: result.installed, name: result.name, success: true };
278
+ }
279
+ },
280
+ {
281
+ name: "plur.packs.list",
282
+ description: "List all installed engram packs",
283
+ inputSchema: {
284
+ type: "object",
285
+ properties: {}
286
+ },
287
+ handler: async (_args, plur) => {
288
+ const packs = plur.listPacks();
289
+ return {
290
+ packs: packs.map((p) => ({
291
+ name: p.name,
292
+ version: p.version,
293
+ description: p.description,
294
+ engram_count: p.engram_count
295
+ })),
296
+ count: packs.length
297
+ };
298
+ }
299
+ },
300
+ {
301
+ name: "plur.sync",
302
+ 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.",
303
+ inputSchema: {
304
+ type: "object",
305
+ properties: {
306
+ remote: {
307
+ type: "string",
308
+ description: "Git remote URL (e.g. git@github.com:user/plur-engrams.git). Only needed on first call to set up remote."
309
+ }
310
+ }
311
+ },
312
+ handler: async (args, plur) => {
313
+ const result = plur.sync(args.remote);
314
+ return result;
315
+ }
316
+ },
317
+ {
318
+ name: "plur.sync.status",
319
+ description: "Check git sync status \u2014 whether repo is initialized, has remote, is dirty, ahead/behind counts",
320
+ inputSchema: {
321
+ type: "object",
322
+ properties: {}
323
+ },
324
+ handler: async (_args, plur) => {
325
+ return plur.syncStatus();
326
+ }
327
+ },
328
+ {
329
+ name: "plur.status",
330
+ description: "Return system health \u2014 engram count, episode count, pack count, storage root",
331
+ inputSchema: {
332
+ type: "object",
333
+ properties: {}
334
+ },
335
+ handler: async (_args, plur) => {
336
+ const status = plur.status();
337
+ return {
338
+ engram_count: status.engram_count,
339
+ episode_count: status.episode_count,
340
+ pack_count: status.pack_count,
341
+ storage_root: status.storage_root
342
+ };
343
+ }
344
+ }
345
+ ];
346
+ }
347
+
348
+ // src/server.ts
349
+ var VERSION = "0.2.4";
350
+ async function createServer(plur) {
351
+ const instance = plur ?? new Plur();
352
+ const tools = getToolDefinitions();
353
+ checkForUpdate("@plur-ai/mcp", VERSION, (r) => {
354
+ if (r.updateAvailable) {
355
+ console.error(`[plur] Update available: ${r.current} \u2192 ${r.latest}. Run: npx @plur-ai/mcp@latest`);
356
+ }
357
+ });
358
+ const server = new Server(
359
+ { name: "plur-mcp", version: VERSION },
360
+ { capabilities: { tools: {} } }
361
+ );
362
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
363
+ tools: tools.map((t) => ({
364
+ name: t.name,
365
+ description: t.description,
366
+ inputSchema: t.inputSchema
367
+ }))
368
+ }));
369
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
370
+ const tool = tools.find((t) => t.name === request.params.name);
371
+ if (!tool) {
372
+ return {
373
+ content: [{ type: "text", text: `Unknown tool: ${request.params.name}` }],
374
+ isError: true
375
+ };
376
+ }
377
+ try {
378
+ const result = await tool.handler(request.params.arguments ?? {}, instance);
379
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
380
+ } catch (err) {
381
+ return {
382
+ content: [{ type: "text", text: `Error: ${err.message}` }],
383
+ isError: true
384
+ };
385
+ }
386
+ });
387
+ return server;
388
+ }
389
+ async function runStdio() {
390
+ const server = await createServer();
391
+ const transport = new StdioServerTransport();
392
+ await server.connect(transport);
393
+ }
394
+ export {
395
+ createServer,
396
+ runStdio
397
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plur-ai/mcp",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
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.3"
16
+ "@plur-ai/core": "0.2.4"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/node": "^25.5.0"