filemayor-mcp 4.0.5

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 (4) hide show
  1. package/LICENSE +90 -0
  2. package/README.md +168 -0
  3. package/index.mjs +494 -0
  4. package/package.json +54 -0
package/LICENSE ADDED
@@ -0,0 +1,90 @@
1
+ FILEMAYOR PROPRIETARY LICENSE
2
+
3
+ Copyright (c) 2024-2026 Lehlohonolo Goodwill Nchefu (Chevza). All rights reserved.
4
+
5
+ NOTICE: This software and all associated documentation files (the "Software")
6
+ are the proprietary property of Lehlohonolo Goodwill Nchefu (Chevza),
7
+ operating as FileMayor, and its contributors.
8
+
9
+ 1. GRANT OF LICENSE
10
+
11
+ Subject to the terms of this License, you are granted a limited,
12
+ non-exclusive, non-transferable, revocable license to:
13
+
14
+ a) USE the Software for personal, non-commercial purposes at no cost
15
+ ("Free Tier").
16
+
17
+ b) USE the Software for commercial purposes only upon purchasing a valid
18
+ commercial license from FileMayor ("Paid Tier").
19
+
20
+ 2. RESTRICTIONS
21
+
22
+ You may NOT:
23
+
24
+ a) Copy, modify, merge, publish, distribute, sublicense, or sell copies
25
+ of the Software without express written permission from FileMayor.
26
+
27
+ b) Reverse engineer, decompile, disassemble, or otherwise attempt to
28
+ derive the source code of compiled portions of the Software.
29
+
30
+ c) Remove or alter any proprietary notices, labels, or trademarks on
31
+ the Software.
32
+
33
+ d) Use the Software to create a competing product or service.
34
+
35
+ e) Use the Software's source code, in whole or in part, in any other
36
+ software product without a commercial license agreement.
37
+
38
+ 3. FREE TIER
39
+
40
+ The Free Tier grants individuals the right to:
41
+ - Use the desktop application for personal file organization
42
+ - Use the CLI tool for personal, non-commercial purposes
43
+ - Contribute improvements via pull requests (you retain copyright of
44
+ your contributions but grant FileMayor a perpetual license to use them)
45
+
46
+ 4. PAID TIER
47
+
48
+ Commercial use, including but not limited to:
49
+ - Enterprise deployment across multiple machines
50
+ - Integration into commercial products or workflows
51
+ - Use in a business or organizational setting
52
+ - Server / data center deployment
53
+ - SOP AI Engine features
54
+
55
+ requires a valid Paid Tier license. Contact licensing@filemayor.com
56
+ for commercial licensing inquiries.
57
+
58
+ 5. AI FEATURES
59
+
60
+ The Software includes AI-powered features that connect to third-party
61
+ APIs (Google Gemini). Use of these features is subject to:
62
+ - The respective third-party API terms of service
63
+ - Data processing as described in FileMayor's Privacy Policy
64
+ - Availability and rate limits of the third-party service
65
+
66
+ 6. DATA & PRIVACY
67
+
68
+ The Software processes files locally on your device. AI features may
69
+ transmit document text (not file contents) to third-party APIs for
70
+ analysis. No personal data is collected, stored, or sold by FileMayor.
71
+
72
+ 7. NO WARRANTY
73
+
74
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
75
+ OR IMPLIED. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
76
+ FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM THE USE OF THE
77
+ SOFTWARE.
78
+
79
+ 8. TERMINATION
80
+
81
+ This License is effective until terminated. It terminates automatically
82
+ if you fail to comply with any term. Upon termination, you must destroy
83
+ all copies of the Software in your possession.
84
+
85
+ 9. GOVERNING LAW
86
+
87
+ This License shall be governed by the laws of the Republic of South Africa.
88
+
89
+ For licensing inquiries: licensing@filemayor.com
90
+ For support: support@filemayor.com
package/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # filemayor-mcp
2
+
3
+ **The FileMayor MCP server.** Drive your intelligent filesystem clerk from Claude, Cursor, Zed, or any [Model Context Protocol](https://modelcontextprotocol.io) client.
4
+
5
+ `v4.0.5` ยท Node โ‰ฅ20 ยท [filemayor.com/mcp](https://filemayor.com/mcp)
6
+
7
+ [![smithery badge](https://smithery.ai/badge/filemayor-mcp)](https://smithery.ai/server/filemayor-mcp)
8
+
9
+ ---
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install -g filemayor-mcp
15
+ ```
16
+
17
+ Or run without installing:
18
+
19
+ ```bash
20
+ npx -y filemayor-mcp
21
+ ```
22
+
23
+ ## Wire it into Claude Desktop
24
+
25
+ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
26
+
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "filemayor": {
31
+ "command": "npx",
32
+ "args": ["-y", "filemayor-mcp"],
33
+ "env": {
34
+ "GEMINI_API_KEY": "your-key-here"
35
+ }
36
+ }
37
+ }
38
+ }
39
+ ```
40
+
41
+ Restart Claude. You'll see the FileMayor tools in the ๐Ÿ”ง menu.
42
+
43
+ ## Wire it into Cursor / Zed / other MCP clients
44
+
45
+ Any client that speaks MCP over stdio works. Point it at `filemayor-mcp` and you're done.
46
+
47
+ ## Tools exposed
48
+
49
+ | Tool | What it does |
50
+ |:---|:---|
51
+ | `filemayor_scan` | List every file in a directory tree with size + category. |
52
+ | `filemayor_analyze` | Deep audit: duplicates, bloat, junk, largest dirs. |
53
+ | `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`. |
55
+ | `filemayor_apply` | Execute the most recent plan from `filemayor_plan`. |
56
+ | `filemayor_rollback` | Reverse the most recent applied plan. |
57
+ | `filemayor_organize` | Deterministic auto-organize by extension (no AI). |
58
+ | `filemayor_clean` | Find junk files (temp, cache, .DS_Store, Thumbs.db). |
59
+ | `filemayor_duplicates` | Hash-based duplicate detection. |
60
+ | `filemayor_dedupe` | Find AND remove duplicates. Destructive. |
61
+ | `filemayor_delete_files` | Delete explicit file paths with hardened-runtime validation. |
62
+ | `filemayor_history` | Recent move journal. |
63
+ | `filemayor_undo_last` | Undo the last N moves. |
64
+ | `filemayor_info` | Version, runtime, license tier. |
65
+
66
+ ## The Curative Triad
67
+
68
+ The headline workflow:
69
+
70
+ ```
71
+ filemayor_explain โ†’ filemayor_plan โ†’ filemayor_apply
72
+ โ†“ โ†“ โ†“
73
+ "diagnose" "propose" "execute"
74
+ (always safe) (no mutations) (journaled)
75
+ ```
76
+
77
+ Every applied plan can be reversed with `filemayor_rollback`.
78
+
79
+ ## Security & threat model
80
+
81
+ A recent viral post warned about MCP servers exposing API keys, email access, AWS infrastructure maps, and clinical-trial data to anonymous internet scans. The threat is real for some MCP servers โ€” but it doesn't generalize. Here's exactly where FileMayor sits against each part of that threat model, plus how you can verify the claims yourself.
82
+
83
+ ### Self-audit (verify before you trust)
84
+
85
+ Before you wire this server into your MCP client, run:
86
+
87
+ ```bash
88
+ npx -y filemayor-mcp --audit
89
+ ```
90
+
91
+ It prints a structured JSON report โ€” transport type, network listeners, outbound destinations, tool list (with destructive flags), runtime safeguards, and a `verifyBy` checklist. The report exits with code 0 and **does not start an MCP session**, so you can run it on any machine, diff it across upgrades, or pipe it into your own checks.
92
+
93
+ ### Threat-by-threat
94
+
95
+ **1. "Scannable from the internet ยท no auth ยท exposed credentials"**
96
+
97
+ Doesn't apply. FileMayor MCP uses **stdio transport only**. The server is launched as a subprocess of your MCP client (Claude Desktop, Cursor, Zed, Continue) and communicates over the subprocess's stdin/stdout. There is no HTTP listener, no SSE endpoint, no WebSocket, no port to scan. `--audit` confirms `transport.networkListeners: false`.
98
+
99
+ **2. "Starlette / FastAPI / Python CVE"**
100
+
101
+ Doesn't apply. FileMayor MCP is **Node.js**, not Python. Its single runtime dependency is `@modelcontextprotocol/sdk` (the official Anthropic Node SDK). No Starlette, no FastAPI, no ASGI, no Python in the runtime path. `--audit` confirms `dependencies.python: false`.
102
+
103
+ **3. "A tool's stated purpose hides exfiltration" (e.g., a `summarize_webpage` tool that also emails out credentials)**
104
+
105
+ This is the supply-chain risk that applies to *every* MCP server in principle, ours included. Our mitigations:
106
+
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.
108
+ - `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.
111
+
112
+ ### What `--audit` reports
113
+
114
+ ```json
115
+ {
116
+ "package": "filemayor-mcp",
117
+ "version": "4.0.0",
118
+ "transport": { "type": "stdio", "networkListeners": false, "ports": [] },
119
+ "outbound": {
120
+ "defaultEgress": "none",
121
+ "optionalEgress": {
122
+ "enabled": false,
123
+ "destination": "https://generativelanguage.googleapis.com",
124
+ "trigger": "filemayor_plan tool only",
125
+ "payload": "directory metadata only โ€” no file contents",
126
+ "gatedBy": "GEMINI_API_KEY environment variable"
127
+ }
128
+ },
129
+ "runtimeSafeguards": {
130
+ "pathJailing": true,
131
+ "systemDirBlocking": true,
132
+ "symlinkResolution": true,
133
+ "journaledMoves": true,
134
+ "rollbackAvailable": true
135
+ },
136
+ "tools": [ /* 14 entries, each with `destructive: true|false` */ ],
137
+ "dependencies": { "runtime": ["@modelcontextprotocol/sdk"], "python": false, "starlette": false, "fastapi": false },
138
+ "source": "https://github.com/Hrypopo/FileMayor/blob/main/mcp/index.mjs",
139
+ "verifyBy": [
140
+ "Read mcp/index.mjs end-to-end โ€” it's ~410 lines.",
141
+ "grep for outbound calls: rg \"fetch\\(|http\\.request|https\\.request|net\\.\" mcp/index.mjs",
142
+ "Run `npm view filemayor-mcp` and compare the published shasum to the GitHub tag.",
143
+ "Run this same `--audit` after upgrading to detect changes to transport / egress / tool list."
144
+ ]
145
+ }
146
+ ```
147
+
148
+ ### A note on third-party "MCP audit" tools
149
+
150
+ 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
+
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.
153
+
154
+ ### Hardened-runtime safeguards (the engine, not just the MCP shell)
155
+
156
+ Every tool that touches disk runs through the same security layers the CLI and Desktop app use:
157
+
158
+ | Layer | Purpose |
159
+ |:---|:---|
160
+ | Path jailing | Symlinks resolved before validation. System dirs (`/System`, `C:\Windows`, `/etc`, `/proc`, โ€ฆ) are blocked. |
161
+ | Plan-then-apply gate | `_explain` and `_plan` are read-only. Only `_apply` mutates disk. |
162
+ | Journaled moves | Every move is recorded to a write-ahead log on disk. |
163
+ | 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. |
165
+
166
+ ## License
167
+
168
+ Proprietary โ€” ยฉ 2026 Lehlohonolo Goodwill Nchefu (Chevza)
package/index.mjs ADDED
@@ -0,0 +1,494 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
4
+ * FILEMAYOR โ€” MCP SERVER
5
+ *
6
+ * Exposes the FileMayor core engine as Model Context Protocol tools so
7
+ * Claude, Cursor, or any MCP-compatible client can drive your
8
+ * filesystem clerk programmatically.
9
+ *
10
+ * Run with: filemayor-mcp
11
+ * Or: node index.mjs
12
+ *
13
+ * All operations honor the same hardened-runtime safeguards as the CLI
14
+ * and Desktop app: path jailing, system-dir blocking, journaled moves,
15
+ * full rollback. AI-driven planning still requires GEMINI_API_KEY.
16
+ * โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
17
+ */
18
+
19
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
20
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
21
+ import {
22
+ CallToolRequestSchema,
23
+ ListToolsRequestSchema,
24
+ } from '@modelcontextprotocol/sdk/types.js';
25
+ import { createRequire } from 'node:module';
26
+ import path from 'node:path';
27
+ import fs from 'node:fs';
28
+ import os from 'node:os';
29
+
30
+ const require = createRequire(import.meta.url);
31
+ const PKG_VERSION = require('./package.json').version;
32
+
33
+ // Load FileMayor core engine (CJS) from the parent package
34
+ const core = require('../cli/core');
35
+ const { scan } = require('../cli/core/scanner');
36
+ const { organize } = require('../cli/core/organizer');
37
+ const { findJunk } = require('../cli/core/cleaner');
38
+ const { analyzeDirectory } = require('../cli/core/analyzer');
39
+ const { validatePath, isDirSafe, isFileSafe } = require('../cli/core/security');
40
+ const FileMayorFS = require('../cli/core/fs-abstraction');
41
+
42
+ const { ExplainEngine, CureEngine, ApplyEngine, DedupeEngine, getLicenseInfo } = core;
43
+
44
+ // History file shared with the desktop app + CLI
45
+ const HISTORY_FILE = path.join(os.homedir(), '.filemayor-mcp-history.json');
46
+
47
+ // In-process state for the curative triad
48
+ let activeCureEngine = null;
49
+
50
+ // โ”€โ”€โ”€ Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
51
+
52
+ function loadHistory() {
53
+ try { return JSON.parse(fs.readFileSync(HISTORY_FILE, 'utf8')); }
54
+ catch { return []; }
55
+ }
56
+
57
+ function saveHistory(history) {
58
+ try { fs.writeFileSync(HISTORY_FILE, JSON.stringify(history)); } catch {}
59
+ }
60
+
61
+ function ok(data) {
62
+ return { content: [{ type: 'text', text: typeof data === 'string' ? data : JSON.stringify(data, null, 2) }] };
63
+ }
64
+
65
+ function err(message) {
66
+ return { isError: true, content: [{ type: 'text', text: `Error: ${message}` }] };
67
+ }
68
+
69
+ function checkDir(p) {
70
+ if (!p || typeof p !== 'string') return { error: 'path is required' };
71
+ const v = validatePath(p);
72
+ if (!v.valid) return { error: v.error };
73
+ const s = isDirSafe(v.resolved);
74
+ if (!s.safe) return { error: s.reason };
75
+ return { resolved: v.resolved };
76
+ }
77
+
78
+ // โ”€โ”€โ”€ Tool definitions โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
79
+
80
+ const TOOLS = [
81
+ {
82
+ name: 'filemayor_scan',
83
+ description: 'List every file in a directory tree with size, modified date, and detected category. Read-only โ€” no mutations.',
84
+ inputSchema: {
85
+ type: 'object',
86
+ properties: {
87
+ path: { type: 'string', description: 'Absolute path to the directory to scan.' },
88
+ maxDepth: { type: 'number', default: 10, description: 'How deep to recurse.' },
89
+ includeHidden: { type: 'boolean', default: false },
90
+ },
91
+ required: ['path'],
92
+ },
93
+ },
94
+ {
95
+ name: 'filemayor_analyze',
96
+ description: 'Deep audit of a directory: duplicates, bloat, junk categories, largest subdirs, potential space recovery. Read-only.',
97
+ inputSchema: {
98
+ type: 'object',
99
+ properties: {
100
+ path: { type: 'string', description: 'Absolute path to analyze.' },
101
+ },
102
+ required: ['path'],
103
+ },
104
+ },
105
+ {
106
+ name: 'filemayor_explain',
107
+ description: 'The "diagnose" half of the Curative Triad. Computes a folder health score (0โ€“100), detects atomic bundles to protect, and surfaces actionable insights. Read-only.',
108
+ inputSchema: {
109
+ type: 'object',
110
+ properties: {
111
+ path: { type: 'string', description: 'Absolute path to diagnose.' },
112
+ },
113
+ required: ['path'],
114
+ },
115
+ },
116
+ {
117
+ 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.',
119
+ inputSchema: {
120
+ type: 'object',
121
+ properties: {
122
+ path: { type: 'string', description: 'Absolute path to plan against.' },
123
+ prompt: { type: 'string', description: 'Natural-language description of what to do. e.g. "organize by type but keep project folders intact".' },
124
+ },
125
+ required: ['path', 'prompt'],
126
+ },
127
+ },
128
+ {
129
+ 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: {} },
132
+ },
133
+ {
134
+ name: 'filemayor_rollback',
135
+ description: 'Reverse the most recent applied plan using the journal. Restores files to their original locations.',
136
+ inputSchema: { type: 'object', properties: {} },
137
+ },
138
+ {
139
+ name: 'filemayor_organize',
140
+ description: 'Deterministic auto-organize (no AI): sorts files into category folders by extension. Use dryRun:true to preview without moving.',
141
+ inputSchema: {
142
+ type: 'object',
143
+ properties: {
144
+ path: { type: 'string', description: 'Absolute path to organize.' },
145
+ dryRun: { type: 'boolean', default: true, description: 'If true, returns the plan without executing.' },
146
+ naming: { type: 'string', enum: ['original', 'category_prefix', 'date_prefix', 'clean'], default: 'original' },
147
+ duplicateStrategy: { type: 'string', enum: ['rename', 'skip', 'overwrite'], default: 'rename' },
148
+ },
149
+ required: ['path'],
150
+ },
151
+ },
152
+ {
153
+ name: 'filemayor_clean',
154
+ description: 'Find junk files (temp, cache, .DS_Store, Thumbs.db, logs) inside a directory. Returns the list without deleting. Pair with filemayor_delete_files to remove.',
155
+ inputSchema: {
156
+ type: 'object',
157
+ properties: {
158
+ path: { type: 'string', description: 'Absolute path to scan for junk.' },
159
+ categories: {
160
+ type: 'array',
161
+ items: { type: 'string', enum: ['temp', 'cache', 'system', 'logs'] },
162
+ default: ['temp', 'cache', 'system'],
163
+ },
164
+ },
165
+ required: ['path'],
166
+ },
167
+ },
168
+ {
169
+ name: 'filemayor_duplicates',
170
+ description: 'Hash-based duplicate file detection. Returns groups of identical files with total wasted space.',
171
+ inputSchema: {
172
+ type: 'object',
173
+ properties: {
174
+ path: { type: 'string', description: 'Absolute path to search for duplicates.' },
175
+ },
176
+ required: ['path'],
177
+ },
178
+ },
179
+ {
180
+ name: 'filemayor_dedupe',
181
+ description: 'Find AND remove duplicate files (keeps the first occurrence in each group). Destructive โ€” use filemayor_duplicates first to preview.',
182
+ inputSchema: {
183
+ type: 'object',
184
+ properties: {
185
+ path: { type: 'string', description: 'Absolute path to dedupe.' },
186
+ },
187
+ required: ['path'],
188
+ },
189
+ },
190
+ {
191
+ name: 'filemayor_delete_files',
192
+ description: 'Permanently delete a list of file paths. Each path is validated against the hardened-runtime safeguards (no system dirs, no path traversal). Destructive.',
193
+ inputSchema: {
194
+ type: 'object',
195
+ properties: {
196
+ paths: { type: 'array', items: { type: 'string' }, description: 'Absolute paths to delete.' },
197
+ },
198
+ required: ['paths'],
199
+ },
200
+ },
201
+ {
202
+ name: 'filemayor_history',
203
+ description: 'Return the journal of recent moves (source โ†’ destination + timestamp) so they can be inspected before rollback.',
204
+ inputSchema: { type: 'object', properties: {} },
205
+ },
206
+ {
207
+ name: 'filemayor_undo_last',
208
+ description: 'Undo the last N moves from the move journal. Each move is restored to its original location.',
209
+ inputSchema: {
210
+ type: 'object',
211
+ properties: {
212
+ count: { type: 'number', default: 1, minimum: 1 },
213
+ },
214
+ },
215
+ },
216
+ {
217
+ name: 'filemayor_info',
218
+ description: 'Report FileMayor version, runtime, and license tier.',
219
+ inputSchema: { type: 'object', properties: {} },
220
+ },
221
+ ];
222
+
223
+ // โ”€โ”€โ”€ Tool implementations โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
224
+
225
+ async function handleTool(name, args) {
226
+ switch (name) {
227
+ case 'filemayor_scan': {
228
+ const c = checkDir(args.path);
229
+ if (c.error) return err(c.error);
230
+ const r = scan(c.resolved, {
231
+ maxDepth: args.maxDepth ?? 10,
232
+ includeHidden: args.includeHidden ?? false,
233
+ });
234
+ return ok({ stats: r.stats, fileCount: r.files.length, files: r.files.slice(0, 500) });
235
+ }
236
+
237
+ case 'filemayor_analyze': {
238
+ const c = checkDir(args.path);
239
+ if (c.error) return err(c.error);
240
+ const r = await analyzeDirectory(c.resolved);
241
+ return ok(r);
242
+ }
243
+
244
+ case 'filemayor_explain': {
245
+ const c = checkDir(args.path);
246
+ if (c.error) return err(c.error);
247
+ const engine = new ExplainEngine();
248
+ const r = await engine.run(c.resolved);
249
+ return ok(r);
250
+ }
251
+
252
+ case 'filemayor_plan': {
253
+ const c = checkDir(args.path);
254
+ if (c.error) return err(c.error);
255
+ 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);
262
+ }
263
+
264
+ case 'filemayor_apply': {
265
+ if (!activeCureEngine || !activeCureEngine.plan) {
266
+ return err('No active plan. Call filemayor_plan first.');
267
+ }
268
+ const applyer = new ApplyEngine();
269
+ const results = await applyer.apply(activeCureEngine.plan);
270
+ activeCureEngine = null;
271
+ return ok(results);
272
+ }
273
+
274
+ case 'filemayor_rollback': {
275
+ const fmfs = new FileMayorFS();
276
+ await fmfs.rollback();
277
+ return ok({ success: true });
278
+ }
279
+
280
+ case 'filemayor_organize': {
281
+ const c = checkDir(args.path);
282
+ if (c.error) return err(c.error);
283
+ const r = await organize(c.resolved, {
284
+ dryRun: args.dryRun ?? true,
285
+ naming: args.naming ?? 'original',
286
+ duplicateStrategy: args.duplicateStrategy ?? 'rename',
287
+ });
288
+ // Record successful moves in the shared history so filemayor_undo_last works
289
+ if (!r.dryRun && Array.isArray(r.results)) {
290
+ const history = loadHistory();
291
+ for (const item of r.results) {
292
+ if (item.status === 'success') {
293
+ history.push({ source: item.source, destination: item.destination, timestamp: Date.now() });
294
+ }
295
+ }
296
+ saveHistory(history);
297
+ }
298
+ return ok(r);
299
+ }
300
+
301
+ case 'filemayor_clean': {
302
+ const c = checkDir(args.path);
303
+ if (c.error) return err(c.error);
304
+ const r = findJunk(c.resolved, {
305
+ maxDepth: 5,
306
+ categories: args.categories ?? ['temp', 'cache', 'system'],
307
+ });
308
+ return ok({ count: r.junk.length, junk: r.junk });
309
+ }
310
+
311
+ case 'filemayor_duplicates': {
312
+ const c = checkDir(args.path);
313
+ if (c.error) return err(c.error);
314
+ const engine = new DedupeEngine();
315
+ const r = await engine.find(c.resolved);
316
+ return ok(r);
317
+ }
318
+
319
+ case 'filemayor_dedupe': {
320
+ const c = checkDir(args.path);
321
+ if (c.error) return err(c.error);
322
+ const engine = new DedupeEngine();
323
+ const dupes = await engine.find(c.resolved);
324
+ const r = await engine.clean(dupes);
325
+ return ok(r);
326
+ }
327
+
328
+ case 'filemayor_delete_files': {
329
+ if (!Array.isArray(args.paths) || args.paths.length === 0) return err('paths array is required');
330
+ const deleted = [];
331
+ const errors = [];
332
+ for (const p of args.paths) {
333
+ if (typeof p !== 'string') { errors.push({ path: p, error: 'not a string' }); continue; }
334
+ const v = validatePath(p);
335
+ if (!v.valid) { errors.push({ path: p, error: v.error }); continue; }
336
+ const s = isFileSafe(v.resolved);
337
+ if (!s.safe) { errors.push({ path: p, error: s.reason }); continue; }
338
+ try {
339
+ fs.unlinkSync(v.resolved);
340
+ deleted.push(v.resolved);
341
+ } catch (e) {
342
+ errors.push({ path: p, error: e.message });
343
+ }
344
+ }
345
+ return ok({ deleted: deleted.length, errors });
346
+ }
347
+
348
+ case 'filemayor_history': {
349
+ return ok({ history: loadHistory() });
350
+ }
351
+
352
+ case 'filemayor_undo_last': {
353
+ const count = Math.max(1, args.count ?? 1);
354
+ const history = loadHistory();
355
+ const undone = [];
356
+ for (let i = 0; i < count && history.length > 0; i++) {
357
+ const move = history.pop();
358
+ try {
359
+ fs.mkdirSync(path.dirname(move.source), { recursive: true });
360
+ fs.renameSync(move.destination, move.source);
361
+ undone.push(move);
362
+ } catch {
363
+ try {
364
+ fs.copyFileSync(move.destination, move.source);
365
+ fs.unlinkSync(move.destination);
366
+ undone.push(move);
367
+ } catch (e2) {
368
+ history.push(move);
369
+ break;
370
+ }
371
+ }
372
+ }
373
+ saveHistory(history);
374
+ return ok({ undone: undone.length, remaining: history.length, moves: undone });
375
+ }
376
+
377
+ case 'filemayor_info': {
378
+ return ok({
379
+ name: 'FileMayor MCP',
380
+ version: PKG_VERSION,
381
+ node: process.version,
382
+ platform: `${process.platform} ${process.arch}`,
383
+ license: getLicenseInfo?.() ?? { tier: 'free', name: 'Free' },
384
+ geminiConfigured: !!process.env.GEMINI_API_KEY,
385
+ });
386
+ }
387
+
388
+ default:
389
+ return err(`Unknown tool: ${name}`);
390
+ }
391
+ }
392
+
393
+ // โ”€โ”€โ”€ Self-audit (--audit flag) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
394
+ // Prints a structured, machine-readable trust report and exits.
395
+ // Intended use: any user can run `npx -y filemayor-mcp --audit` to see
396
+ // transport, network exposure, tools exposed, and optional outbound
397
+ // destinations BEFORE wiring this server into their MCP client. The
398
+ // output is the same blob the README documents so it can be diffed
399
+ // across versions to catch supply-chain tampering.
400
+ if (process.argv.includes('--audit')) {
401
+ const destructiveTools = new Set([
402
+ 'filemayor_apply',
403
+ 'filemayor_rollback',
404
+ 'filemayor_organize',
405
+ 'filemayor_dedupe',
406
+ 'filemayor_delete_files',
407
+ 'filemayor_undo_last',
408
+ ]);
409
+
410
+ const report = {
411
+ package: 'filemayor-mcp',
412
+ version: '4.0.0',
413
+ node: process.version,
414
+ platform: `${process.platform}-${process.arch}`,
415
+ transport: {
416
+ type: 'stdio',
417
+ networkListeners: false,
418
+ ports: [],
419
+ note: 'Server runs as a subprocess of the MCP client; no HTTP/SSE/WebSocket listener exists. Not reachable from the network.',
420
+ },
421
+ outbound: {
422
+ defaultEgress: 'none',
423
+ optionalEgress: process.env.GEMINI_API_KEY
424
+ ? {
425
+ enabled: true,
426
+ destination: 'https://generativelanguage.googleapis.com',
427
+ trigger: 'filemayor_plan tool only',
428
+ payload: 'directory metadata (paths, sizes, extensions). No file contents.',
429
+ gatedBy: 'GEMINI_API_KEY environment variable',
430
+ }
431
+ : {
432
+ enabled: false,
433
+ destination: 'https://generativelanguage.googleapis.com',
434
+ trigger: 'filemayor_plan tool only',
435
+ payload: 'directory metadata only โ€” no file contents',
436
+ gatedBy: 'GEMINI_API_KEY environment variable (currently NOT set)',
437
+ },
438
+ },
439
+ runtimeSafeguards: {
440
+ pathJailing: true,
441
+ systemDirBlocking: true,
442
+ symlinkResolution: true,
443
+ journaledMoves: true,
444
+ rollbackAvailable: true,
445
+ details: 'See cli/core/security.js (validatePath, isDirSafe, isFileSafe)',
446
+ },
447
+ tools: TOOLS.map(t => ({
448
+ name: t.name,
449
+ destructive: destructiveTools.has(t.name),
450
+ description: t.description?.slice(0, 200),
451
+ })),
452
+ toolCount: TOOLS.length,
453
+ dependencies: {
454
+ runtime: ['@modelcontextprotocol/sdk'],
455
+ python: false,
456
+ starlette: false,
457
+ fastapi: false,
458
+ },
459
+ source: 'https://github.com/Hrypopo/FileMayor/blob/main/mcp/index.mjs',
460
+ verifyBy: [
461
+ "Read mcp/index.mjs end-to-end โ€” it's ~410 lines.",
462
+ "grep for outbound calls: rg \"fetch\\(|http\\.request|https\\.request|net\\.\" mcp/index.mjs",
463
+ "Run `npm view filemayor-mcp` and compare the published shasum to the GitHub tag.",
464
+ "Run this same `--audit` after upgrading to detect changes to transport / egress / tool list.",
465
+ ],
466
+ generatedAt: new Date().toISOString(),
467
+ };
468
+
469
+ process.stdout.write(JSON.stringify(report, null, 2) + '\n');
470
+ process.exit(0);
471
+ }
472
+
473
+ // โ”€โ”€โ”€ Server bootstrap โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
474
+
475
+ const server = new Server(
476
+ { name: 'filemayor-mcp', version: PKG_VERSION },
477
+ { capabilities: { tools: {} } }
478
+ );
479
+
480
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
481
+
482
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
483
+ try {
484
+ return await handleTool(request.params.name, request.params.arguments || {});
485
+ } catch (e) {
486
+ return err(e?.message || String(e));
487
+ }
488
+ });
489
+
490
+ const transport = new StdioServerTransport();
491
+ await server.connect(transport);
492
+
493
+ // Quiet startup notice on stderr so MCP stdio framing isn't disturbed
494
+ process.stderr.write(`filemayor-mcp v${PKG_VERSION} โ€” ${TOOLS.length} tools ready\n`);
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "filemayor-mcp",
3
+ "version": "4.0.5",
4
+ "description": "FileMayor MCP server โ€” drive the intelligent filesystem clerk from Claude, Cursor, or any MCP client.",
5
+ "type": "module",
6
+ "main": "index.mjs",
7
+ "bin": {
8
+ "filemayor-mcp": "./index.mjs"
9
+ },
10
+ "files": [
11
+ "index.mjs",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "engines": {
16
+ "node": ">=20.20.0"
17
+ },
18
+ "keywords": [
19
+ "mcp",
20
+ "model-context-protocol",
21
+ "filemayor",
22
+ "filesystem",
23
+ "file-organization",
24
+ "file-manager",
25
+ "local-first",
26
+ "reversible",
27
+ "claude",
28
+ "cursor",
29
+ "zed",
30
+ "anthropic",
31
+ "ai",
32
+ "llm",
33
+ "agent"
34
+ ],
35
+ "mcp": {
36
+ "startCommand": "npx -y filemayor-mcp",
37
+ "transport": "stdio",
38
+ "auditCommand": "npx -y filemayor-mcp --audit"
39
+ },
40
+ "author": {
41
+ "name": "Lehlohonolo Goodwill Nchefu (Chevza)",
42
+ "email": "hloninchefu@gmail.com"
43
+ },
44
+ "license": "PROPRIETARY",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/Hrypopo/FileMayor.git",
48
+ "directory": "mcp"
49
+ },
50
+ "homepage": "https://filemayor.com",
51
+ "dependencies": {
52
+ "@modelcontextprotocol/sdk": "^1.0.4"
53
+ }
54
+ }