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.
- package/LICENSE +90 -0
- package/README.md +168 -0
- package/index.mjs +494 -0
- 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
|
+
[](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
|
+
}
|