@plur-ai/mcp 0.20.1 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -1
- package/dist/{chunk-445S5QNU.js → chunk-O2NJFMNP.js} +1 -1
- package/dist/{chunk-SWQDW42Z.js → chunk-OETJSRYE.js} +523 -90
- package/dist/index.d.ts +52 -1
- package/dist/index.js +286 -121
- package/dist/server-EHKHU4JH.js +1006 -0
- package/dist/tools-export.js +2 -2
- package/package.json +2 -2
- package/dist/server-LODRVEQB.js +0 -483
package/dist/tools-export.js
CHANGED
|
@@ -2,8 +2,8 @@ import {
|
|
|
2
2
|
CURSOR_CORE_TOOL_NAMES,
|
|
3
3
|
getToolDefinitions,
|
|
4
4
|
validateToolArgs
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-OETJSRYE.js";
|
|
6
|
+
import "./chunk-O2NJFMNP.js";
|
|
7
7
|
|
|
8
8
|
// src/tools-export.ts
|
|
9
9
|
function getToolSchemas(profile) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plur-ai/mcp",
|
|
3
3
|
"mcpName": "io.github.plur-ai/plur",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.21.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"plur-mcp": "dist/index.js"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"@modelcontextprotocol/client": "2.0.0-beta.4",
|
|
17
17
|
"@modelcontextprotocol/core": "2.0.0-beta.4",
|
|
18
18
|
"zod": "^3.23.0",
|
|
19
|
-
"@plur-ai/core": "0.
|
|
19
|
+
"@plur-ai/core": "0.21.1"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "^25.5.0"
|
package/dist/server-LODRVEQB.js
DELETED
|
@@ -1,483 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CURSOR_CORE_TOOL_NAMES,
|
|
3
|
-
getToolDefinitions,
|
|
4
|
-
mcpCanary,
|
|
5
|
-
registerFlushOnExit,
|
|
6
|
-
resolveToolProfile,
|
|
7
|
-
setActiveToolProfile,
|
|
8
|
-
validateToolArgs
|
|
9
|
-
} from "./chunk-SWQDW42Z.js";
|
|
10
|
-
import {
|
|
11
|
-
VERSION
|
|
12
|
-
} from "./chunk-445S5QNU.js";
|
|
13
|
-
|
|
14
|
-
// src/server.ts
|
|
15
|
-
import { Server, ProtocolError, ProtocolErrorCode } from "@modelcontextprotocol/server";
|
|
16
|
-
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
17
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
18
|
-
import { join as join2 } from "path";
|
|
19
|
-
import { homedir } from "os";
|
|
20
|
-
import { Plur, checkForUpdate, VERSION_CHECK_SUCCESS_TTL_MS } from "@plur-ai/core";
|
|
21
|
-
|
|
22
|
-
// src/drop-log.ts
|
|
23
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "fs";
|
|
24
|
-
import { dirname, join } from "path";
|
|
25
|
-
import { atomicWrite, withLock } from "@plur-ai/core";
|
|
26
|
-
var PAYLOAD_DROP_LOG_MAX_ENTRIES = 500;
|
|
27
|
-
function payloadDropLogPath(storageRoot) {
|
|
28
|
-
return join(storageRoot, "logs", "payload-drops.jsonl");
|
|
29
|
-
}
|
|
30
|
-
function recordPayloadDrop(storageRoot, record) {
|
|
31
|
-
try {
|
|
32
|
-
const path = payloadDropLogPath(storageRoot);
|
|
33
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
34
|
-
withLock(path, () => {
|
|
35
|
-
appendFileSync(path, JSON.stringify(record) + "\n");
|
|
36
|
-
const lines = readFileSync(path, "utf8").split("\n").filter((l) => l.length > 0);
|
|
37
|
-
if (lines.length > PAYLOAD_DROP_LOG_MAX_ENTRIES) {
|
|
38
|
-
atomicWrite(path, lines.slice(-PAYLOAD_DROP_LOG_MAX_ENTRIES).join("\n") + "\n", { durable: false });
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
} catch {
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// src/server.ts
|
|
46
|
-
function serverPidPath(baseDir) {
|
|
47
|
-
return join2(baseDir ?? join2(homedir(), ".plur"), "server.pid");
|
|
48
|
-
}
|
|
49
|
-
function readEnterpriseToken(baseDir) {
|
|
50
|
-
const configPath = join2(baseDir ?? join2(homedir(), ".plur"), "config.json");
|
|
51
|
-
if (!existsSync2(configPath)) return void 0;
|
|
52
|
-
try {
|
|
53
|
-
const cfg = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
54
|
-
const ent = cfg?.enterprise;
|
|
55
|
-
if (!ent || typeof ent.url !== "string" || typeof ent.token !== "string") return void 0;
|
|
56
|
-
return { url: ent.url, token: ent.token, username: ent.username };
|
|
57
|
-
} catch {
|
|
58
|
-
return void 0;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
var _pendingReload = false;
|
|
62
|
-
function isPendingReload() {
|
|
63
|
-
return _pendingReload;
|
|
64
|
-
}
|
|
65
|
-
function clearPendingReload() {
|
|
66
|
-
_pendingReload = false;
|
|
67
|
-
}
|
|
68
|
-
var INSTRUCTIONS = `PLUR is your persistent memory. Corrections, preferences, and conventions persist across sessions as engrams.
|
|
69
|
-
|
|
70
|
-
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.
|
|
71
|
-
|
|
72
|
-
TOOL PROFILE: by default only the core session tools are exposed directly (lean profile). Every other plur_* operation is reachable via plur_admin: { action: "<tool name>", args: {...} } \u2014 same arguments and validation as a direct call. A plur_* name missing from tools/list means it MOVED behind plur_admin, not that the MCP is down \u2014 never conclude the server is unavailable from a name-lookup miss. Discover the live surface with plur_admin { action: "help" } (every action with description + argument schema) or plur_doctor (tool_surface). PLUR_TOOL_PROFILE=full exposes everything directly.
|
|
73
|
-
|
|
74
|
-
SESSION LIFECYCLE:
|
|
75
|
-
- 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.
|
|
76
|
-
- Without hooks: call plur_session_start at the start, plur_session_end at the end.
|
|
77
|
-
|
|
78
|
-
DURING the session:
|
|
79
|
-
- When user corrects you ("no, use X not Y") \u2192 call plur_learn immediately
|
|
80
|
-
- When user states a preference ("always X", "never Y") \u2192 call plur_learn immediately
|
|
81
|
-
- When you discover a codebase convention or pattern \u2192 call plur_learn
|
|
82
|
-
|
|
83
|
-
SCOPE SELECTION (set scope PER engram, by content \u2014 not once per session):
|
|
84
|
-
A single session produces engrams that belong in different stores. Choose the
|
|
85
|
-
"scope" on EACH plur_learn call from what the engram is about:
|
|
86
|
-
- Team / engineering / architecture / shared-convention knowledge \u2192 the matching
|
|
87
|
-
team store scope (e.g. "group:<org>/<team>"). plur_session_start lists the
|
|
88
|
-
scopes this install can write to \u2014 use them.
|
|
89
|
-
- Personal preferences, your workflow, or details specific to one project \u2192 the
|
|
90
|
-
default/local scope (or "project:<name>").
|
|
91
|
-
- "global" is for genuinely cross-project facts only (language gotchas, tool
|
|
92
|
-
quirks). Do NOT let team-relevant knowledge fall back to "global" by omitting
|
|
93
|
-
scope \u2014 with a team store configured it never reaches the team store. If no
|
|
94
|
-
scope fits, prefer the project/local default over global.
|
|
95
|
-
|
|
96
|
-
OPTIONAL but improves quality:
|
|
97
|
-
- Call plur_feedback to rate which injected engrams helped (positive/negative)
|
|
98
|
-
- Call plur_recall before answering factual questions \u2014 the answer may be in memory
|
|
99
|
-
|
|
100
|
-
Do not ask permission to use these tools \u2014 they are your memory system.
|
|
101
|
-
|
|
102
|
-
Setup: If this is a fresh install, suggest the user run: npx @plur-ai/mcp init
|
|
103
|
-
This installs hooks for automatic injection + session management. One-time global setup.`;
|
|
104
|
-
var GUIDE_RESOURCE = `# PLUR \u2014 Agent Guide
|
|
105
|
-
|
|
106
|
-
## What is PLUR?
|
|
107
|
-
|
|
108
|
-
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 runs locally (BM25 + embeddings); when an enterprise/remote store is configured AND relevant to the current project, recall additionally makes one live timeout-bounded call per remote host and merges the results \u2014 degradation is surfaced per host via a \`remote_stores\` block, never silent. With no remote store configured, search is fully local with zero API calls.
|
|
109
|
-
|
|
110
|
-
## Quick Start
|
|
111
|
-
|
|
112
|
-
1. \`plur_session_start\` \u2014 start a session, inject relevant context
|
|
113
|
-
2. \`plur_learn\` \u2014 store a new learning
|
|
114
|
-
3. \`plur_feedback\` \u2014 rate injected engrams
|
|
115
|
-
4. \`plur_session_end\` \u2014 capture summary and new learnings
|
|
116
|
-
|
|
117
|
-
## When to Call Each Tool
|
|
118
|
-
|
|
119
|
-
| Trigger | Tool |
|
|
120
|
-
|---------|------|
|
|
121
|
-
| Session starts | \`plur_session_start\` with task description |
|
|
122
|
-
| User corrects you | \`plur_learn\` with the correction |
|
|
123
|
-
| User states preference ("always X", "never Y") | \`plur_learn\` with scope and type |
|
|
124
|
-
| You used a recalled engram successfully | \`plur_feedback\` with "positive" |
|
|
125
|
-
| A recalled engram was wrong or irrelevant | \`plur_feedback\` with "negative" |
|
|
126
|
-
| User says "forget X" or a memory is outdated | \`plur_forget\` |
|
|
127
|
-
| You need to check what's stored | \`plur_status\` or \`plur_packs_list\` |
|
|
128
|
-
| User asks what memory did for them / is memory working | \`plur_receipt\` (relay its \`summary\`; activation_rate is coverage, not quality) |
|
|
129
|
-
| End of session | \`plur_session_end\` with summary and suggestions |
|
|
130
|
-
|
|
131
|
-
## Tool Categories
|
|
132
|
-
|
|
133
|
-
### Session Management
|
|
134
|
-
- **plur_session_start** \u2014 start a session, inject relevant context
|
|
135
|
-
- **plur_session_end** \u2014 end a session, capture summary and new learnings
|
|
136
|
-
|
|
137
|
-
### Core Memory
|
|
138
|
-
- **plur_learn** \u2014 store a correction, preference, or convention
|
|
139
|
-
- **plur_recall** \u2014 hybrid search by default (BM25 + embeddings); pass mode:"keyword" for BM25-only
|
|
140
|
-
- **plur_feedback** \u2014 rate an engram (trains relevance)
|
|
141
|
-
- **plur_forget** \u2014 retire an outdated engram
|
|
142
|
-
- **plur_promote** \u2014 activate a candidate engram (status only \u2014 never changes scope)
|
|
143
|
-
- **plur_rescope** \u2014 move an engram to another scope (e.g. promote a local engram into a team store)
|
|
144
|
-
|
|
145
|
-
### Context Injection
|
|
146
|
-
- **plur_inject** \u2014 select engrams for a task (BM25)
|
|
147
|
-
- **plur_inject_hybrid** \u2014 select engrams for a task (BM25 + embeddings, recommended)
|
|
148
|
-
|
|
149
|
-
### Episodic Timeline
|
|
150
|
-
- **plur_capture** \u2014 record what happened in a session
|
|
151
|
-
- **plur_timeline** \u2014 query past episodes
|
|
152
|
-
|
|
153
|
-
### Knowledge Management
|
|
154
|
-
- **plur_ingest** \u2014 extract engrams from text content
|
|
155
|
-
- **plur_packs_install** \u2014 install curated engram packs
|
|
156
|
-
- **plur_packs_list** \u2014 list installed packs
|
|
157
|
-
- **plur_packs_export** \u2014 export engrams as a shareable pack
|
|
158
|
-
|
|
159
|
-
### Multi-Store
|
|
160
|
-
- **plur_stores_add** \u2014 register an additional engram store
|
|
161
|
-
- **plur_stores_list** \u2014 list all configured stores
|
|
162
|
-
|
|
163
|
-
**Note:** Multi-store is currently config-only. Recall and inject search the primary store. Cross-store search coming in a future release.
|
|
164
|
-
|
|
165
|
-
### Sync & Status
|
|
166
|
-
- **plur_sync** \u2014 sync engrams across devices via git
|
|
167
|
-
- **plur_sync_status** \u2014 check sync state
|
|
168
|
-
- **plur_status** \u2014 system health
|
|
169
|
-
- **plur_receipt** \u2014 counted report of what memory retrieved for the user (local, read-only)
|
|
170
|
-
|
|
171
|
-
## Scoping
|
|
172
|
-
|
|
173
|
-
Use \`scope\` to namespace engrams per project:
|
|
174
|
-
- \`scope: "global"\` \u2014 applies everywhere (default)
|
|
175
|
-
- \`scope: "project:my-app"\` \u2014 applies only to my-app
|
|
176
|
-
- Scoped recall automatically includes global engrams
|
|
177
|
-
|
|
178
|
-
## Storage
|
|
179
|
-
|
|
180
|
-
\`\`\`
|
|
181
|
-
~/.plur/
|
|
182
|
-
\u251C\u2500\u2500 engrams.yaml # learned knowledge
|
|
183
|
-
\u251C\u2500\u2500 episodes.yaml # session timeline
|
|
184
|
-
\u2514\u2500\u2500 config.yaml # settings
|
|
185
|
-
\`\`\`
|
|
186
|
-
|
|
187
|
-
Override with \`PLUR_PATH\` environment variable.
|
|
188
|
-
`;
|
|
189
|
-
var versionRecheckTimer;
|
|
190
|
-
async function createServer(plur, options) {
|
|
191
|
-
const instance = plur ?? new Plur();
|
|
192
|
-
const profile = options?.profile ?? "lean";
|
|
193
|
-
setActiveToolProfile(profile);
|
|
194
|
-
const tools = getToolDefinitions(profile);
|
|
195
|
-
const announceUpdate = (r) => {
|
|
196
|
-
if (r.updateAvailable) {
|
|
197
|
-
console.error(`[plur] Update available: ${r.current} \u2192 ${r.latest}. Run: npm i -g @plur-ai/cli@latest && plur init (configs pin versions)`);
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
checkForUpdate("@plur-ai/mcp", VERSION, announceUpdate);
|
|
201
|
-
if (!versionRecheckTimer) {
|
|
202
|
-
versionRecheckTimer = setInterval(() => {
|
|
203
|
-
checkForUpdate("@plur-ai/mcp", VERSION, announceUpdate);
|
|
204
|
-
}, VERSION_CHECK_SUCCESS_TTL_MS);
|
|
205
|
-
versionRecheckTimer.unref?.();
|
|
206
|
-
}
|
|
207
|
-
const server = new Server(
|
|
208
|
-
{ name: "plur-mcp", version: VERSION },
|
|
209
|
-
{
|
|
210
|
-
capabilities: {
|
|
211
|
-
tools: {},
|
|
212
|
-
resources: {},
|
|
213
|
-
prompts: {},
|
|
214
|
-
logging: {}
|
|
215
|
-
},
|
|
216
|
-
instructions: INSTRUCTIONS
|
|
217
|
-
}
|
|
218
|
-
);
|
|
219
|
-
server.setRequestHandler("tools/list", async () => ({
|
|
220
|
-
tools: tools.map((t) => ({
|
|
221
|
-
name: t.name,
|
|
222
|
-
description: t.description,
|
|
223
|
-
inputSchema: t.inputSchema,
|
|
224
|
-
...t.annotations && { annotations: t.annotations }
|
|
225
|
-
}))
|
|
226
|
-
}));
|
|
227
|
-
server.setRequestHandler("tools/call", async (request) => {
|
|
228
|
-
const tool = tools.find((t) => t.name === request.params.name);
|
|
229
|
-
if (!tool) {
|
|
230
|
-
const hidden = getToolDefinitions("full").find((t) => t.name === request.params.name);
|
|
231
|
-
if (hidden) {
|
|
232
|
-
return {
|
|
233
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
234
|
-
error: `Tool "${request.params.name}" exists but is not directly callable under the current tool profile.`,
|
|
235
|
-
success: false,
|
|
236
|
-
hint: `Call it via plur_admin: { action: "${request.params.name}", args: { ... } } \u2014 same arguments, same validation, same result. To expose all tools directly, set PLUR_TOOL_PROFILE=full.`
|
|
237
|
-
}) }],
|
|
238
|
-
isError: true
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
return {
|
|
242
|
-
content: [{ type: "text", text: JSON.stringify({ error: `Unknown tool: ${request.params.name}`, success: false }) }],
|
|
243
|
-
isError: true
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
mcpCanary.tick();
|
|
247
|
-
try {
|
|
248
|
-
const rawArguments = request.params.arguments;
|
|
249
|
-
let args = rawArguments ?? {};
|
|
250
|
-
const validated = validateToolArgs(tool, args);
|
|
251
|
-
if (!validated.ok) {
|
|
252
|
-
const drop = validated.errorPayload.drop;
|
|
253
|
-
if (drop) {
|
|
254
|
-
recordPayloadDrop(instance.storageRoot, {
|
|
255
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
256
|
-
tool: request.params.name,
|
|
257
|
-
arguments_wire: drop === "whole_payload" ? rawArguments === void 0 ? "absent" : "empty_object" : "partial",
|
|
258
|
-
params_keys: Object.keys(request.params ?? {}),
|
|
259
|
-
received_fields: validated.errorPayload.received_fields,
|
|
260
|
-
missing_fields: validated.errorPayload.missing_fields,
|
|
261
|
-
missing_array_params: validated.missingArrayParams,
|
|
262
|
-
request_id: request.id,
|
|
263
|
-
server_version: VERSION
|
|
264
|
-
});
|
|
265
|
-
try {
|
|
266
|
-
void Promise.resolve(server.sendLoggingMessage({
|
|
267
|
-
level: "warning",
|
|
268
|
-
data: `Payload drop (#772) on ${request.params.name}: arguments ${rawArguments === void 0 ? "key absent from frame" : `arrived with fields [${validated.errorPayload.received_fields.join(", ")}]`}. Recorded in ${payloadDropLogPath(instance.storageRoot)}.`
|
|
269
|
-
})).catch(() => {
|
|
270
|
-
});
|
|
271
|
-
} catch {
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
return {
|
|
275
|
-
content: [{ type: "text", text: JSON.stringify(validated.errorPayload) }],
|
|
276
|
-
isError: true
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
args = validated.data;
|
|
280
|
-
const result = await tool.handler(args, instance);
|
|
281
|
-
let payload = result;
|
|
282
|
-
let resultIsError = false;
|
|
283
|
-
if (result && typeof result === "object" && result._isError === true) {
|
|
284
|
-
resultIsError = true;
|
|
285
|
-
const { _isError, ...rest } = result;
|
|
286
|
-
payload = rest;
|
|
287
|
-
}
|
|
288
|
-
return {
|
|
289
|
-
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
290
|
-
...resultIsError ? { isError: true } : {}
|
|
291
|
-
};
|
|
292
|
-
} catch (err) {
|
|
293
|
-
const message = err?.message ?? String(err);
|
|
294
|
-
server.sendLoggingMessage({ level: "error", data: `Tool ${request.params.name} failed: ${message}` });
|
|
295
|
-
return {
|
|
296
|
-
content: [{ type: "text", text: JSON.stringify({ error: message, success: false }) }],
|
|
297
|
-
isError: true
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
server.setRequestHandler("resources/list", async () => ({
|
|
302
|
-
resources: [
|
|
303
|
-
{
|
|
304
|
-
uri: "plur://guide",
|
|
305
|
-
name: "PLUR Agent Guide",
|
|
306
|
-
description: "Complete reference for all PLUR tools, when to use them, scoping, and storage",
|
|
307
|
-
mimeType: "text/markdown"
|
|
308
|
-
},
|
|
309
|
-
{
|
|
310
|
-
uri: "plur://status",
|
|
311
|
-
name: "PLUR Status",
|
|
312
|
-
description: "Live system health \u2014 engram count, episode count, pack count, storage path",
|
|
313
|
-
mimeType: "application/json"
|
|
314
|
-
}
|
|
315
|
-
]
|
|
316
|
-
}));
|
|
317
|
-
server.setRequestHandler("resources/read", async (request) => {
|
|
318
|
-
const uri = request.params.uri;
|
|
319
|
-
if (uri === "plur://guide") {
|
|
320
|
-
const cursorNote = options?.profile === "cursor" || options?.profile === "lean" || options?.profile == null ? `
|
|
321
|
-
|
|
322
|
-
## Lean tool profile (default)
|
|
323
|
-
|
|
324
|
-
Most tools above are NOT directly callable in this session \u2014 only ${[...CURSOR_CORE_TOOL_NAMES].join(", ")} are top-level tools here. Everything else in this guide is reachable through **plur_admin**: call it with \`{ action: "<tool name above>", args: {...} }\`. Set \`PLUR_TOOL_PROFILE=full\` to expose all ${getToolDefinitions("full").length} tools directly.` : "";
|
|
325
|
-
return {
|
|
326
|
-
contents: [{
|
|
327
|
-
uri: "plur://guide",
|
|
328
|
-
mimeType: "text/markdown",
|
|
329
|
-
text: GUIDE_RESOURCE + cursorNote
|
|
330
|
-
}]
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
if (uri === "plur://status") {
|
|
334
|
-
const status = await instance.status();
|
|
335
|
-
return {
|
|
336
|
-
contents: [{
|
|
337
|
-
uri: "plur://status",
|
|
338
|
-
mimeType: "application/json",
|
|
339
|
-
text: JSON.stringify({
|
|
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
|
-
version: VERSION
|
|
345
|
-
}, null, 2)
|
|
346
|
-
}]
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
throw new ProtocolError(ProtocolErrorCode.InvalidRequest, `Unknown resource: ${uri}`);
|
|
350
|
-
});
|
|
351
|
-
server.setRequestHandler("prompts/list", async () => ({
|
|
352
|
-
prompts: [
|
|
353
|
-
{
|
|
354
|
-
name: "plur-getting-started",
|
|
355
|
-
description: "Step-by-step guide to set up and start using PLUR memory"
|
|
356
|
-
},
|
|
357
|
-
{
|
|
358
|
-
name: "plur-session-start",
|
|
359
|
-
description: "Load relevant context for a task \u2014 call at the start of each session",
|
|
360
|
-
arguments: [
|
|
361
|
-
{ name: "task", description: "Brief description of the task or goal", required: true },
|
|
362
|
-
{ name: "scope", description: "Project scope (e.g. project:my-app)", required: false }
|
|
363
|
-
]
|
|
364
|
-
}
|
|
365
|
-
]
|
|
366
|
-
}));
|
|
367
|
-
server.setRequestHandler("prompts/get", async (request) => {
|
|
368
|
-
const name = request.params.name;
|
|
369
|
-
if (name === "plur-getting-started") {
|
|
370
|
-
const status = await instance.status();
|
|
371
|
-
return {
|
|
372
|
-
description: "Get started with PLUR memory",
|
|
373
|
-
messages: [{
|
|
374
|
-
role: "user",
|
|
375
|
-
content: {
|
|
376
|
-
type: "text",
|
|
377
|
-
text: `I just set up PLUR. Here's my current status:
|
|
378
|
-
|
|
379
|
-
- Engrams stored: ${status.engram_count}
|
|
380
|
-
- Episodes recorded: ${status.episode_count}
|
|
381
|
-
- Packs installed: ${status.pack_count}
|
|
382
|
-
- Storage: ${status.storage_root}
|
|
383
|
-
|
|
384
|
-
${status.engram_count === 0 ? `I have no memories yet. Help me get started by:
|
|
385
|
-
1. Teaching me a coding preference or convention (I'll use plur_learn)
|
|
386
|
-
2. Then recalling it to verify it works (I'll use plur_recall)
|
|
387
|
-
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.`}`
|
|
388
|
-
}
|
|
389
|
-
}]
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
if (name === "plur-session-start") {
|
|
393
|
-
const task = request.params.arguments?.task ?? "general work";
|
|
394
|
-
const scope = request.params.arguments?.scope;
|
|
395
|
-
return {
|
|
396
|
-
description: "Load relevant context for this session",
|
|
397
|
-
messages: [{
|
|
398
|
-
role: "user",
|
|
399
|
-
content: {
|
|
400
|
-
type: "text",
|
|
401
|
-
text: `Starting a new session. Task: ${task}${scope ? ` (scope: ${scope})` : ""}
|
|
402
|
-
|
|
403
|
-
Please:
|
|
404
|
-
1. Call plur_recall with query "${task}"${scope ? ` and scope "${scope}"` : ""} to load relevant memories
|
|
405
|
-
2. Review the recalled engrams and apply any relevant conventions or preferences
|
|
406
|
-
3. If any recalled engrams are helpful, call plur_feedback with "positive"
|
|
407
|
-
4. If any are irrelevant, call plur_feedback with "negative"`
|
|
408
|
-
}
|
|
409
|
-
}]
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
throw new ProtocolError(ProtocolErrorCode.InvalidRequest, `Unknown prompt: ${name}`);
|
|
413
|
-
});
|
|
414
|
-
return server;
|
|
415
|
-
}
|
|
416
|
-
async function runStdio() {
|
|
417
|
-
const LOG_WINDOW_MS = 6e4;
|
|
418
|
-
const LOG_MAX_PER_WINDOW = 20;
|
|
419
|
-
let logWindowStart = 0;
|
|
420
|
-
let logCount = 0;
|
|
421
|
-
let suppressed = 0;
|
|
422
|
-
const logFault = (kind, err) => {
|
|
423
|
-
const now = Date.now();
|
|
424
|
-
if (now - logWindowStart > LOG_WINDOW_MS) {
|
|
425
|
-
if (suppressed > 0) {
|
|
426
|
-
process.stderr.write(`[plur:mcp] (${suppressed} further fault log lines suppressed in the last minute)
|
|
427
|
-
`);
|
|
428
|
-
}
|
|
429
|
-
logWindowStart = now;
|
|
430
|
-
logCount = 0;
|
|
431
|
-
suppressed = 0;
|
|
432
|
-
}
|
|
433
|
-
if (logCount >= LOG_MAX_PER_WINDOW) {
|
|
434
|
-
suppressed++;
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
logCount++;
|
|
438
|
-
const detail = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
439
|
-
process.stderr.write(`[plur:mcp] ${kind} (#1070): ${detail}
|
|
440
|
-
`);
|
|
441
|
-
};
|
|
442
|
-
process.on("unhandledRejection", (err) => logFault("unhandled rejection \u2014 server stays up", err));
|
|
443
|
-
process.on("uncaughtException", (err) => {
|
|
444
|
-
logFault("UNCAUGHT EXCEPTION \u2014 exiting so lock recovery and the harness can see the failure", err);
|
|
445
|
-
setTimeout(() => process.exit(1), 100);
|
|
446
|
-
});
|
|
447
|
-
const induceMs = Number(process.env.PLUR_TEST_INDUCE_UNHANDLED_REJECTION_MS ?? "");
|
|
448
|
-
if (Number.isFinite(induceMs) && induceMs > 0) {
|
|
449
|
-
setTimeout(() => {
|
|
450
|
-
void Promise.reject(new Error("induced test rejection (#1070)"));
|
|
451
|
-
}, induceMs);
|
|
452
|
-
}
|
|
453
|
-
const induceThrowMs = Number(process.env.PLUR_TEST_INDUCE_UNCAUGHT_EXCEPTION_MS ?? "");
|
|
454
|
-
if (Number.isFinite(induceThrowMs) && induceThrowMs > 0) {
|
|
455
|
-
setTimeout(() => {
|
|
456
|
-
throw new Error("induced test exception (#1070)");
|
|
457
|
-
}, induceThrowMs);
|
|
458
|
-
}
|
|
459
|
-
const profile = resolveToolProfile();
|
|
460
|
-
const server = await createServer(void 0, { profile });
|
|
461
|
-
registerFlushOnExit({});
|
|
462
|
-
try {
|
|
463
|
-
writeFileSync(serverPidPath(), String(process.pid));
|
|
464
|
-
} catch {
|
|
465
|
-
}
|
|
466
|
-
if (process.platform !== "win32") {
|
|
467
|
-
process.on("SIGUSR1", () => {
|
|
468
|
-
_pendingReload = true;
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
const transport = new StdioServerTransport();
|
|
472
|
-
await server.connect(transport);
|
|
473
|
-
}
|
|
474
|
-
export {
|
|
475
|
-
GUIDE_RESOURCE,
|
|
476
|
-
INSTRUCTIONS,
|
|
477
|
-
clearPendingReload,
|
|
478
|
-
createServer,
|
|
479
|
-
isPendingReload,
|
|
480
|
-
readEnterpriseToken,
|
|
481
|
-
runStdio,
|
|
482
|
-
serverPidPath
|
|
483
|
-
};
|