@plur-ai/mcp 0.11.0 → 0.13.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
|
@@ -5,7 +5,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSy
|
|
|
5
5
|
import { join } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import { homedir, platform } from "os";
|
|
8
|
-
var VERSION = "0.
|
|
8
|
+
var VERSION = "0.13.0";
|
|
9
9
|
var HELP = `plur-mcp v${VERSION} \u2014 persistent memory for AI agents
|
|
10
10
|
|
|
11
11
|
Usage:
|
|
@@ -279,7 +279,7 @@ if (arg === "init") {
|
|
|
279
279
|
process.exit(0);
|
|
280
280
|
}
|
|
281
281
|
if (arg === "serve" || arg === void 0) {
|
|
282
|
-
const { runStdio } = await import("./server-
|
|
282
|
+
const { runStdio } = await import("./server-B6OJN4ZX.js");
|
|
283
283
|
runStdio().catch((err) => {
|
|
284
284
|
console.error("Failed to start PLUR MCP server:", err);
|
|
285
285
|
process.exit(1);
|
|
@@ -31,9 +31,10 @@ function recordTelemetry(event) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// src/version.ts
|
|
34
|
-
var VERSION = "0.
|
|
34
|
+
var VERSION = "0.13.0";
|
|
35
35
|
|
|
36
36
|
// src/tools.ts
|
|
37
|
+
import { z } from "zod";
|
|
37
38
|
function makeHttpLlm(baseUrl, apiKey, model = "gpt-4o-mini") {
|
|
38
39
|
return async (prompt) => {
|
|
39
40
|
const response = await fetch(`${baseUrl.replace(/\/$/, "")}/chat/completions`, {
|
|
@@ -55,6 +56,73 @@ function makeHttpLlm(baseUrl, apiKey, model = "gpt-4o-mini") {
|
|
|
55
56
|
return data.choices?.[0]?.message?.content ?? "";
|
|
56
57
|
};
|
|
57
58
|
}
|
|
59
|
+
function jsonSchemaPropToZod(prop) {
|
|
60
|
+
if (!prop || typeof prop !== "object") return z.unknown();
|
|
61
|
+
const variants = prop.anyOf ?? prop.oneOf;
|
|
62
|
+
if (Array.isArray(variants) && variants.length > 0) {
|
|
63
|
+
const zodVariants = variants.map(jsonSchemaPropToZod);
|
|
64
|
+
if (zodVariants.length === 1) return zodVariants[0];
|
|
65
|
+
return z.union(zodVariants);
|
|
66
|
+
}
|
|
67
|
+
if (prop.type === "string") return prop.enum ? z.enum(prop.enum) : z.string();
|
|
68
|
+
if (prop.type === "number" || prop.type === "integer") return z.number();
|
|
69
|
+
if (prop.type === "boolean") return z.boolean();
|
|
70
|
+
if (prop.type === "array") {
|
|
71
|
+
const itemSchema = prop.items ? jsonSchemaPropToZod(prop.items) : z.unknown();
|
|
72
|
+
return z.preprocess((val) => {
|
|
73
|
+
if (typeof val !== "string") return val;
|
|
74
|
+
const trimmed = val.trim();
|
|
75
|
+
if (trimmed.startsWith("[")) {
|
|
76
|
+
try {
|
|
77
|
+
const parsed = JSON.parse(trimmed);
|
|
78
|
+
return Array.isArray(parsed) ? parsed : val;
|
|
79
|
+
} catch {
|
|
80
|
+
return val;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (prop.items?.type === "string") {
|
|
84
|
+
return trimmed.length === 0 ? [] : trimmed.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
85
|
+
}
|
|
86
|
+
return val;
|
|
87
|
+
}, z.array(itemSchema));
|
|
88
|
+
}
|
|
89
|
+
if (prop.type === "object" && prop.properties) {
|
|
90
|
+
const shape = {};
|
|
91
|
+
for (const [k, p] of Object.entries(prop.properties)) {
|
|
92
|
+
const field = jsonSchemaPropToZod(p);
|
|
93
|
+
shape[k] = prop.required?.includes(k) ? field : field.optional();
|
|
94
|
+
}
|
|
95
|
+
return z.object(shape).passthrough();
|
|
96
|
+
}
|
|
97
|
+
return z.unknown();
|
|
98
|
+
}
|
|
99
|
+
function validateToolArgs(tool, rawArgs) {
|
|
100
|
+
const schema = tool.inputSchema;
|
|
101
|
+
if (!schema?.properties) return { ok: true, data: rawArgs };
|
|
102
|
+
const shape = {};
|
|
103
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
104
|
+
const field = jsonSchemaPropToZod(prop);
|
|
105
|
+
shape[key] = schema.required?.includes(key) ? field : field.optional();
|
|
106
|
+
}
|
|
107
|
+
const parsed = z.object(shape).passthrough().safeParse(rawArgs);
|
|
108
|
+
if (!parsed.success) {
|
|
109
|
+
const receivedFields = Object.keys(rawArgs);
|
|
110
|
+
const details = parsed.error.issues.map((i) => `${i.path.join(".") || "root"}: ${i.message}`).join(", ");
|
|
111
|
+
const hasArrayParam = Object.values(schema.properties ?? {}).some((p) => p?.type === "array");
|
|
112
|
+
const arrayBugHint = receivedFields.length === 0 && hasArrayParam ? ' Known client-side bug (plur-ai/plur#297): some MCP clients drop the entire arguments payload when an array-typed parameter is included. Retry passing array parameters as a JSON string (e.g. tags: "[\\"a\\",\\"b\\"]") or a comma-separated string (tags: "a, b") \u2014 the server coerces both back into arrays.' : "";
|
|
113
|
+
const receivedNote = receivedFields.length > 0 ? `Received fields: [${receivedFields.join(", ")}].` : "Received no fields (the arguments object was empty).";
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
errorPayload: {
|
|
117
|
+
error: `Invalid arguments: ${details}. ${receivedNote} The call reached the server \u2014 this is a malformed-arguments error, not a transport failure. Fix the field(s) named above and retry; do not abandon the call.` + arrayBugHint,
|
|
118
|
+
success: false,
|
|
119
|
+
received_fields: receivedFields,
|
|
120
|
+
_isError: true
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return { ok: true, data: parsed.data };
|
|
125
|
+
}
|
|
58
126
|
var PLUR_GUIDE_EMPTY = `## PLUR \u2014 Empty Store
|
|
59
127
|
|
|
60
128
|
You have **0 engrams**. This session's learnings will be lost unless you store them.
|
|
@@ -111,7 +179,67 @@ mcpCanary.expect({
|
|
|
111
179
|
description: "Learning from corrections",
|
|
112
180
|
fix: "Call plur_learn when corrected. If using hooks, verify they are installed."
|
|
113
181
|
});
|
|
114
|
-
|
|
182
|
+
var CURSOR_CORE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
183
|
+
"plur_session_start",
|
|
184
|
+
"plur_session_end",
|
|
185
|
+
"plur_learn",
|
|
186
|
+
"plur_recall_hybrid",
|
|
187
|
+
"plur_feedback",
|
|
188
|
+
"plur_forget",
|
|
189
|
+
"plur_status",
|
|
190
|
+
"plur_doctor",
|
|
191
|
+
"plur_packs_uninstall",
|
|
192
|
+
"plur_tensions_purge"
|
|
193
|
+
]);
|
|
194
|
+
function buildAdminDispatchTool(all) {
|
|
195
|
+
const byName = new Map(all.map((t) => [t.name, t]));
|
|
196
|
+
const adminActions = all.map((t) => t.name).filter((n) => !CURSOR_CORE_TOOL_NAMES.has(n)).sort();
|
|
197
|
+
return {
|
|
198
|
+
name: "plur_admin",
|
|
199
|
+
description: `Dispatch for less-common PLUR operations (packs, sync, tensions, stores, timeline, ingest, and more), collapsed into one tool so Cursor's ~40-tool-per-workspace limit is not exhausted by PLUR alone. Set "action" to the underlying tool name and "args" to that tool's normal arguments. Valid actions: ${adminActions.join(", ")}.`,
|
|
200
|
+
annotations: { title: "Admin dispatch", readOnlyHint: false },
|
|
201
|
+
inputSchema: {
|
|
202
|
+
type: "object",
|
|
203
|
+
properties: {
|
|
204
|
+
// No `enum` here — an invalid action must reach the handler's custom
|
|
205
|
+
// Unknown-action message with the full valid-actions list, not fail
|
|
206
|
+
// at top-level schema validation with a generic "Invalid arguments"
|
|
207
|
+
// error. If `enum: adminActions` were set, the top-level
|
|
208
|
+
// CallToolRequestSchema handler's Zod validation would reject
|
|
209
|
+
// unknown actions before this handler's `if (!target)` branch ever
|
|
210
|
+
// ran.
|
|
211
|
+
action: { type: "string", description: "Which underlying plur_* tool to invoke" },
|
|
212
|
+
args: { type: "object", description: "Arguments for the chosen action, matching that tool's normal input schema", additionalProperties: true }
|
|
213
|
+
},
|
|
214
|
+
required: ["action"]
|
|
215
|
+
},
|
|
216
|
+
handler: async (args, plur) => {
|
|
217
|
+
const action = args.action;
|
|
218
|
+
const target = byName.get(action);
|
|
219
|
+
if (!target) {
|
|
220
|
+
return { error: `Unknown action "${action}". Valid actions: ${adminActions.join(", ")}`, success: false, _isError: true };
|
|
221
|
+
}
|
|
222
|
+
const innerArgs = args.args ?? {};
|
|
223
|
+
const validated = validateToolArgs(target, innerArgs);
|
|
224
|
+
if (!validated.ok) {
|
|
225
|
+
return { ...validated.errorPayload, error: `${action}: ${validated.errorPayload.error}` };
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
return await target.handler(validated.data, plur);
|
|
229
|
+
} catch (err) {
|
|
230
|
+
const message = err?.message ?? String(err);
|
|
231
|
+
throw new Error(`${action}: ${message}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function getToolDefinitions(profile = "full") {
|
|
237
|
+
const all = getAllToolDefinitions();
|
|
238
|
+
if (profile !== "cursor") return all;
|
|
239
|
+
const core = all.filter((t) => CURSOR_CORE_TOOL_NAMES.has(t.name));
|
|
240
|
+
return [...core, buildAdminDispatchTool(all)];
|
|
241
|
+
}
|
|
242
|
+
function getAllToolDefinitions() {
|
|
115
243
|
return [
|
|
116
244
|
{
|
|
117
245
|
name: "plur_learn",
|
|
@@ -964,7 +1092,7 @@ function getToolDefinitions() {
|
|
|
964
1092
|
},
|
|
965
1093
|
{
|
|
966
1094
|
name: "plur_doctor",
|
|
967
|
-
description: 'Diagnose the PLUR
|
|
1095
|
+
description: 'Diagnose the PLUR ENGINE (embedder, hybrid search, remote-store auth) \u2014 not hook/MCP wiring. Reports whether the embedding model loaded, whether hybrid search is fully operational, and \u2014 for any configured enterprise/remote store \u2014 whether its auth is valid (probes /api/v1/me and decodes token expiry), so a dead or soon-to-expire token surfaces instead of hiding behind a "healthy" report. Run this first when recall feels off or team engrams stop syncing. Does NOT check .cursor/mcp.json, .cursor/hooks.json, or the live MCP tool count \u2014 for that, run the `plur doctor` CLI command in a terminal (a different, more thorough check with the same name).',
|
|
968
1096
|
annotations: { title: "Doctor", readOnlyHint: false, idempotentHint: false },
|
|
969
1097
|
inputSchema: {
|
|
970
1098
|
type: "object",
|
|
@@ -1931,7 +2059,6 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1931
2059
|
}
|
|
1932
2060
|
|
|
1933
2061
|
// src/server.ts
|
|
1934
|
-
import { z } from "zod";
|
|
1935
2062
|
var INSTRUCTIONS = `PLUR is your persistent memory. Corrections, preferences, and conventions persist across sessions as engrams.
|
|
1936
2063
|
|
|
1937
2064
|
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.
|
|
@@ -2049,49 +2176,9 @@ Use \`scope\` to namespace engrams per project:
|
|
|
2049
2176
|
|
|
2050
2177
|
Override with \`PLUR_PATH\` environment variable.
|
|
2051
2178
|
`;
|
|
2052
|
-
function
|
|
2053
|
-
if (!prop || typeof prop !== "object") return z.unknown();
|
|
2054
|
-
const variants = prop.anyOf ?? prop.oneOf;
|
|
2055
|
-
if (Array.isArray(variants) && variants.length > 0) {
|
|
2056
|
-
const zodVariants = variants.map(jsonSchemaPropToZod);
|
|
2057
|
-
if (zodVariants.length === 1) return zodVariants[0];
|
|
2058
|
-
return z.union(zodVariants);
|
|
2059
|
-
}
|
|
2060
|
-
if (prop.type === "string") return prop.enum ? z.enum(prop.enum) : z.string();
|
|
2061
|
-
if (prop.type === "number" || prop.type === "integer") return z.number();
|
|
2062
|
-
if (prop.type === "boolean") return z.boolean();
|
|
2063
|
-
if (prop.type === "array") {
|
|
2064
|
-
const itemSchema = prop.items ? jsonSchemaPropToZod(prop.items) : z.unknown();
|
|
2065
|
-
return z.preprocess((val) => {
|
|
2066
|
-
if (typeof val !== "string") return val;
|
|
2067
|
-
const trimmed = val.trim();
|
|
2068
|
-
if (trimmed.startsWith("[")) {
|
|
2069
|
-
try {
|
|
2070
|
-
const parsed = JSON.parse(trimmed);
|
|
2071
|
-
return Array.isArray(parsed) ? parsed : val;
|
|
2072
|
-
} catch {
|
|
2073
|
-
return val;
|
|
2074
|
-
}
|
|
2075
|
-
}
|
|
2076
|
-
if (prop.items?.type === "string") {
|
|
2077
|
-
return trimmed.length === 0 ? [] : trimmed.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
2078
|
-
}
|
|
2079
|
-
return val;
|
|
2080
|
-
}, z.array(itemSchema));
|
|
2081
|
-
}
|
|
2082
|
-
if (prop.type === "object" && prop.properties) {
|
|
2083
|
-
const shape = {};
|
|
2084
|
-
for (const [k, p] of Object.entries(prop.properties)) {
|
|
2085
|
-
const field = jsonSchemaPropToZod(p);
|
|
2086
|
-
shape[k] = prop.required?.includes(k) ? field : field.optional();
|
|
2087
|
-
}
|
|
2088
|
-
return z.object(shape).passthrough();
|
|
2089
|
-
}
|
|
2090
|
-
return z.unknown();
|
|
2091
|
-
}
|
|
2092
|
-
async function createServer(plur) {
|
|
2179
|
+
async function createServer(plur, options) {
|
|
2093
2180
|
const instance = plur ?? new Plur2();
|
|
2094
|
-
const tools = getToolDefinitions();
|
|
2181
|
+
const tools = getToolDefinitions(options?.profile ?? "full");
|
|
2095
2182
|
checkForUpdate("@plur-ai/mcp", VERSION, (r) => {
|
|
2096
2183
|
if (r.updateAvailable) {
|
|
2097
2184
|
console.error(`[plur] Update available: ${r.current} \u2192 ${r.latest}. Run: npx @plur-ai/mcp@latest`);
|
|
@@ -2128,33 +2215,26 @@ async function createServer(plur) {
|
|
|
2128
2215
|
mcpCanary.tick();
|
|
2129
2216
|
try {
|
|
2130
2217
|
let args = request.params.arguments ?? {};
|
|
2131
|
-
const
|
|
2132
|
-
if (
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
}
|
|
2138
|
-
const parsed = z.object(shape).passthrough().safeParse(args);
|
|
2139
|
-
if (!parsed.success) {
|
|
2140
|
-
const receivedFields = Object.keys(args);
|
|
2141
|
-
const details = parsed.error.issues.map((i) => `${i.path.join(".") || "root"}: ${i.message}`).join(", ");
|
|
2142
|
-
const receivedNote = receivedFields.length > 0 ? `Received fields: [${receivedFields.join(", ")}].` : "Received no fields (the arguments object was empty).";
|
|
2143
|
-
const hasArrayParam = Object.values(schema.properties).some((p) => p?.type === "array");
|
|
2144
|
-
const arrayBugHint = receivedFields.length === 0 && hasArrayParam ? ' Known client-side bug (plur-ai/plur#297): some MCP clients drop the entire arguments payload when an array-typed parameter is included. Retry passing array parameters as a JSON string (e.g. tags: "[\\"a\\",\\"b\\"]") or a comma-separated string (tags: "a, b") \u2014 the server coerces both back into arrays.' : "";
|
|
2145
|
-
return {
|
|
2146
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
2147
|
-
error: `Invalid arguments: ${details}. ${receivedNote} The call reached the server \u2014 this is a malformed-arguments error, not a transport failure. Fix the field(s) named above and retry; do not abandon the call.` + arrayBugHint,
|
|
2148
|
-
success: false,
|
|
2149
|
-
received_fields: receivedFields
|
|
2150
|
-
}) }],
|
|
2151
|
-
isError: true
|
|
2152
|
-
};
|
|
2153
|
-
}
|
|
2154
|
-
args = parsed.data;
|
|
2218
|
+
const validated = validateToolArgs(tool, args);
|
|
2219
|
+
if (!validated.ok) {
|
|
2220
|
+
return {
|
|
2221
|
+
content: [{ type: "text", text: JSON.stringify(validated.errorPayload) }],
|
|
2222
|
+
isError: true
|
|
2223
|
+
};
|
|
2155
2224
|
}
|
|
2225
|
+
args = validated.data;
|
|
2156
2226
|
const result = await tool.handler(args, instance);
|
|
2157
|
-
|
|
2227
|
+
let payload = result;
|
|
2228
|
+
let resultIsError = false;
|
|
2229
|
+
if (result && typeof result === "object" && result._isError === true) {
|
|
2230
|
+
resultIsError = true;
|
|
2231
|
+
const { _isError, ...rest } = result;
|
|
2232
|
+
payload = rest;
|
|
2233
|
+
}
|
|
2234
|
+
return {
|
|
2235
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
2236
|
+
...resultIsError ? { isError: true } : {}
|
|
2237
|
+
};
|
|
2158
2238
|
} catch (err) {
|
|
2159
2239
|
const message = err?.message ?? String(err);
|
|
2160
2240
|
server.sendLoggingMessage({ level: "error", data: `Tool ${request.params.name} failed: ${message}` });
|
|
@@ -2183,11 +2263,16 @@ async function createServer(plur) {
|
|
|
2183
2263
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
2184
2264
|
const uri = request.params.uri;
|
|
2185
2265
|
if (uri === "plur://guide") {
|
|
2266
|
+
const cursorNote = options?.profile === "cursor" ? `
|
|
2267
|
+
|
|
2268
|
+
## Cursor tool profile
|
|
2269
|
+
|
|
2270
|
+
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: {...} }\`.` : "";
|
|
2186
2271
|
return {
|
|
2187
2272
|
contents: [{
|
|
2188
2273
|
uri: "plur://guide",
|
|
2189
2274
|
mimeType: "text/markdown",
|
|
2190
|
-
text: GUIDE_RESOURCE
|
|
2275
|
+
text: GUIDE_RESOURCE + cursorNote
|
|
2191
2276
|
}]
|
|
2192
2277
|
};
|
|
2193
2278
|
}
|
|
@@ -2275,7 +2360,8 @@ Please:
|
|
|
2275
2360
|
return server;
|
|
2276
2361
|
}
|
|
2277
2362
|
async function runStdio() {
|
|
2278
|
-
const
|
|
2363
|
+
const profile = process.env.PLUR_TOOL_PROFILE === "cursor" ? "cursor" : "full";
|
|
2364
|
+
const server = await createServer(void 0, { profile });
|
|
2279
2365
|
registerFlushOnExit({});
|
|
2280
2366
|
const transport = new StdioServerTransport();
|
|
2281
2367
|
await server.connect(transport);
|
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.13.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"plur-mcp": "dist/index.js"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
16
16
|
"zod": "^3.23.0",
|
|
17
|
-
"@plur-ai/core": "0.
|
|
17
|
+
"@plur-ai/core": "0.13.0"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@types/node": "^25.5.0"
|