filemayor-mcp 4.0.5 → 4.0.7

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.
Files changed (3) hide show
  1. package/README.md +19 -16
  2. package/index.mjs +109 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **The FileMayor MCP server.** Drive your intelligent filesystem clerk from Claude, Cursor, Zed, or any [Model Context Protocol](https://modelcontextprotocol.io) client.
4
4
 
5
- `v4.0.5` · Node ≥20 · [filemayor.com/mcp](https://filemayor.com/mcp)
5
+ `v4.0.7` · Node ≥20 · [filemayor.com/mcp](https://filemayor.com/mcp)
6
6
 
7
7
  [![smithery badge](https://smithery.ai/badge/filemayor-mcp)](https://smithery.ai/server/filemayor-mcp)
8
8
 
@@ -29,10 +29,7 @@ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) o
29
29
  "mcpServers": {
30
30
  "filemayor": {
31
31
  "command": "npx",
32
- "args": ["-y", "filemayor-mcp"],
33
- "env": {
34
- "GEMINI_API_KEY": "your-key-here"
35
- }
32
+ "args": ["-y", "filemayor-mcp"]
36
33
  }
37
34
  }
38
35
  }
@@ -40,6 +37,8 @@ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) o
40
37
 
41
38
  Restart Claude. You'll see the FileMayor tools in the 🔧 menu.
42
39
 
40
+ > **No API key needed.** When Claude is the client, Claude IS the AI — it calls `filemayor_explain` to audit the folder, reasons about the moves, and passes them directly to `filemayor_apply`. The `filemayor_plan` tool (which calls an external AI) is only for non-Claude clients.
41
+
43
42
  ## Wire it into Cursor / Zed / other MCP clients
44
43
 
45
44
  Any client that speaks MCP over stdio works. Point it at `filemayor-mcp` and you're done.
@@ -51,7 +50,7 @@ Any client that speaks MCP over stdio works. Point it at `filemayor-mcp` and you
51
50
  | `filemayor_scan` | List every file in a directory tree with size + category. |
52
51
  | `filemayor_analyze` | Deep audit: duplicates, bloat, junk, largest dirs. |
53
52
  | `filemayor_explain` | Folder health score (0–100), atomic bundles, insights. |
54
- | `filemayor_plan` | AI-generated plan from a natural-language prompt. Requires `GEMINI_API_KEY`. |
53
+ | `filemayor_plan` | For non-Claude clients: AI-generated plan using whichever key is set (`ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, or `OPENAI_API_KEY`). When Claude is the client, skip this — Claude reasons directly. |
55
54
  | `filemayor_apply` | Execute the most recent plan from `filemayor_plan`. |
56
55
  | `filemayor_rollback` | Reverse the most recent applied plan. |
57
56
  | `filemayor_organize` | Deterministic auto-organize by extension (no AI). |
@@ -104,26 +103,30 @@ Doesn't apply. FileMayor MCP is **Node.js**, not Python. Its single runtime depe
104
103
 
105
104
  This is the supply-chain risk that applies to *every* MCP server in principle, ours included. Our mitigations:
106
105
 
107
- - The whole server is one file: [`mcp/index.mjs`](https://github.com/Hrypopo/FileMayor/blob/main/mcp/index.mjs) — ~410 lines. Tool declarations and their `case` handlers sit side-by-side. You can read it end-to-end in 10 minutes.
106
+ - The whole server is one file: [`mcp/index.mjs`](https://github.com/Hrypopo/FileMayor/blob/main/mcp/index.mjs) — ~580 lines. Tool declarations and their `case` handlers sit side-by-side. You can read it end-to-end in about 15 minutes. (`--audit` prints the exact current line count under `verifyBy`, so the number can't drift out of date.)
108
107
  - `grep -E "fetch\(|http\.request|https\.request|net\." mcp/index.mjs` returns nothing. The server makes no outbound network calls of its own.
109
- - The *only* off-machine egress path is the optional Gemini call inside `filemayor_plan`, gated by the `GEMINI_API_KEY` environment variable. Without that key, planning falls back to deterministic rules. With the key, only directory metadata (paths, sizes, extensions) is sent — no file contents.
110
- - Published under the `@filemayor` npm scope; the GitHub repo is the canonical source.
108
+ - The *only* off-machine egress path is the optional AI call inside `filemayor_plan`, gated by an AI provider key (`ANTHROPIC_API_KEY` → Claude, `GEMINI_API_KEY` Gemini, or `OPENAI_API_KEY` → GPT-4o-mini). The first key present wins. Without any key, the tool returns an error and nothing is sent. With a key, only directory metadata (paths, sizes, extensions) is sent — no file contents. When Claude is the MCP client, `filemayor_plan` is not needed at all.
109
+ - The GitHub repo is the canonical source.
111
110
 
112
111
  ### What `--audit` reports
113
112
 
114
113
  ```json
115
114
  {
116
115
  "package": "filemayor-mcp",
117
- "version": "4.0.0",
116
+ "version": "4.0.7",
118
117
  "transport": { "type": "stdio", "networkListeners": false, "ports": [] },
119
118
  "outbound": {
120
119
  "defaultEgress": "none",
121
120
  "optionalEgress": {
122
121
  "enabled": false,
123
- "destination": "https://generativelanguage.googleapis.com",
124
- "trigger": "filemayor_plan tool only",
122
+ "destinations": [
123
+ "https://api.anthropic.com",
124
+ "https://generativelanguage.googleapis.com",
125
+ "https://api.openai.com"
126
+ ],
127
+ "trigger": "filemayor_plan tool only (not used when Claude is the MCP client)",
125
128
  "payload": "directory metadata only — no file contents",
126
- "gatedBy": "GEMINI_API_KEY environment variable"
129
+ "gatedBy": "one of ANTHROPIC_API_KEY / GEMINI_API_KEY / OPENAI_API_KEY (none currently set)"
127
130
  }
128
131
  },
129
132
  "runtimeSafeguards": {
@@ -137,7 +140,7 @@ This is the supply-chain risk that applies to *every* MCP server in principle, o
137
140
  "dependencies": { "runtime": ["@modelcontextprotocol/sdk"], "python": false, "starlette": false, "fastapi": false },
138
141
  "source": "https://github.com/Hrypopo/FileMayor/blob/main/mcp/index.mjs",
139
142
  "verifyBy": [
140
- "Read mcp/index.mjs end-to-end — it's ~410 lines.",
143
+ "Read mcp/index.mjs end-to-end — the report prints the exact current line count here.",
141
144
  "grep for outbound calls: rg \"fetch\\(|http\\.request|https\\.request|net\\.\" mcp/index.mjs",
142
145
  "Run `npm view filemayor-mcp` and compare the published shasum to the GitHub tag.",
143
146
  "Run this same `--audit` after upgrading to detect changes to transport / egress / tool list."
@@ -149,7 +152,7 @@ This is the supply-chain risk that applies to *every* MCP server in principle, o
149
152
 
150
153
  A viral tutorial recommended `pip install mcp-audit && mcp-audit scan` to audit installed MCP servers. As of this release, `mcp-audit` is **not published on PyPI**, and the `mcp-audit` package on npm is a 408-byte hello-world stub (v0.0.1, published April 2025, no functional code). The tutorial directs viewers to comment on a social-media post to receive the "real" tool by direct message — that's a social-engineering pattern, not a verifiable tool. Be skeptical of any installer not on a public registry under a known maintainer.
151
154
 
152
- If a real, signed, open-source MCP scanner emerges, we'll link to it from this section and publish the verdict against `filemayor-mcp`. Until then, `--audit` + reading the 410 lines of source is the trustworthy path.
155
+ If a real, signed, open-source MCP scanner emerges, we'll link to it from this section and publish the verdict against `filemayor-mcp`. Until then, `--audit` + reading the ~580 lines of source is the trustworthy path.
153
156
 
154
157
  ### Hardened-runtime safeguards (the engine, not just the MCP shell)
155
158
 
@@ -161,7 +164,7 @@ Every tool that touches disk runs through the same security layers the CLI and D
161
164
  | Plan-then-apply gate | `_explain` and `_plan` are read-only. Only `_apply` mutates disk. |
162
165
  | Journaled moves | Every move is recorded to a write-ahead log on disk. |
163
166
  | Rollback | `_rollback` reverses the last applied plan; `_undo_last` rolls back N specific moves. The journal survives crashes. |
164
- | No-telemetry | The MCP server makes no analytics calls. The only off-machine call is the documented, opt-in Gemini planner. |
167
+ | No-telemetry | The MCP server makes no analytics calls. The only off-machine call is the documented, opt-in AI planner in `filemayor_plan` (not used when Claude is the client). |
165
168
 
166
169
  ## License
167
170
 
package/index.mjs CHANGED
@@ -12,7 +12,17 @@
12
12
  *
13
13
  * All operations honor the same hardened-runtime safeguards as the CLI
14
14
  * and Desktop app: path jailing, system-dir blocking, journaled moves,
15
- * full rollback. AI-driven planning still requires GEMINI_API_KEY.
15
+ * full rollback.
16
+ *
17
+ * When Claude is the MCP client, Claude IS the AI — no external API key
18
+ * is needed. filemayor_plan is available for non-Claude clients (Cursor,
19
+ * Zed, scripts) and picks up whichever key is set in the environment, in
20
+ * this priority order:
21
+ * ANTHROPIC_API_KEY → Claude (claude-haiku-4-5-20251001)
22
+ * GEMINI_API_KEY → Gemini (gemini-2.0-flash)
23
+ * OPENAI_API_KEY → GPT-4o-mini
24
+ * The key selects the provider; the core planner (cli/core/ai) dispatches
25
+ * the actual call. See detectAiProvider() below.
16
26
  * ═══════════════════════════════════════════════════════════════════
17
27
  */
18
28
 
@@ -75,6 +85,24 @@ function checkDir(p) {
75
85
  return { resolved: v.resolved };
76
86
  }
77
87
 
88
+ // Pick the external AI provider for filemayor_plan from whichever key is
89
+ // set, in priority order. The core planner (cli/core/ai) dispatches on
90
+ // FM_AI_PROVIDER + FM_AI_API_KEY, so the keys below are the single source
91
+ // of truth for what the tool actually supports. Returns null if no key.
92
+ const AI_PROVIDERS = [
93
+ { env: 'ANTHROPIC_API_KEY', provider: 'anthropic', label: 'Claude (claude-haiku-4-5-20251001)' },
94
+ { env: 'GEMINI_API_KEY', provider: 'gemini', label: 'Gemini (gemini-2.0-flash)' },
95
+ { env: 'OPENAI_API_KEY', provider: 'openai', label: 'GPT-4o-mini' },
96
+ ];
97
+
98
+ function detectAiProvider() {
99
+ for (const p of AI_PROVIDERS) {
100
+ const apiKey = process.env[p.env];
101
+ if (apiKey) return { ...p, apiKey };
102
+ }
103
+ return null;
104
+ }
105
+
78
106
  // ─── Tool definitions ──────────────────────────────────────────────
79
107
 
80
108
  const TOOLS = [
@@ -115,7 +143,7 @@ const TOOLS = [
115
143
  },
116
144
  {
117
145
  name: 'filemayor_plan',
118
- description: 'The "cure" half of the Curative Triad. Given a natural-language intent ("organize by type", "group by year", "tidy downloads"), uses the AI planner to propose a journaled, reversible plan of file moves. Does NOT execute call filemayor_apply to commit. Requires GEMINI_API_KEY.',
146
+ description: 'For non-Claude MCP clients: generates a move plan from a natural-language prompt using an available AI provider (ANTHROPIC_API_KEY Claude, GEMINI_API_KEY Gemini, OPENAI_API_KEY → GPT-4o-mini). When Claude IS the client, skip this tool — instead call filemayor_explain to audit the folder, reason about the moves yourself, then pass them directly to filemayor_apply as the "moves" parameter.',
119
147
  inputSchema: {
120
148
  type: 'object',
121
149
  properties: {
@@ -127,8 +155,24 @@ const TOOLS = [
127
155
  },
128
156
  {
129
157
  name: 'filemayor_apply',
130
- description: 'Execute the most recently generated plan from filemayor_plan. Returns per-file results. The journal is written to disk so filemayor_rollback can undo it.',
131
- inputSchema: { type: 'object', properties: {} },
158
+ description: 'Execute a move plan and journal every operation for rollback. Two modes: (1) Claude-native — pass a "moves" array of {src, dst} objects that you generated yourself after calling filemayor_explain; (2) external-AI — call filemayor_plan first (Anthropic / Gemini / OpenAI, by key), then call this with no arguments to execute that plan. The journal is written to disk so filemayor_rollback can undo it.',
159
+ inputSchema: {
160
+ type: 'object',
161
+ properties: {
162
+ moves: {
163
+ type: 'array',
164
+ description: 'Optional. Array of move operations to execute. Each entry must have "src" (absolute source path) and "dst" (absolute destination path). When provided, skips filemayor_plan entirely — use this when Claude is generating the plan directly.',
165
+ items: {
166
+ type: 'object',
167
+ properties: {
168
+ src: { type: 'string', description: 'Absolute path of the file to move.' },
169
+ dst: { type: 'string', description: 'Absolute destination path.' },
170
+ },
171
+ required: ['src', 'dst'],
172
+ },
173
+ },
174
+ },
175
+ },
132
176
  },
133
177
  {
134
178
  name: 'filemayor_rollback',
@@ -253,17 +297,51 @@ async function handleTool(name, args) {
253
297
  const c = checkDir(args.path);
254
298
  if (c.error) return err(c.error);
255
299
  if (!args.prompt) return err('prompt is required');
256
- const apiKey = process.env.GEMINI_API_KEY || '';
257
- if (!apiKey) return err('GEMINI_API_KEY not set. filemayor_plan needs it for AI planning.');
258
- const engine = new CureEngine(c.resolved, apiKey);
259
- const plan = await engine.generatePlan(args.prompt);
260
- activeCureEngine = engine;
261
- return ok(plan);
300
+ const ai = detectAiProvider();
301
+ if (!ai) {
302
+ return err(
303
+ 'filemayor_plan needs an AI provider key for non-Claude clients. Set one of:\n' +
304
+ ' ANTHROPIC_API_KEY Claude (claude-haiku-4-5-20251001)\n' +
305
+ ' GEMINI_API_KEY → Gemini (gemini-2.0-flash)\n' +
306
+ ' OPENAI_API_KEY → GPT-4o-mini\n\n' +
307
+ 'If Claude is your MCP client you do NOT need this tool — Claude is already the AI. ' +
308
+ 'Instead: call filemayor_explain to audit the folder, reason about the moves yourself, ' +
309
+ 'then pass them directly to filemayor_apply as the "moves" parameter.'
310
+ );
311
+ }
312
+ // Route the core planner to the selected provider. Restore the
313
+ // previous env afterwards so we never leak state between calls.
314
+ const prevProvider = process.env.FM_AI_PROVIDER;
315
+ const prevKey = process.env.FM_AI_API_KEY;
316
+ process.env.FM_AI_PROVIDER = ai.provider;
317
+ process.env.FM_AI_API_KEY = ai.apiKey;
318
+ try {
319
+ const engine = new CureEngine(c.resolved, ai.apiKey);
320
+ const plan = await engine.generatePlan(args.prompt);
321
+ activeCureEngine = engine;
322
+ return ok({ provider: ai.label, ...plan });
323
+ } finally {
324
+ if (prevProvider === undefined) delete process.env.FM_AI_PROVIDER;
325
+ else process.env.FM_AI_PROVIDER = prevProvider;
326
+ if (prevKey === undefined) delete process.env.FM_AI_API_KEY;
327
+ else process.env.FM_AI_API_KEY = prevKey;
328
+ }
262
329
  }
263
330
 
264
331
  case 'filemayor_apply': {
332
+ // Claude-native path: caller supplies explicit move list.
333
+ if (Array.isArray(args.moves) && args.moves.length > 0) {
334
+ const applyer = new ApplyEngine();
335
+ const plan = args.moves.map(m => ({ source: m.src, destination: m.dst }));
336
+ const results = await applyer.apply(plan);
337
+ return ok(results);
338
+ }
339
+ // External-planner path: filemayor_plan must have run first.
265
340
  if (!activeCureEngine || !activeCureEngine.plan) {
266
- return err('No active plan. Call filemayor_plan first.');
341
+ return err(
342
+ 'No active plan. Either call filemayor_plan first, or pass a "moves" array directly ' +
343
+ '(recommended when Claude is the client).'
344
+ );
267
345
  }
268
346
  const applyer = new ApplyEngine();
269
347
  const results = await applyer.apply(activeCureEngine.plan);
@@ -381,7 +459,11 @@ async function handleTool(name, args) {
381
459
  node: process.version,
382
460
  platform: `${process.platform} ${process.arch}`,
383
461
  license: getLicenseInfo?.() ?? { tier: 'free', name: 'Free' },
384
- geminiConfigured: !!process.env.GEMINI_API_KEY,
462
+ aiProviders: {
463
+ anthropic: !!process.env.ANTHROPIC_API_KEY,
464
+ gemini: !!process.env.GEMINI_API_KEY,
465
+ openai: !!process.env.OPENAI_API_KEY,
466
+ },
385
467
  });
386
468
  }
387
469
 
@@ -407,9 +489,16 @@ if (process.argv.includes('--audit')) {
407
489
  'filemayor_undo_last',
408
490
  ]);
409
491
 
492
+ const activeProvider = detectAiProvider();
493
+ const PROVIDER_ENDPOINTS = {
494
+ anthropic: 'https://api.anthropic.com',
495
+ gemini: 'https://generativelanguage.googleapis.com',
496
+ openai: 'https://api.openai.com',
497
+ };
498
+
410
499
  const report = {
411
500
  package: 'filemayor-mcp',
412
- version: '4.0.0',
501
+ version: PKG_VERSION,
413
502
  node: process.version,
414
503
  platform: `${process.platform}-${process.arch}`,
415
504
  transport: {
@@ -420,20 +509,21 @@ if (process.argv.includes('--audit')) {
420
509
  },
421
510
  outbound: {
422
511
  defaultEgress: 'none',
423
- optionalEgress: process.env.GEMINI_API_KEY
512
+ optionalEgress: activeProvider
424
513
  ? {
425
514
  enabled: true,
426
- destination: 'https://generativelanguage.googleapis.com',
515
+ destination: PROVIDER_ENDPOINTS[activeProvider.provider],
516
+ provider: activeProvider.label,
427
517
  trigger: 'filemayor_plan tool only',
428
518
  payload: 'directory metadata (paths, sizes, extensions). No file contents.',
429
- gatedBy: 'GEMINI_API_KEY environment variable',
519
+ gatedBy: `${activeProvider.env} environment variable`,
430
520
  }
431
521
  : {
432
522
  enabled: false,
433
- destination: 'https://generativelanguage.googleapis.com',
523
+ destinations: Object.values(PROVIDER_ENDPOINTS),
434
524
  trigger: 'filemayor_plan tool only',
435
525
  payload: 'directory metadata only — no file contents',
436
- gatedBy: 'GEMINI_API_KEY environment variable (currently NOT set)',
526
+ gatedBy: 'one of ANTHROPIC_API_KEY / GEMINI_API_KEY / OPENAI_API_KEY (none currently set)',
437
527
  },
438
528
  },
439
529
  runtimeSafeguards: {
@@ -458,7 +548,7 @@ if (process.argv.includes('--audit')) {
458
548
  },
459
549
  source: 'https://github.com/Hrypopo/FileMayor/blob/main/mcp/index.mjs',
460
550
  verifyBy: [
461
- "Read mcp/index.mjs end-to-end — it's ~410 lines.",
551
+ `Read mcp/index.mjs end-to-end — it's ${(() => { try { return fs.readFileSync(new URL(import.meta.url)).toString().split('\n').length; } catch { return 'a few hundred'; } })()} lines.`,
462
552
  "grep for outbound calls: rg \"fetch\\(|http\\.request|https\\.request|net\\.\" mcp/index.mjs",
463
553
  "Run `npm view filemayor-mcp` and compare the published shasum to the GitHub tag.",
464
554
  "Run this same `--audit` after upgrading to detect changes to transport / egress / tool list.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "filemayor-mcp",
3
- "version": "4.0.5",
3
+ "version": "4.0.7",
4
4
  "description": "FileMayor MCP server — drive the intelligent filesystem clerk from Claude, Cursor, or any MCP client.",
5
5
  "type": "module",
6
6
  "main": "index.mjs",