@plur-ai/mcp 0.8.0 → 0.8.3
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 +13 -27
- package/dist/{server-JG4LLIW5.js → server-YEWTBXTD.js} +273 -28
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
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.8.
|
|
7
|
+
var VERSION = "0.8.3";
|
|
8
8
|
var HELP = `plur-mcp v${VERSION} \u2014 persistent memory for AI agents
|
|
9
9
|
|
|
10
10
|
Usage:
|
|
@@ -56,9 +56,15 @@ var CLAUDE_MD_SECTION = `## PLUR Memory
|
|
|
56
56
|
|
|
57
57
|
You have persistent memory via PLUR. Corrections, preferences, and conventions persist across sessions as engrams.
|
|
58
58
|
|
|
59
|
+
### Architecture
|
|
60
|
+
|
|
61
|
+
PLUR is installed **globally** \u2014 one MCP server, one engram store (\`~/.plur/\`), available in every project. You do NOT need per-project installation. Multi-project scoping uses \`domain\` and \`scope\` fields on engrams, not separate stores.
|
|
62
|
+
|
|
63
|
+
Hooks inject engrams automatically on every first message \u2014 you do not need to call \`plur_session_start\` manually (though you can for explicit session tracking).
|
|
64
|
+
|
|
59
65
|
### Session Workflow
|
|
60
66
|
|
|
61
|
-
1. **
|
|
67
|
+
1. **Automatic**: Hooks inject relevant engrams on first message \u2014 no action needed
|
|
62
68
|
2. **Learn**: When corrected or discovering something new, call \`plur_learn\` immediately
|
|
63
69
|
3. **Recall**: Before answering factual questions, call \`plur_recall_hybrid\` \u2014 check memory first
|
|
64
70
|
4. **Feedback**: Rate injected engrams with \`plur_feedback\` (positive/negative) \u2014 trains relevance
|
|
@@ -66,36 +72,12 @@ You have persistent memory via PLUR. Corrections, preferences, and conventions p
|
|
|
66
72
|
|
|
67
73
|
Do not ask permission to use these tools \u2014 they are your memory system.
|
|
68
74
|
|
|
69
|
-
### When to check memory
|
|
70
|
-
|
|
71
|
-
Before reaching for web search, file reads, or guessing \u2014 apply this priority:
|
|
72
|
-
1. Is the answer already in engrams? \u2192 \`plur_recall_hybrid\`
|
|
73
|
-
2. Is the answer in the local filesystem? \u2192 Read/Grep/Glob
|
|
74
|
-
3. Is the answer derivable from context already loaded? \u2192 Just answer
|
|
75
|
-
4. Only if 1-3 fail \u2192 Use external tools
|
|
76
|
-
|
|
77
|
-
| Domain | When to recall |
|
|
78
|
-
|--------|----------------|
|
|
79
|
-
| Decisions | Past design choices, architecture rationale |
|
|
80
|
-
| Corrections | API quirks, bugs, wrong assumptions |
|
|
81
|
-
| Preferences | Formatting, tone, workflow, tool choices |
|
|
82
|
-
| Conventions | Tag formats, file routing, naming rules |
|
|
83
|
-
| Infrastructure | Server IPs, SSH configs, deployment targets |
|
|
84
|
-
|
|
85
75
|
### When corrected
|
|
86
76
|
|
|
87
77
|
When the user corrects you ("no, use X not Y", "that's wrong"):
|
|
88
78
|
1. Call \`plur_learn\` immediately \u2014 before continuing the task
|
|
89
79
|
2. Call \`plur_feedback\` with negative signal on the wrong engram if one was injected
|
|
90
80
|
3. Then continue with the corrected approach
|
|
91
|
-
|
|
92
|
-
### Verification
|
|
93
|
-
|
|
94
|
-
When recalling facts that will drive actions:
|
|
95
|
-
1. State the recalled fact explicitly before acting on it
|
|
96
|
-
2. Include the engram ID or search that produced it
|
|
97
|
-
3. If no engram matches, say so and verify from the filesystem
|
|
98
|
-
4. Never interpolate between two engrams to produce a "probably correct" composite
|
|
99
81
|
`;
|
|
100
82
|
function installClaudeMd() {
|
|
101
83
|
const marker = "## PLUR Memory";
|
|
@@ -198,6 +180,10 @@ async function runInit() {
|
|
|
198
180
|
results.push(`CLAUDE.md: ${claudeMdStatus}`);
|
|
199
181
|
process.stdout.write(`PLUR initialized.
|
|
200
182
|
|
|
183
|
+
Architecture: PLUR is a global tool \u2014 one MCP server, one engram
|
|
184
|
+
store (~/.plur/), available in every project. Multi-project scoping
|
|
185
|
+
uses domain/scope fields on engrams, not separate installations.
|
|
186
|
+
|
|
201
187
|
${results.join("\n ")}
|
|
202
188
|
|
|
203
189
|
`);
|
|
@@ -226,7 +212,7 @@ if (arg === "init") {
|
|
|
226
212
|
process.exit(0);
|
|
227
213
|
}
|
|
228
214
|
if (arg === "serve" || arg === void 0) {
|
|
229
|
-
const { runStdio } = await import("./server-
|
|
215
|
+
const { runStdio } = await import("./server-YEWTBXTD.js");
|
|
230
216
|
runStdio().catch((err) => {
|
|
231
217
|
console.error("Failed to start PLUR MCP server:", err);
|
|
232
218
|
process.exit(1);
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
import { Plur as Plur2, checkForUpdate } from "@plur-ai/core";
|
|
15
15
|
|
|
16
16
|
// src/tools.ts
|
|
17
|
-
import { extractMetaEngrams, validateMetaEngram, confidenceBand } from "@plur-ai/core";
|
|
17
|
+
import { extractMetaEngrams, validateMetaEngram, confidenceBand, generateProfile, getProfileForInjection, selectModelForOperation } from "@plur-ai/core";
|
|
18
18
|
function makeHttpLlm(baseUrl, apiKey, model = "gpt-4o-mini") {
|
|
19
19
|
return async (prompt) => {
|
|
20
20
|
const response = await fetch(`${baseUrl.replace(/\/$/, "")}/chat/completions`, {
|
|
@@ -65,6 +65,13 @@ var PLUR_GUIDE = `## PLUR Quick Start
|
|
|
65
65
|
- **plur_learn** \u2014 record corrections, preferences, patterns (CALL THIS OFTEN)
|
|
66
66
|
- **plur_recall_hybrid** \u2014 search engrams by topic
|
|
67
67
|
- **plur_forget** \u2014 retire an outdated engram`;
|
|
68
|
+
function getLlmFunction() {
|
|
69
|
+
const openaiKey = process.env.OPENAI_API_KEY;
|
|
70
|
+
const openrouterKey = process.env.OPENROUTER_API_KEY;
|
|
71
|
+
if (openrouterKey) return makeHttpLlm("https://openrouter.ai/api/v1", openrouterKey, "openai/gpt-4o-mini");
|
|
72
|
+
if (openaiKey) return makeHttpLlm("https://api.openai.com/v1", openaiKey, "gpt-4o-mini");
|
|
73
|
+
return void 0;
|
|
74
|
+
}
|
|
68
75
|
function getToolDefinitions() {
|
|
69
76
|
return [
|
|
70
77
|
{
|
|
@@ -108,12 +115,17 @@ function getToolDefinitions() {
|
|
|
108
115
|
description: "Dual coding for richer encoding"
|
|
109
116
|
},
|
|
110
117
|
abstract: { type: "string", description: "Abstract engram ID this was derived from" },
|
|
111
|
-
derived_from: { type: "string", description: "Source engram ID this was derived from" }
|
|
118
|
+
derived_from: { type: "string", description: "Source engram ID this was derived from" },
|
|
119
|
+
commitment: { type: "string", enum: ["exploring", "leaning", "decided", "locked"], description: "Commitment level (default: leaning)" },
|
|
120
|
+
locked_reason: { type: "string", description: "Reason for locking (when commitment=locked)" },
|
|
121
|
+
memory_class: { type: "string", enum: ["semantic", "episodic", "procedural", "metacognitive"], description: "Memory classification (auto-set from type if omitted)" },
|
|
122
|
+
session_episode_id: { type: "string", description: "Link to current session episode for episodic anchoring" }
|
|
112
123
|
},
|
|
113
124
|
required: ["statement"]
|
|
114
125
|
},
|
|
115
126
|
handler: async (args, plur) => {
|
|
116
|
-
const
|
|
127
|
+
const llm = getLlmFunction();
|
|
128
|
+
const context = {
|
|
117
129
|
type: args.type,
|
|
118
130
|
scope: args.scope,
|
|
119
131
|
domain: args.domain,
|
|
@@ -124,9 +136,28 @@ function getToolDefinitions() {
|
|
|
124
136
|
knowledge_anchors: args.knowledge_anchors,
|
|
125
137
|
dual_coding: args.dual_coding,
|
|
126
138
|
abstract: args.abstract,
|
|
127
|
-
derived_from: args.derived_from
|
|
128
|
-
|
|
129
|
-
|
|
139
|
+
derived_from: args.derived_from,
|
|
140
|
+
commitment: args.commitment,
|
|
141
|
+
locked_reason: args.locked_reason,
|
|
142
|
+
memory_class: args.memory_class,
|
|
143
|
+
session_episode_id: args.session_episode_id,
|
|
144
|
+
llm
|
|
145
|
+
};
|
|
146
|
+
try {
|
|
147
|
+
const result = await plur.learnAsync(args.statement, context);
|
|
148
|
+
return {
|
|
149
|
+
id: result.engram.id,
|
|
150
|
+
statement: result.engram.statement,
|
|
151
|
+
scope: result.engram.scope,
|
|
152
|
+
type: result.engram.type,
|
|
153
|
+
decision: result.decision,
|
|
154
|
+
existing_id: result.existing_id,
|
|
155
|
+
tensions: result.tensions
|
|
156
|
+
};
|
|
157
|
+
} catch {
|
|
158
|
+
const engram = plur.learn(args.statement, context);
|
|
159
|
+
return { id: engram.id, statement: engram.statement, scope: engram.scope, type: engram.type, decision: "ADD" };
|
|
160
|
+
}
|
|
130
161
|
}
|
|
131
162
|
},
|
|
132
163
|
{
|
|
@@ -140,7 +171,9 @@ function getToolDefinitions() {
|
|
|
140
171
|
scope: { type: "string", description: "Filter by scope (also includes global)" },
|
|
141
172
|
domain: { type: "string", description: "Filter by domain prefix" },
|
|
142
173
|
limit: { type: "number", description: "Max results to return (default 20)" },
|
|
143
|
-
min_strength: { type: "number", description: "Minimum retrieval strength (0-1)" }
|
|
174
|
+
min_strength: { type: "number", description: "Minimum retrieval strength (0-1)" },
|
|
175
|
+
budget: { type: "object", description: "Budget constraints for sub-agents", properties: { max_tokens: { type: "number" }, max_results: { type: "number" } } },
|
|
176
|
+
caller_session_id: { type: "string", description: "Caller session ID for budget enforcement" }
|
|
144
177
|
},
|
|
145
178
|
required: ["query"]
|
|
146
179
|
},
|
|
@@ -175,27 +208,62 @@ function getToolDefinitions() {
|
|
|
175
208
|
scope: { type: "string", description: "Filter by scope (also includes global)" },
|
|
176
209
|
domain: { type: "string", description: "Filter by domain prefix" },
|
|
177
210
|
limit: { type: "number", description: "Max results to return (default 20)" },
|
|
178
|
-
min_strength: { type: "number", description: "Minimum retrieval strength (0-1)" }
|
|
211
|
+
min_strength: { type: "number", description: "Minimum retrieval strength (0-1)" },
|
|
212
|
+
budget: { type: "object", description: "Budget constraints for sub-agents", properties: { max_tokens: { type: "number" }, max_results: { type: "number" }, ttl_seconds: { type: "number" } } },
|
|
213
|
+
caller_session_id: { type: "string", description: "Session ID of calling agent for budget enforcement" },
|
|
214
|
+
include_episodes: { type: "boolean", description: "If true, include linked episode summaries for each engram (SP2 episodic anchoring)" }
|
|
179
215
|
},
|
|
180
216
|
required: ["query"]
|
|
181
217
|
},
|
|
182
218
|
handler: async (args, plur) => {
|
|
219
|
+
const budget = args.budget;
|
|
220
|
+
const effectiveLimit = budget?.max_results ?? args.limit ?? 20;
|
|
183
221
|
const results = await plur.recallHybrid(args.query, {
|
|
184
222
|
scope: args.scope,
|
|
185
223
|
domain: args.domain,
|
|
186
|
-
limit:
|
|
224
|
+
limit: effectiveLimit,
|
|
187
225
|
min_strength: args.min_strength
|
|
188
226
|
});
|
|
227
|
+
let truncated = false;
|
|
228
|
+
let boundedResults = results;
|
|
229
|
+
if (budget?.max_results && results.length > budget.max_results) {
|
|
230
|
+
boundedResults = results.slice(0, budget.max_results);
|
|
231
|
+
truncated = true;
|
|
232
|
+
}
|
|
233
|
+
if (budget?.max_tokens) {
|
|
234
|
+
let tokenCount = 0;
|
|
235
|
+
const withinBudget = [];
|
|
236
|
+
for (const e of boundedResults) {
|
|
237
|
+
const tokens = Math.ceil(e.statement.length / 4) + 20;
|
|
238
|
+
if (tokenCount + tokens > budget.max_tokens) {
|
|
239
|
+
truncated = true;
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
withinBudget.push(e);
|
|
243
|
+
tokenCount += tokens;
|
|
244
|
+
}
|
|
245
|
+
boundedResults = withinBudget;
|
|
246
|
+
}
|
|
247
|
+
const includeEpisodes = args.include_episodes === true;
|
|
189
248
|
return {
|
|
190
|
-
results:
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
249
|
+
results: boundedResults.map((e) => {
|
|
250
|
+
const raw = e;
|
|
251
|
+
const base = {
|
|
252
|
+
id: e.id,
|
|
253
|
+
statement: e.statement,
|
|
254
|
+
type: e.type,
|
|
255
|
+
scope: e.scope,
|
|
256
|
+
domain: e.domain,
|
|
257
|
+
retrieval_strength: e.activation.retrieval_strength
|
|
258
|
+
};
|
|
259
|
+
if (includeEpisodes && raw.episode_ids?.length > 0) {
|
|
260
|
+
const episodes = plur.timeline({ search: "" });
|
|
261
|
+
base.episodes = episodes.filter((ep) => raw.episode_ids.includes(ep.id)).map((ep) => ({ id: ep.id, summary: ep.summary, timestamp: ep.timestamp }));
|
|
262
|
+
}
|
|
263
|
+
return base;
|
|
264
|
+
}),
|
|
265
|
+
count: boundedResults.length,
|
|
266
|
+
truncated,
|
|
199
267
|
mode: "hybrid"
|
|
200
268
|
};
|
|
201
269
|
}
|
|
@@ -735,7 +803,10 @@ function getToolDefinitions() {
|
|
|
735
803
|
engram_count: status.engram_count,
|
|
736
804
|
episode_count: status.episode_count,
|
|
737
805
|
pack_count: status.pack_count,
|
|
738
|
-
storage_root: status.storage_root
|
|
806
|
+
storage_root: status.storage_root,
|
|
807
|
+
locked_count: status.locked_count,
|
|
808
|
+
tension_count: status.tension_count,
|
|
809
|
+
versioned_engram_count: status.versioned_engram_count ?? 0
|
|
739
810
|
};
|
|
740
811
|
}
|
|
741
812
|
},
|
|
@@ -937,6 +1008,147 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
937
1008
|
return { promoted, errors, success: errors.length === 0 };
|
|
938
1009
|
}
|
|
939
1010
|
},
|
|
1011
|
+
{
|
|
1012
|
+
name: "plur_tensions",
|
|
1013
|
+
description: "List engram pairs that have conflicting knowledge \u2014 shows tensions in your memory that may need resolution",
|
|
1014
|
+
annotations: { title: "Tensions", readOnlyHint: true, idempotentHint: true },
|
|
1015
|
+
inputSchema: {
|
|
1016
|
+
type: "object",
|
|
1017
|
+
properties: {
|
|
1018
|
+
scope: { type: "string", description: "Filter by scope" },
|
|
1019
|
+
domain: { type: "string", description: "Filter by domain prefix" }
|
|
1020
|
+
}
|
|
1021
|
+
},
|
|
1022
|
+
handler: async (args, plur) => {
|
|
1023
|
+
const engrams = plur.list({
|
|
1024
|
+
scope: args.scope,
|
|
1025
|
+
domain: args.domain
|
|
1026
|
+
});
|
|
1027
|
+
const tensions = [];
|
|
1028
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1029
|
+
for (const engram of engrams) {
|
|
1030
|
+
if (!engram.relations?.conflicts?.length) continue;
|
|
1031
|
+
for (const conflictId of engram.relations.conflicts) {
|
|
1032
|
+
const pairKey = [engram.id, conflictId].sort().join(":");
|
|
1033
|
+
if (seen.has(pairKey)) continue;
|
|
1034
|
+
seen.add(pairKey);
|
|
1035
|
+
const other = engrams.find((e) => e.id === conflictId);
|
|
1036
|
+
if (!other) continue;
|
|
1037
|
+
tensions.push({
|
|
1038
|
+
engram_a: { id: engram.id, statement: engram.statement, type: engram.type },
|
|
1039
|
+
engram_b: { id: other.id, statement: other.statement, type: other.type },
|
|
1040
|
+
detected_at: engram.activation.last_accessed
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
return { tensions, count: tensions.length };
|
|
1045
|
+
}
|
|
1046
|
+
},
|
|
1047
|
+
{
|
|
1048
|
+
name: "plur_episode_to_engram",
|
|
1049
|
+
description: "Promote an episode to a persistent episodic engram \u2014 useful when a session event deserves long-term memory",
|
|
1050
|
+
annotations: { title: "Episode to Engram", destructiveHint: false, idempotentHint: false },
|
|
1051
|
+
inputSchema: {
|
|
1052
|
+
type: "object",
|
|
1053
|
+
properties: {
|
|
1054
|
+
episode_id: { type: "string", description: "Episode ID to promote (from plur_timeline)" },
|
|
1055
|
+
scope: { type: "string", description: "Scope for the new engram" },
|
|
1056
|
+
domain: { type: "string", description: "Domain tag for the new engram" },
|
|
1057
|
+
tags: { type: "array", items: { type: "string" }, description: "Tags for the new engram" }
|
|
1058
|
+
},
|
|
1059
|
+
required: ["episode_id"]
|
|
1060
|
+
},
|
|
1061
|
+
handler: async (args, plur) => {
|
|
1062
|
+
const engram = plur.episodeToEngram(args.episode_id, {
|
|
1063
|
+
scope: args.scope,
|
|
1064
|
+
domain: args.domain,
|
|
1065
|
+
tags: args.tags
|
|
1066
|
+
});
|
|
1067
|
+
return {
|
|
1068
|
+
id: engram.id,
|
|
1069
|
+
statement: engram.statement,
|
|
1070
|
+
memory_class: engram.knowledge_type?.memory_class,
|
|
1071
|
+
episode_ids: engram.episode_ids,
|
|
1072
|
+
source: engram.source
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
},
|
|
1076
|
+
{
|
|
1077
|
+
name: "plur_history",
|
|
1078
|
+
description: "View the event-sourced history of an engram or all recent history \u2014 shows creation, updates, feedback, and evolution events",
|
|
1079
|
+
annotations: { title: "History", readOnlyHint: true, idempotentHint: true },
|
|
1080
|
+
inputSchema: {
|
|
1081
|
+
type: "object",
|
|
1082
|
+
properties: {
|
|
1083
|
+
engram_id: { type: "string", description: "Filter history for a specific engram ID. If omitted, returns recent history across all engrams." },
|
|
1084
|
+
limit: { type: "number", description: "Max events to return (default 50)" }
|
|
1085
|
+
}
|
|
1086
|
+
},
|
|
1087
|
+
handler: async (args, plur) => {
|
|
1088
|
+
const engramId = args.engram_id;
|
|
1089
|
+
const limit = args.limit ?? 50;
|
|
1090
|
+
if (engramId) {
|
|
1091
|
+
const events = plur.getEngramHistory(engramId);
|
|
1092
|
+
return {
|
|
1093
|
+
engram_id: engramId,
|
|
1094
|
+
events: events.slice(-limit),
|
|
1095
|
+
total: events.length
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
const { listHistoryMonths, readHistory } = await import("@plur-ai/core");
|
|
1099
|
+
const status = plur.status();
|
|
1100
|
+
const months = listHistoryMonths(status.storage_root);
|
|
1101
|
+
const allEvents = [];
|
|
1102
|
+
for (const month of months.reverse()) {
|
|
1103
|
+
const events = readHistory(status.storage_root, month);
|
|
1104
|
+
allEvents.push(...events);
|
|
1105
|
+
if (allEvents.length >= limit) break;
|
|
1106
|
+
}
|
|
1107
|
+
return {
|
|
1108
|
+
events: allEvents.slice(-limit),
|
|
1109
|
+
total: allEvents.length
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
},
|
|
1113
|
+
{
|
|
1114
|
+
name: "plur_report_failure",
|
|
1115
|
+
description: "Report a failure for a procedural engram \u2014 triggers procedure evolution via LLM if configured. Only works on procedural engrams. Max 3 revisions per procedure per 24h.",
|
|
1116
|
+
annotations: { title: "Report Failure", destructiveHint: false, idempotentHint: false },
|
|
1117
|
+
inputSchema: {
|
|
1118
|
+
type: "object",
|
|
1119
|
+
properties: {
|
|
1120
|
+
engram_id: { type: "string", description: "ID of the procedural engram that failed" },
|
|
1121
|
+
failure_context: { type: "string", description: "Description of what went wrong when following this procedure" },
|
|
1122
|
+
llm_base_url: { type: "string", description: "OpenAI-compatible API base URL for procedure evolution" },
|
|
1123
|
+
llm_api_key: { type: "string", description: "API key for the LLM" },
|
|
1124
|
+
llm_model: { type: "string", description: "Model name (default: gpt-4o-mini)" }
|
|
1125
|
+
},
|
|
1126
|
+
required: ["engram_id", "failure_context"]
|
|
1127
|
+
},
|
|
1128
|
+
handler: async (args, plur) => {
|
|
1129
|
+
let llm;
|
|
1130
|
+
if (args.llm_base_url && args.llm_api_key) {
|
|
1131
|
+
llm = makeHttpLlm(
|
|
1132
|
+
args.llm_base_url,
|
|
1133
|
+
args.llm_api_key,
|
|
1134
|
+
args.llm_model
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
const result = await plur.reportFailure(
|
|
1138
|
+
args.engram_id,
|
|
1139
|
+
args.failure_context,
|
|
1140
|
+
llm
|
|
1141
|
+
);
|
|
1142
|
+
return {
|
|
1143
|
+
engram_id: result.engram.id,
|
|
1144
|
+
statement: result.engram.statement,
|
|
1145
|
+
evolved: result.evolved,
|
|
1146
|
+
engram_version: result.engram.engram_version ?? 1,
|
|
1147
|
+
failure_episode_id: result.episode.id,
|
|
1148
|
+
note: result.evolved ? "Procedure was improved based on the failure report" : "Failure logged but procedure was not rewritten (no LLM configured or LLM unavailable)"
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
},
|
|
940
1152
|
{
|
|
941
1153
|
name: "plur_packs_export",
|
|
942
1154
|
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.",
|
|
@@ -990,20 +1202,55 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
990
1202
|
name
|
|
991
1203
|
};
|
|
992
1204
|
}
|
|
1205
|
+
},
|
|
1206
|
+
{
|
|
1207
|
+
name: "plur_profile",
|
|
1208
|
+
description: "Generate or retrieve a cognitive profile \u2014 a narrative summary synthesized from stored engrams. Cached for 24h.",
|
|
1209
|
+
annotations: { title: "Cognitive profile", readOnlyHint: true, idempotentHint: true },
|
|
1210
|
+
inputSchema: {
|
|
1211
|
+
type: "object",
|
|
1212
|
+
properties: {
|
|
1213
|
+
scope: { type: "string", description: "Filter engrams by scope" },
|
|
1214
|
+
llm_base_url: { type: "string", description: "OpenAI-compatible API base URL" },
|
|
1215
|
+
llm_api_key: { type: "string", description: "API key for the LLM" },
|
|
1216
|
+
llm_model: { type: "string", description: "Model name" },
|
|
1217
|
+
force_regenerate: { type: "boolean", description: "Force regeneration (default false)" }
|
|
1218
|
+
}
|
|
1219
|
+
},
|
|
1220
|
+
handler: async (args, plur) => {
|
|
1221
|
+
const status = plur.status();
|
|
1222
|
+
const storagePath = status.storage_root;
|
|
1223
|
+
if (!args.force_regenerate) {
|
|
1224
|
+
const cached = getProfileForInjection(storagePath);
|
|
1225
|
+
if (cached) return { profile: cached, source: "cache" };
|
|
1226
|
+
}
|
|
1227
|
+
if (!args.llm_base_url || !args.llm_api_key) {
|
|
1228
|
+
const cached = getProfileForInjection(storagePath);
|
|
1229
|
+
if (cached) return { profile: cached, source: "stale_cache" };
|
|
1230
|
+
return { profile: null, error: "No cached profile. Provide llm_base_url and llm_api_key." };
|
|
1231
|
+
}
|
|
1232
|
+
const model = args.llm_model ?? selectModelForOperation("profile", status.config?.llm);
|
|
1233
|
+
const llm = makeHttpLlm(args.llm_base_url, args.llm_api_key, model);
|
|
1234
|
+
const engrams = plur.list({ scope: args.scope });
|
|
1235
|
+
const profile = await generateProfile(engrams, llm, storagePath, status.config?.profile?.cache_ttl_hours ?? 24);
|
|
1236
|
+
return { profile, source: "generated", engram_count: engrams.length, model };
|
|
1237
|
+
}
|
|
993
1238
|
}
|
|
994
1239
|
];
|
|
995
1240
|
}
|
|
996
1241
|
|
|
997
1242
|
// src/server.ts
|
|
998
1243
|
import { z } from "zod";
|
|
999
|
-
var VERSION = "0.8.
|
|
1244
|
+
var VERSION = "0.8.3";
|
|
1000
1245
|
var INSTRUCTIONS = `PLUR is your persistent memory. Corrections, preferences, and conventions persist across sessions as engrams.
|
|
1001
1246
|
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1247
|
+
PLUR is a GLOBAL tool \u2014 one MCP server, one engram store (~/.plur/), available in every project. Multi-project scoping uses domain/scope fields on engrams, not separate installations.
|
|
1248
|
+
|
|
1249
|
+
SESSION LIFECYCLE:
|
|
1250
|
+
- With hooks installed (plur init): engrams are injected automatically on first message. You do NOT need to call plur_session_start \u2014 it happens via hooks. Just call plur_session_end before the conversation ends.
|
|
1251
|
+
- Without hooks: call plur_session_start at the start, plur_session_end at the end.
|
|
1005
1252
|
|
|
1006
|
-
|
|
1253
|
+
DURING the session:
|
|
1007
1254
|
- When user corrects you ("no, use X not Y") \u2192 call plur_learn immediately
|
|
1008
1255
|
- When user states a preference ("always X", "never Y") \u2192 call plur_learn immediately
|
|
1009
1256
|
- When you discover a codebase convention or pattern \u2192 call plur_learn
|
|
@@ -1012,12 +1259,10 @@ OPTIONAL but improves quality:
|
|
|
1012
1259
|
- Call plur_feedback to rate which injected engrams helped (positive/negative)
|
|
1013
1260
|
- Call plur_recall_hybrid before answering factual questions \u2014 the answer may be in memory
|
|
1014
1261
|
|
|
1015
|
-
For combined search (engrams + files): call plur_recall_hybrid for memories, then datacore.search for journal/knowledge files.
|
|
1016
|
-
|
|
1017
1262
|
Do not ask permission to use these tools \u2014 they are your memory system.
|
|
1018
1263
|
|
|
1019
|
-
Setup: If this is a fresh install, suggest the user run: npx @plur-ai/
|
|
1020
|
-
This installs
|
|
1264
|
+
Setup: If this is a fresh install, suggest the user run: npx @plur-ai/mcp init
|
|
1265
|
+
This installs hooks for automatic injection + session management. One-time global setup.`;
|
|
1021
1266
|
var GUIDE_RESOURCE = `# PLUR \u2014 Agent Guide
|
|
1022
1267
|
|
|
1023
1268
|
## What is PLUR?
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plur-ai/mcp",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
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.8.
|
|
15
|
+
"@plur-ai/core": "0.8.3"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@types/node": "^25.5.0"
|