@n0zer0d4y/vulcan-file-ops 1.2.11 → 1.2.13
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/CHANGELOG.md +33 -0
- package/README.md +89 -39
- package/dist/cli.js +1 -1
- package/dist/server/index.js +12 -5
- package/dist/tools/filesystem-tools.js +15 -11
- package/dist/tools/read-tools.js +11 -3
- package/dist/tools/search-tools.js +3 -2
- package/dist/tools/shell-tool.js +2 -1
- package/dist/tools/write-tools.js +4 -3
- package/dist/utils/tool-schema.js +87 -0
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,39 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
8
8
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
|
|
12
|
+
## [1.2.13] - 2026-04-23
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Resolved the remaining MCP client tool discovery failure introduced after the SDK upgrade.
|
|
17
|
+
- Tool schemas exposed through `tools/list` are now normalized for stricter MCP clients.
|
|
18
|
+
- Removed client-hostile schema constructs from published tool input schemas, including `$schema`, `$ref`, `anyOf`, `oneOf`, and `allOf`.
|
|
19
|
+
- Replaced the highest-risk wire schemas with explicit compatibility-first object schemas for `attach_image` and `make_directory`.
|
|
20
|
+
- Preserved runtime backward compatibility while tightening the schemas advertised to clients.
|
|
21
|
+
- Fixed residual MCP mode detection drift so stdio mode is triggered reliably in local and `npx` execution paths.
|
|
22
|
+
- Added `zod` as a direct runtime dependency to avoid install-time fragility in clean environments and `npx` usage.
|
|
23
|
+
- Synchronized package metadata so published registry metadata and local package metadata report the same release version.
|
|
24
|
+
- Re-enabled and stabilized the previously skipped PDF-related Jest coverage.
|
|
25
|
+
- Enabled the 10 skipped document tests for PDF read, HTML-to-PDF write, mixed document batches, and PDF round-trip paths.
|
|
26
|
+
- Updated Jest PDF mocks so document tests exercise meaningful content paths instead of placeholder-only buffers.
|
|
27
|
+
- Suppressed expected negative-path test diagnostics inside tests so successful runs no longer look like runtime failures.
|
|
28
|
+
- Added a regression test to prevent incompatible tool schema shapes from reappearing in future releases.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- Documented the Codex-specific MCP configuration format in the README alongside the JSON-based client examples.
|
|
33
|
+
- Updated `npx` examples to use `-y` explicitly for non-interactive client execution.
|
|
34
|
+
|
|
35
|
+
## [1.2.12] - 2026-02-21
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- **CRITICAL**: Fixed MCP server toggle failure and "no tools configured" error caused by Node.js version incompatibility and restrictive MCP detection.
|
|
40
|
+
- **Node.js Compatibility**: Replaced `import ... with { type: "json" }` with manual `fs.readFileSync` for `package.json` to support Node.js versions earlier than 20.10.0 and 18.20.0 (including Node 14/16).
|
|
41
|
+
- **Robust MCP Detection**: Changed TTY detection from AND to OR logic. The server now enters MCP mode (suppressing console output) if *either* stdin or stdout is redirected, improving reliability on Windows when run via `npx`.
|
|
42
|
+
- **Silenced Dotenv**: Added `quiet: true` to `dotenv.config()` to prevent any leakage into the protocol stream.
|
|
43
|
+
- See `local_docs/RCA-MCP-Toggle-Failure-Node-Compatibility-2026-02-21.md` for full details.
|
|
11
44
|
|
|
12
45
|
## [1.2.11] - 2026-02-21
|
|
13
46
|
|
package/README.md
CHANGED
|
@@ -124,7 +124,10 @@ This server can be used directly with npx (recommended) or installed globally/lo
|
|
|
124
124
|
|
|
125
125
|
### Basic Configuration
|
|
126
126
|
|
|
127
|
-
Add to your MCP client configuration
|
|
127
|
+
Add to your MCP client configuration.
|
|
128
|
+
|
|
129
|
+
For JSON-based clients such as Claude Desktop and Cursor, use their `mcpServers` JSON format.
|
|
130
|
+
For Codex, use `C:\Users\<username>\.codex\config.toml` and the `mcp_servers` TOML table format shown below.
|
|
128
131
|
|
|
129
132
|
#### Option 1: Using npx (Recommended - No Installation Required)
|
|
130
133
|
|
|
@@ -132,12 +135,22 @@ Add to your MCP client configuration (e.g., `claude_desktop_config.json`):
|
|
|
132
135
|
{
|
|
133
136
|
"mcpServers": {
|
|
134
137
|
"vulcan-file-ops": {
|
|
135
|
-
"command": "npx",
|
|
136
|
-
"args": ["@n0zer0d4y/vulcan-file-ops"]
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
```
|
|
138
|
+
"command": "npx",
|
|
139
|
+
"args": ["-y", "@n0zer0d4y/vulcan-file-ops"]
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Codex (`config.toml`)**
|
|
146
|
+
|
|
147
|
+
```toml
|
|
148
|
+
[mcp_servers.vulcan_file_ops]
|
|
149
|
+
command = "npx"
|
|
150
|
+
args = ["-y", "@n0zer0d4y/vulcan-file-ops"]
|
|
151
|
+
enabled = true
|
|
152
|
+
startup_timeout_sec = 120.0
|
|
153
|
+
```
|
|
141
154
|
|
|
142
155
|
#### Option 2: Using Global Installation
|
|
143
156
|
|
|
@@ -167,7 +180,7 @@ After running `npm install @n0zer0d4y/vulcan-file-ops` in your project:
|
|
|
167
180
|
}
|
|
168
181
|
```
|
|
169
182
|
|
|
170
|
-
#### Option 4: Local Repository Execution (For Developers)
|
|
183
|
+
#### Option 4: Local Repository Execution (For Developers)
|
|
171
184
|
|
|
172
185
|
If you've cloned this repository and want to run from source:
|
|
173
186
|
|
|
@@ -178,20 +191,39 @@ npm install
|
|
|
178
191
|
npm run build
|
|
179
192
|
```
|
|
180
193
|
|
|
181
|
-
Then configure your MCP client:
|
|
182
|
-
|
|
183
|
-
```json
|
|
184
|
-
{
|
|
185
|
-
"mcpServers": {
|
|
186
|
-
"vulcan-file-ops": {
|
|
187
|
-
"command": "
|
|
188
|
-
"args": [
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
194
|
+
Then configure your MCP client:
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"mcpServers": {
|
|
199
|
+
"vulcan-file-ops": {
|
|
200
|
+
"command": "node",
|
|
201
|
+
"args": [
|
|
202
|
+
"/absolute/path/to/vulcan-file-ops/dist/cli.js",
|
|
203
|
+
"--approved-folders",
|
|
204
|
+
"/path/to/your/allowed/directories"
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Codex (`config.toml`)**
|
|
212
|
+
|
|
213
|
+
```toml
|
|
214
|
+
[mcp_servers.vulcan_file_ops]
|
|
215
|
+
command = "node"
|
|
216
|
+
args = [
|
|
217
|
+
'C:\absolute\path\to\vulcan-file-ops\dist\cli.js',
|
|
218
|
+
"--approved-folders",
|
|
219
|
+
'C:\path\to\your\allowed\directories'
|
|
220
|
+
]
|
|
221
|
+
cwd = 'C:\absolute\path\to\vulcan-file-ops'
|
|
222
|
+
enabled = true
|
|
223
|
+
startup_timeout_sec = 120.0
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Note:** For local repository execution, prefer `node dist/cli.js` with an absolute path. This works reliably in Codex and avoids PATH ambiguity.
|
|
195
227
|
|
|
196
228
|
### Advanced Configuration
|
|
197
229
|
|
|
@@ -205,11 +237,12 @@ Pre-configure specific directories for immediate access on server start:
|
|
|
205
237
|
{
|
|
206
238
|
"mcpServers": {
|
|
207
239
|
"vulcan-file-ops": {
|
|
208
|
-
"command": "npx",
|
|
209
|
-
"args": [
|
|
210
|
-
"
|
|
211
|
-
"
|
|
212
|
-
"
|
|
240
|
+
"command": "npx",
|
|
241
|
+
"args": [
|
|
242
|
+
"-y",
|
|
243
|
+
"@n0zer0d4y/vulcan-file-ops",
|
|
244
|
+
"--approved-folders",
|
|
245
|
+
"/Users/username/projects",
|
|
213
246
|
"/Users/username/documents"
|
|
214
247
|
]
|
|
215
248
|
}
|
|
@@ -223,11 +256,12 @@ Pre-configure specific directories for immediate access on server start:
|
|
|
223
256
|
{
|
|
224
257
|
"mcpServers": {
|
|
225
258
|
"vulcan-file-ops": {
|
|
226
|
-
"command": "npx",
|
|
227
|
-
"args": [
|
|
228
|
-
"
|
|
229
|
-
"
|
|
230
|
-
"
|
|
259
|
+
"command": "npx",
|
|
260
|
+
"args": [
|
|
261
|
+
"-y",
|
|
262
|
+
"@n0zer0d4y/vulcan-file-ops",
|
|
263
|
+
"--approved-folders",
|
|
264
|
+
"C:/Users/username/projects",
|
|
231
265
|
"C:/Users/username/documents"
|
|
232
266
|
]
|
|
233
267
|
}
|
|
@@ -235,7 +269,7 @@ Pre-configure specific directories for immediate access on server start:
|
|
|
235
269
|
}
|
|
236
270
|
```
|
|
237
271
|
|
|
238
|
-
**Alternative: Local Repository Execution**
|
|
272
|
+
**Alternative: Local Repository Execution**
|
|
239
273
|
|
|
240
274
|
For users running from a cloned repository (after `npm run build`):
|
|
241
275
|
|
|
@@ -243,16 +277,32 @@ For users running from a cloned repository (after `npm run build`):
|
|
|
243
277
|
{
|
|
244
278
|
"mcpServers": {
|
|
245
279
|
"vulcan-file-ops": {
|
|
246
|
-
"command": "vulcan-file-ops",
|
|
247
|
-
"args": [
|
|
248
|
-
"--approved-folders",
|
|
249
|
-
"/Users/username/projects",
|
|
250
|
-
"/Users/username/documents"
|
|
280
|
+
"command": "vulcan-file-ops",
|
|
281
|
+
"args": [
|
|
282
|
+
"--approved-folders",
|
|
283
|
+
"/Users/username/projects",
|
|
284
|
+
"/Users/username/documents"
|
|
251
285
|
]
|
|
252
286
|
}
|
|
253
287
|
}
|
|
254
288
|
}
|
|
255
|
-
```
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Codex with Approved Folders (`config.toml`)**
|
|
292
|
+
|
|
293
|
+
```toml
|
|
294
|
+
[mcp_servers.vulcan_file_ops]
|
|
295
|
+
command = "node"
|
|
296
|
+
args = [
|
|
297
|
+
'C:\absolute\path\to\vulcan-file-ops\dist\cli.js',
|
|
298
|
+
"--approved-folders",
|
|
299
|
+
'C:\Users\username\projects',
|
|
300
|
+
'C:\Users\username\documents'
|
|
301
|
+
]
|
|
302
|
+
cwd = 'C:\absolute\path\to\vulcan-file-ops'
|
|
303
|
+
enabled = true
|
|
304
|
+
startup_timeout_sec = 120.0
|
|
305
|
+
```
|
|
256
306
|
|
|
257
307
|
**Path Format Note:**
|
|
258
308
|
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// MCP servers use stdin/stdout for JSON-RPC via stdio transport
|
|
4
4
|
// Detection: stdin/stdout are NOT TTY (piped/redirected) OR explicit MCP flags are present.
|
|
5
5
|
// Note: We exclude help/version flags to allow CLI usage even when piped.
|
|
6
|
-
const isMCP = ((!process.stdin.isTTY
|
|
6
|
+
const isMCP = ((!process.stdin.isTTY || !process.stdout.isTTY) ||
|
|
7
7
|
process.argv.some((arg) => arg.includes("mcp") || arg.includes("stdio") || arg.includes("inspector"))) &&
|
|
8
8
|
!process.argv.some((arg) => arg === "--help" || arg === "-h" || arg === "--version" || arg === "-v");
|
|
9
9
|
if (isMCP) {
|
package/dist/server/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// due to ES Module static import execution order (imports are evaluated BEFORE top-level code).
|
|
4
4
|
// Detection: stdin/stdout are NOT TTY (piped/redirected) OR explicit MCP flags are present.
|
|
5
5
|
// Note: We exclude help/version flags to allow CLI usage even when piped.
|
|
6
|
-
const _isMCP = ((!process.stdin.isTTY
|
|
6
|
+
const _isMCP = ((!process.stdin.isTTY || !process.stdout.isTTY) ||
|
|
7
7
|
process.argv.some((arg) => arg.includes("mcp") || arg.includes("stdio") || arg.includes("inspector"))) &&
|
|
8
8
|
!process.argv.some((arg) => arg === "--help" || arg === "-h" || arg === "--version" || arg === "-v");
|
|
9
9
|
if (_isMCP) {
|
|
@@ -18,9 +18,14 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
18
18
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
19
19
|
import { CallToolRequestSchema, ListToolsRequestSchema, InitializeRequestSchema, PingRequestSchema, RootsListChangedNotificationSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
20
20
|
import fs from "fs/promises";
|
|
21
|
+
import { readFileSync } from "fs";
|
|
21
22
|
import path from "path";
|
|
23
|
+
import { fileURLToPath } from "url";
|
|
22
24
|
import dotenv from "dotenv";
|
|
23
|
-
import
|
|
25
|
+
// Manual JSON import for compatibility with older Node.js versions
|
|
26
|
+
// (native 'import ... with { type: "json" }' requires Node 20.10+ or 18.20+)
|
|
27
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
const packageJson = JSON.parse(readFileSync(path.resolve(__dirname, "../../package.json"), "utf-8"));
|
|
24
29
|
import { normalizePath, expandHome } from "../utils/path-utils.js";
|
|
25
30
|
import { getValidRootDirectories } from "../utils/roots-utils.js";
|
|
26
31
|
import { setAllowedDirectories, getAllowedDirectories, setIgnoredFolders, setEnabledTools, } from "../utils/lib.js";
|
|
@@ -336,7 +341,7 @@ async function initializeDirectories() {
|
|
|
336
341
|
// Priority 2: Load from .env file
|
|
337
342
|
try {
|
|
338
343
|
const envPath = path.join(process.cwd(), ".env");
|
|
339
|
-
dotenv.config({ path: envPath });
|
|
344
|
+
dotenv.config({ path: envPath, quiet: true });
|
|
340
345
|
if (process.env.APPROVED_COMMANDS) {
|
|
341
346
|
finalApprovedCommands = process.env.APPROVED_COMMANDS.split(",")
|
|
342
347
|
.map((c) => c.trim())
|
|
@@ -643,8 +648,10 @@ export async function runServer() {
|
|
|
643
648
|
catch (error) {
|
|
644
649
|
// In MCP mode, don't crash the server on init errors
|
|
645
650
|
// Just continue with empty configuration
|
|
646
|
-
const isMCP = (!process.stdin.isTTY
|
|
647
|
-
process.argv.some((arg) => arg.includes("mcp") ||
|
|
651
|
+
const isMCP = (!process.stdin.isTTY || !process.stdout.isTTY) ||
|
|
652
|
+
process.argv.some((arg) => arg.includes("mcp") ||
|
|
653
|
+
arg.includes("stdio") ||
|
|
654
|
+
arg.includes("inspector"));
|
|
648
655
|
if (!isMCP) {
|
|
649
656
|
// In non-MCP mode, we can show errors and exit
|
|
650
657
|
throw error;
|
|
@@ -7,6 +7,7 @@ import { expandHome, normalizePath } from "../utils/path-utils.js";
|
|
|
7
7
|
import { isPathWithinAllowedDirectories } from "../utils/path-validation.js";
|
|
8
8
|
import { MakeDirectoryArgsSchema, ListDirectoryArgsSchema, MoveFileArgsSchema, GetFileInfoArgsSchema, RegisterDirectoryArgsSchema, FileOperationsArgsSchema, DeleteFilesArgsSchema, } from "../types/index.js";
|
|
9
9
|
import { validatePath, getFileStats, formatSize, getAllowedDirectories, setAllowedDirectories, shouldIgnoreFolder, } from "../utils/lib.js";
|
|
10
|
+
import { createEmptyObjectSchema, createPathArraySchema, sanitizeToolInputSchema, } from "../utils/tool-schema.js";
|
|
10
11
|
const ToolInputSchema = ToolSchema.shape.inputSchema;
|
|
11
12
|
export function getFileSystemTools() {
|
|
12
13
|
// Get current allowed directories for dynamic descriptions
|
|
@@ -25,7 +26,14 @@ export function getFileSystemTools() {
|
|
|
25
26
|
description: "Create single or multiple directories with recursive parent creation " +
|
|
26
27
|
"(like Unix 'mkdir -p'). Idempotent - won't error if directories exist. " +
|
|
27
28
|
"Only works within allowed directories.",
|
|
28
|
-
inputSchema:
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
paths: createPathArraySchema("Directory paths to create. For maximum MCP client compatibility, provide an array even when creating a single directory."),
|
|
33
|
+
},
|
|
34
|
+
required: ["paths"],
|
|
35
|
+
additionalProperties: false,
|
|
36
|
+
},
|
|
29
37
|
},
|
|
30
38
|
{
|
|
31
39
|
name: "list_directory",
|
|
@@ -34,7 +42,7 @@ export function getFileSystemTools() {
|
|
|
34
42
|
"Supports simple listings, detailed views with sizes/timestamps, hierarchical " +
|
|
35
43
|
"tree display, and structured JSON output. Automatically filters globally " +
|
|
36
44
|
"configured ignored folders. Only works within allowed directories.",
|
|
37
|
-
inputSchema: zodToJsonSchema(ListDirectoryArgsSchema),
|
|
45
|
+
inputSchema: sanitizeToolInputSchema(zodToJsonSchema(ListDirectoryArgsSchema)),
|
|
38
46
|
},
|
|
39
47
|
{
|
|
40
48
|
name: "move_file",
|
|
@@ -43,7 +51,7 @@ export function getFileSystemTools() {
|
|
|
43
51
|
"Fails safely if the destination path already exists to prevent accidental overwrites. " +
|
|
44
52
|
"Can also perform simple same-directory renames. " +
|
|
45
53
|
"Both source and destination must be within allowed directories.",
|
|
46
|
-
inputSchema: zodToJsonSchema(MoveFileArgsSchema),
|
|
54
|
+
inputSchema: sanitizeToolInputSchema(zodToJsonSchema(MoveFileArgsSchema)),
|
|
47
55
|
},
|
|
48
56
|
{
|
|
49
57
|
name: "get_file_info",
|
|
@@ -51,7 +59,7 @@ export function getFileSystemTools() {
|
|
|
51
59
|
"Provides detailed information including size, timestamps (creation and last modification), permissions, and entry type. " +
|
|
52
60
|
"Perfect for inspecting file properties and attributes without accessing the actual content. " +
|
|
53
61
|
"Only works within allowed directories.",
|
|
54
|
-
inputSchema: zodToJsonSchema(GetFileInfoArgsSchema),
|
|
62
|
+
inputSchema: sanitizeToolInputSchema(zodToJsonSchema(GetFileInfoArgsSchema)),
|
|
55
63
|
},
|
|
56
64
|
{
|
|
57
65
|
name: "register_directory",
|
|
@@ -59,7 +67,7 @@ export function getFileSystemTools() {
|
|
|
59
67
|
"to directories specified by the human user during conversation. The directory " +
|
|
60
68
|
"and all its subdirectories will become accessible for all filesystem operations." +
|
|
61
69
|
generateApprovedDirsText(),
|
|
62
|
-
inputSchema: zodToJsonSchema(RegisterDirectoryArgsSchema),
|
|
70
|
+
inputSchema: sanitizeToolInputSchema(zodToJsonSchema(RegisterDirectoryArgsSchema)),
|
|
63
71
|
},
|
|
64
72
|
{
|
|
65
73
|
name: "list_allowed_directories",
|
|
@@ -67,11 +75,7 @@ export function getFileSystemTools() {
|
|
|
67
75
|
"Note that subdirectories within listed paths are implicitly accessible as well. " +
|
|
68
76
|
"Use this to determine available filesystem scope and plan operations accordingly before attempting file access." +
|
|
69
77
|
generateApprovedDirsText(),
|
|
70
|
-
inputSchema:
|
|
71
|
-
type: "object",
|
|
72
|
-
properties: {},
|
|
73
|
-
required: [],
|
|
74
|
-
},
|
|
78
|
+
inputSchema: createEmptyObjectSchema(),
|
|
75
79
|
},
|
|
76
80
|
{
|
|
77
81
|
name: "file_operations",
|
|
@@ -126,7 +130,7 @@ export function getFileSystemTools() {
|
|
|
126
130
|
"Operations are processed concurrently for performance. " +
|
|
127
131
|
"Maximum 100 paths per operation. " +
|
|
128
132
|
"Only works within allowed directories.",
|
|
129
|
-
inputSchema: zodToJsonSchema(DeleteFilesArgsSchema),
|
|
133
|
+
inputSchema: sanitizeToolInputSchema(zodToJsonSchema(DeleteFilesArgsSchema)),
|
|
130
134
|
},
|
|
131
135
|
];
|
|
132
136
|
}
|
package/dist/tools/read-tools.js
CHANGED
|
@@ -5,6 +5,7 @@ import { ToolSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
|
5
5
|
import { ReadFileArgsSchema, AttachImageArgsSchema, ReadMultipleFilesArgsSchema, } from "../types/index.js";
|
|
6
6
|
import { validatePath, readFileContent, tailFile, headFile, rangeFile, } from "../utils/lib.js";
|
|
7
7
|
import { isDocumentFile, parseDocument } from "../utils/document-parser.js";
|
|
8
|
+
import { createPathArraySchema, sanitizeToolInputSchema, } from "../utils/tool-schema.js";
|
|
8
9
|
const ToolInputSchema = ToolSchema.shape.inputSchema;
|
|
9
10
|
// Reads a file as a stream of buffers, concatenates them, and then encodes
|
|
10
11
|
// the result to a Base64 string. This is a memory-efficient way to handle
|
|
@@ -34,7 +35,7 @@ export function getReadTools() {
|
|
|
34
35
|
"range (lines from startLine to endLine, inclusive, 1-indexed). " +
|
|
35
36
|
"Document files ignore mode parameters and always return full content. " +
|
|
36
37
|
"Only works within allowed directories.",
|
|
37
|
-
inputSchema: zodToJsonSchema(ReadFileArgsSchema),
|
|
38
|
+
inputSchema: sanitizeToolInputSchema(zodToJsonSchema(ReadFileArgsSchema)),
|
|
38
39
|
},
|
|
39
40
|
{
|
|
40
41
|
name: "attach_image",
|
|
@@ -45,7 +46,14 @@ export function getReadTools() {
|
|
|
45
46
|
"Supports PNG, JPEG, GIF, WebP, BMP, and SVG formats. " +
|
|
46
47
|
"Note: This requires the MCP client to support vision capabilities. " +
|
|
47
48
|
"Only works within allowed directories.",
|
|
48
|
-
inputSchema:
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: "object",
|
|
51
|
+
properties: {
|
|
52
|
+
path: createPathArraySchema("Path(s) to image file(s) to attach for AI vision analysis. For maximum MCP client compatibility, provide an array even when attaching a single image."),
|
|
53
|
+
},
|
|
54
|
+
required: ["path"],
|
|
55
|
+
additionalProperties: false,
|
|
56
|
+
},
|
|
49
57
|
},
|
|
50
58
|
{
|
|
51
59
|
name: "read_multiple_files",
|
|
@@ -56,7 +64,7 @@ export function getReadTools() {
|
|
|
56
64
|
"Document files ignore mode parameters and return full content. " +
|
|
57
65
|
"Processes files concurrently for performance. Maximum 50 files per operation. " +
|
|
58
66
|
"Only works within allowed directories.",
|
|
59
|
-
inputSchema: zodToJsonSchema(ReadMultipleFilesArgsSchema),
|
|
67
|
+
inputSchema: sanitizeToolInputSchema(zodToJsonSchema(ReadMultipleFilesArgsSchema)),
|
|
60
68
|
},
|
|
61
69
|
];
|
|
62
70
|
}
|
|
@@ -2,6 +2,7 @@ import { zodToJsonSchema } from "zod-to-json-schema";
|
|
|
2
2
|
import { ToolSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
3
3
|
import { SearchFilesArgsSchema, GrepArgsSchema, } from "../types/index.js";
|
|
4
4
|
import { validatePath, searchFilesWithValidation, grepFilesWithValidation, getAllowedDirectories, getIgnoredFolders, } from "../utils/lib.js";
|
|
5
|
+
import { sanitizeToolInputSchema } from "../utils/tool-schema.js";
|
|
5
6
|
const ToolInputSchema = ToolSchema.shape.inputSchema;
|
|
6
7
|
export function getSearchTools() {
|
|
7
8
|
return [
|
|
@@ -12,7 +13,7 @@ export function getSearchTools() {
|
|
|
12
13
|
"Use simple patterns like '*.ext' for current directory matches, or '**/*.ext' for deep subdirectory searches. " +
|
|
13
14
|
"Returns absolute paths to all discovered items. Excellent for locating files when exact paths are unknown. " +
|
|
14
15
|
"Only searches within allowed directories.",
|
|
15
|
-
inputSchema: zodToJsonSchema(SearchFilesArgsSchema),
|
|
16
|
+
inputSchema: sanitizeToolInputSchema(zodToJsonSchema(SearchFilesArgsSchema)),
|
|
16
17
|
},
|
|
17
18
|
{
|
|
18
19
|
name: "grep_files",
|
|
@@ -20,7 +21,7 @@ export function getSearchTools() {
|
|
|
20
21
|
"Supports: regex patterns, case-insensitive (-i), context lines (-A/-B/-C), file type filters (type: js/py/ts/etc), glob patterns, multiline mode. " +
|
|
21
22
|
"Output modes: content (lines+context), files_with_matches (paths only), count (match counts). " +
|
|
22
23
|
"Respects ignored folders. Use head_limit to cap results. Only searches within allowed directories.",
|
|
23
|
-
inputSchema: zodToJsonSchema(GrepArgsSchema),
|
|
24
|
+
inputSchema: sanitizeToolInputSchema(zodToJsonSchema(GrepArgsSchema)),
|
|
24
25
|
},
|
|
25
26
|
];
|
|
26
27
|
}
|
package/dist/tools/shell-tool.js
CHANGED
|
@@ -6,6 +6,7 @@ import { executeShellCommand, } from "../utils/shell-execution.js";
|
|
|
6
6
|
import { validatePath, getAllowedDirectories } from "../utils/lib.js";
|
|
7
7
|
import { extractPathsFromCommand } from "../utils/command-path-extraction.js";
|
|
8
8
|
import { isPathWithinAllowedDirectories } from "../utils/path-validation.js";
|
|
9
|
+
import { sanitizeToolInputSchema } from "../utils/tool-schema.js";
|
|
9
10
|
const ToolInputSchema = ToolSchema.shape.inputSchema;
|
|
10
11
|
// Global state for approved commands
|
|
11
12
|
let approvedCommands = new Set();
|
|
@@ -62,7 +63,7 @@ export function getShellTools() {
|
|
|
62
63
|
`If no workdir is specified, the server's current working directory will be used and validated.` +
|
|
63
64
|
approvedCommandsText +
|
|
64
65
|
`\n\nIMPORTANT: Always provide a clear description of what the command does and why it's needed.`,
|
|
65
|
-
inputSchema: zodToJsonSchema(ShellCommandArgsSchema),
|
|
66
|
+
inputSchema: sanitizeToolInputSchema(zodToJsonSchema(ShellCommandArgsSchema)),
|
|
66
67
|
},
|
|
67
68
|
];
|
|
68
69
|
}
|
|
@@ -5,6 +5,7 @@ import { promises as fs } from "fs";
|
|
|
5
5
|
import { WriteFileArgsSchema, WriteMultipleFilesArgsSchema, EditFileArgsSchema, } from "../types/index.js";
|
|
6
6
|
import { validatePath, writeFileContent, readFileContent, applyFileEdits, } from "../utils/lib.js";
|
|
7
7
|
import { isHTMLContent, convertHTMLToPDF, convertHTMLToDOCX, } from "../utils/html-to-document.js";
|
|
8
|
+
import { sanitizeToolInputSchema } from "../utils/tool-schema.js";
|
|
8
9
|
const ToolInputSchema = ToolSchema.shape.inputSchema;
|
|
9
10
|
/**
|
|
10
11
|
* Helper function to write file content based on file extension
|
|
@@ -261,7 +262,7 @@ export function getWriteTools() {
|
|
|
261
262
|
"- Correct: Use actual line breaks in your JSON string value\n" +
|
|
262
263
|
"\n" +
|
|
263
264
|
"Only works within allowed directories.",
|
|
264
|
-
inputSchema: zodToJsonSchema(WriteFileArgsSchema),
|
|
265
|
+
inputSchema: sanitizeToolInputSchema(zodToJsonSchema(WriteFileArgsSchema)),
|
|
265
266
|
},
|
|
266
267
|
{
|
|
267
268
|
name: "edit_file",
|
|
@@ -294,7 +295,7 @@ export function getWriteTools() {
|
|
|
294
295
|
"- The MCP/JSON layer handles encoding automatically\n" +
|
|
295
296
|
"- Using \\n literally will search for/write backslash+n characters (wrong!)\n\n" +
|
|
296
297
|
"Only works within allowed directories.",
|
|
297
|
-
inputSchema: zodToJsonSchema(EditFileArgsSchema),
|
|
298
|
+
inputSchema: sanitizeToolInputSchema(zodToJsonSchema(EditFileArgsSchema)),
|
|
298
299
|
},
|
|
299
300
|
{
|
|
300
301
|
name: "write_multiple_files",
|
|
@@ -310,7 +311,7 @@ export function getWriteTools() {
|
|
|
310
311
|
"- Each file's content will be written exactly as provided in the string\n" +
|
|
311
312
|
"\n" +
|
|
312
313
|
"Only works within allowed directories.",
|
|
313
|
-
inputSchema: zodToJsonSchema(WriteMultipleFilesArgsSchema),
|
|
314
|
+
inputSchema: sanitizeToolInputSchema(zodToJsonSchema(WriteMultipleFilesArgsSchema)),
|
|
314
315
|
},
|
|
315
316
|
];
|
|
316
317
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
function cloneValue(value) {
|
|
2
|
+
return JSON.parse(JSON.stringify(value));
|
|
3
|
+
}
|
|
4
|
+
function unescapeJsonPointerSegment(segment) {
|
|
5
|
+
return segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
6
|
+
}
|
|
7
|
+
function resolveLocalRef(root, ref) {
|
|
8
|
+
if (!ref.startsWith("#/")) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
const segments = ref
|
|
12
|
+
.slice(2)
|
|
13
|
+
.split("/")
|
|
14
|
+
.map((segment) => unescapeJsonPointerSegment(segment));
|
|
15
|
+
let current = root;
|
|
16
|
+
for (const segment of segments) {
|
|
17
|
+
if (current == null || typeof current !== "object" || !(segment in current)) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
current = current[segment];
|
|
21
|
+
}
|
|
22
|
+
return current;
|
|
23
|
+
}
|
|
24
|
+
function sanitizeSchemaNode(node, root) {
|
|
25
|
+
if (Array.isArray(node)) {
|
|
26
|
+
return node.map((item) => sanitizeSchemaNode(item, root));
|
|
27
|
+
}
|
|
28
|
+
if (!node || typeof node !== "object") {
|
|
29
|
+
return node;
|
|
30
|
+
}
|
|
31
|
+
const schema = node;
|
|
32
|
+
if (typeof schema.$ref === "string") {
|
|
33
|
+
const resolved = resolveLocalRef(root, schema.$ref);
|
|
34
|
+
if (resolved && typeof resolved === "object") {
|
|
35
|
+
const { $ref: _ignoredRef, ...siblings } = schema;
|
|
36
|
+
return sanitizeSchemaNode({
|
|
37
|
+
...cloneValue(resolved),
|
|
38
|
+
...siblings,
|
|
39
|
+
}, root);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const result = {};
|
|
43
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
44
|
+
if (key === "$schema" || key === "$defs" || key === "definitions") {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
result[key] = sanitizeSchemaNode(value, root);
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
export function sanitizeToolInputSchema(schema) {
|
|
52
|
+
const cloned = cloneValue(schema);
|
|
53
|
+
const sanitized = sanitizeSchemaNode(cloned, cloned);
|
|
54
|
+
if (!sanitized || typeof sanitized !== "object" || Array.isArray(sanitized)) {
|
|
55
|
+
return {
|
|
56
|
+
type: "object",
|
|
57
|
+
additionalProperties: false,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const normalized = sanitized;
|
|
61
|
+
if (normalized.type !== "object") {
|
|
62
|
+
normalized.type = "object";
|
|
63
|
+
}
|
|
64
|
+
if (!("additionalProperties" in normalized)) {
|
|
65
|
+
normalized.additionalProperties = false;
|
|
66
|
+
}
|
|
67
|
+
return normalized;
|
|
68
|
+
}
|
|
69
|
+
export function createEmptyObjectSchema() {
|
|
70
|
+
return {
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: {},
|
|
73
|
+
required: [],
|
|
74
|
+
additionalProperties: false,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function createPathArraySchema(description) {
|
|
78
|
+
return {
|
|
79
|
+
type: "array",
|
|
80
|
+
items: {
|
|
81
|
+
type: "string",
|
|
82
|
+
},
|
|
83
|
+
minItems: 1,
|
|
84
|
+
description,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=tool-schema.js.map
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@n0zer0d4y/vulcan-file-ops",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.13",
|
|
4
4
|
"mcpName": "io.github.n0zer0d4y/vulcan-file-ops",
|
|
5
5
|
"description": "MCP server for AI assistants: read, write, edit, and manage files securely on local filesystem.",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"author": "
|
|
7
|
+
"author": "n0zer0d4y",
|
|
8
8
|
"homepage": "https://github.com/n0zer0d4y/vulcan-file-ops",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
"pdf-lib": "^1.17.1",
|
|
80
80
|
"pdf-parse": "^2.4.5",
|
|
81
81
|
"pdfmake": "^0.2.20",
|
|
82
|
+
"zod": "^3.25.76",
|
|
82
83
|
"zod-to-json-schema": "^3.25.1"
|
|
83
84
|
},
|
|
84
85
|
"devDependencies": {
|