filemayor-mcp 4.0.6 → 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 +12 -8
  2. package/index.mjs +63 -18
  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
 
@@ -103,9 +103,9 @@ Doesn't apply. FileMayor MCP is **Node.js**, not Python. Its single runtime depe
103
103
 
104
104
  This is the supply-chain risk that applies to *every* MCP server in principle, ours included. Our mitigations:
105
105
 
106
- - 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.)
107
107
  - `grep -E "fetch\(|http\.request|https\.request|net\." mcp/index.mjs` returns nothing. The server makes no outbound network calls of its own.
108
- - The *only* off-machine egress path is the optional AI call inside `filemayor_plan`, gated by an AI provider key (`ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, or `OPENAI_API_KEY`). 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.
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
109
  - The GitHub repo is the canonical source.
110
110
 
111
111
  ### What `--audit` reports
@@ -113,16 +113,20 @@ This is the supply-chain risk that applies to *every* MCP server in principle, o
113
113
  ```json
114
114
  {
115
115
  "package": "filemayor-mcp",
116
- "version": "4.0.0",
116
+ "version": "4.0.7",
117
117
  "transport": { "type": "stdio", "networkListeners": false, "ports": [] },
118
118
  "outbound": {
119
119
  "defaultEgress": "none",
120
120
  "optionalEgress": {
121
121
  "enabled": false,
122
- "destination": "ai provider API (Anthropic / Google / OpenAI — whichever key is set)",
122
+ "destinations": [
123
+ "https://api.anthropic.com",
124
+ "https://generativelanguage.googleapis.com",
125
+ "https://api.openai.com"
126
+ ],
123
127
  "trigger": "filemayor_plan tool only (not used when Claude is the MCP client)",
124
128
  "payload": "directory metadata only — no file contents",
125
- "gatedBy": "ANTHROPIC_API_KEY, GEMINI_API_KEY, or OPENAI_API_KEY environment variable"
129
+ "gatedBy": "one of ANTHROPIC_API_KEY / GEMINI_API_KEY / OPENAI_API_KEY (none currently set)"
126
130
  }
127
131
  },
128
132
  "runtimeSafeguards": {
@@ -136,7 +140,7 @@ This is the supply-chain risk that applies to *every* MCP server in principle, o
136
140
  "dependencies": { "runtime": ["@modelcontextprotocol/sdk"], "python": false, "starlette": false, "fastapi": false },
137
141
  "source": "https://github.com/Hrypopo/FileMayor/blob/main/mcp/index.mjs",
138
142
  "verifyBy": [
139
- "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.",
140
144
  "grep for outbound calls: rg \"fetch\\(|http\\.request|https\\.request|net\\.\" mcp/index.mjs",
141
145
  "Run `npm view filemayor-mcp` and compare the published shasum to the GitHub tag.",
142
146
  "Run this same `--audit` after upgrading to detect changes to transport / egress / tool list."
@@ -148,7 +152,7 @@ This is the supply-chain risk that applies to *every* MCP server in principle, o
148
152
 
149
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.
150
154
 
151
- 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.
152
156
 
153
157
  ### Hardened-runtime safeguards (the engine, not just the MCP shell)
154
158
 
package/index.mjs CHANGED
@@ -16,10 +16,13 @@
16
16
  *
17
17
  * When Claude is the MCP client, Claude IS the AI — no external API key
18
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:
20
- * ANTHROPIC_API_KEY → Claude claude-haiku-4-5-20251001
21
- * GEMINI_API_KEY Gemini Flash
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)
22
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.
23
26
  * ═══════════════════════════════════════════════════════════════════
24
27
  */
25
28
 
@@ -82,6 +85,24 @@ function checkDir(p) {
82
85
  return { resolved: v.resolved };
83
86
  }
84
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
+
85
106
  // ─── Tool definitions ──────────────────────────────────────────────
86
107
 
87
108
  const TOOLS = [
@@ -134,7 +155,7 @@ const TOOLS = [
134
155
  },
135
156
  {
136
157
  name: 'filemayor_apply',
137
- 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) Gemini/external — call filemayor_plan first, then call this with no arguments to execute that plan. The journal is written to disk so filemayor_rollback can undo it.',
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.',
138
159
  inputSchema: {
139
160
  type: 'object',
140
161
  properties: {
@@ -276,19 +297,35 @@ async function handleTool(name, args) {
276
297
  const c = checkDir(args.path);
277
298
  if (c.error) return err(c.error);
278
299
  if (!args.prompt) return err('prompt is required');
279
- const apiKey = process.env.GEMINI_API_KEY || '';
280
- if (!apiKey) {
300
+ const ai = detectAiProvider();
301
+ if (!ai) {
281
302
  return err(
282
- 'filemayor_plan requires GEMINI_API_KEY (the underlying planner uses the Gemini API).\n\n' +
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' +
283
307
  'If Claude is your MCP client you do NOT need this tool — Claude is already the AI. ' +
284
308
  'Instead: call filemayor_explain to audit the folder, reason about the moves yourself, ' +
285
309
  'then pass them directly to filemayor_apply as the "moves" parameter.'
286
310
  );
287
311
  }
288
- const engine = new CureEngine(c.resolved, apiKey);
289
- const plan = await engine.generatePlan(args.prompt);
290
- activeCureEngine = engine;
291
- return ok(plan);
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
+ }
292
329
  }
293
330
 
294
331
  case 'filemayor_apply': {
@@ -452,9 +489,16 @@ if (process.argv.includes('--audit')) {
452
489
  'filemayor_undo_last',
453
490
  ]);
454
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
+
455
499
  const report = {
456
500
  package: 'filemayor-mcp',
457
- version: '4.0.0',
501
+ version: PKG_VERSION,
458
502
  node: process.version,
459
503
  platform: `${process.platform}-${process.arch}`,
460
504
  transport: {
@@ -465,20 +509,21 @@ if (process.argv.includes('--audit')) {
465
509
  },
466
510
  outbound: {
467
511
  defaultEgress: 'none',
468
- optionalEgress: process.env.GEMINI_API_KEY
512
+ optionalEgress: activeProvider
469
513
  ? {
470
514
  enabled: true,
471
- destination: 'https://generativelanguage.googleapis.com',
515
+ destination: PROVIDER_ENDPOINTS[activeProvider.provider],
516
+ provider: activeProvider.label,
472
517
  trigger: 'filemayor_plan tool only',
473
518
  payload: 'directory metadata (paths, sizes, extensions). No file contents.',
474
- gatedBy: 'GEMINI_API_KEY environment variable',
519
+ gatedBy: `${activeProvider.env} environment variable`,
475
520
  }
476
521
  : {
477
522
  enabled: false,
478
- destination: 'https://generativelanguage.googleapis.com',
523
+ destinations: Object.values(PROVIDER_ENDPOINTS),
479
524
  trigger: 'filemayor_plan tool only',
480
525
  payload: 'directory metadata only — no file contents',
481
- 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)',
482
527
  },
483
528
  },
484
529
  runtimeSafeguards: {
@@ -503,7 +548,7 @@ if (process.argv.includes('--audit')) {
503
548
  },
504
549
  source: 'https://github.com/Hrypopo/FileMayor/blob/main/mcp/index.mjs',
505
550
  verifyBy: [
506
- "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.`,
507
552
  "grep for outbound calls: rg \"fetch\\(|http\\.request|https\\.request|net\\.\" mcp/index.mjs",
508
553
  "Run `npm view filemayor-mcp` and compare the published shasum to the GitHub tag.",
509
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.6",
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",