@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.11.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-M7GBKD4M.js");
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.11.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
- function getToolDefinitions() {
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 install. 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.',
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 jsonSchemaPropToZod(prop) {
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 schema = tool.inputSchema;
2132
- if (schema?.properties) {
2133
- const shape = {};
2134
- for (const [key, prop] of Object.entries(schema.properties)) {
2135
- const field = jsonSchemaPropToZod(prop);
2136
- shape[key] = schema.required?.includes(key) ? field : field.optional();
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
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
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 server = await createServer();
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.11.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.11.0"
17
+ "@plur-ai/core": "0.13.0"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@types/node": "^25.5.0"