Skip to content

Conversation

@vibegui
Copy link

@vibegui vibegui commented Dec 30, 2025

Summary by cubic

Adds a local filesystem MCP server that mounts any folder and exposes file tools over stdio or HTTP. Includes a CLI, HTTP endpoint, collection bindings, docs, and tests.

  • New Features
    • New package: @decocms/mcp-local-fs with stdio (default) and HTTP transports
    • CLI: npx @decocms/mcp-local-fs [--http] [--path PATH] [--port PORT]
    • Tools: read, write, delete, move, copy, mkdir; plus collection list/get with filters and pagination
    • HTTP: /mcp?path=/dir, plus GET / and /health; SSE supported
    • Docs and examples for Claude Desktop/Cursor
    • Tests for storage and MCP tools

Written for commit 85a5400. Summary will update on new commits.

@github-actions
Copy link

github-actions bot commented Dec 30, 2025

🚀 Preview Deployments Ready!

⚠️ No preview URLs were generated. Check the workflow logs for details.


Deployed from commit: 9085aa6

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 11 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="local-fs/package.json">

<violation number="1" location="local-fs/package.json:2">
P2: The `local-fs` package is not listed in the root `package.json` workspaces array. If this package should be part of the monorepo workspace, ensure it&#39;s added to the `workspaces` array in the root package.json.</violation>
</file>

<file name="local-fs/server/storage.ts">

<violation number="1" location="local-fs/server/storage.ts:97">
P0: Path traversal vulnerability: `sanitizePath` only handles `/` separators. On Windows, paths with `\` separators (e.g., `..\..\secret`) bypass sanitization and can escape the root directory. Add backslash handling and verify the resolved path starts with `rootDir`.</violation>

<violation number="2" location="local-fs/server/storage.ts:119">
P1: Missing path containment check. Even with sanitization, add a verification that the resolved path is within `rootDir` as defense-in-depth against path traversal attacks.</violation>
</file>

<file name="local-fs/bun.lock">

<violation number="1" location="local-fs/bun.lock:5">
P2: Package name mismatch: lock file declares `@anthropic/mcp-local-fs` but package.json defines `@decocms/mcp-local-fs`. The lock file appears to be stale or incorrectly generated. Run `bun install` to regenerate it.</violation>
</file>

Reply to cubic to teach it or ask questions. Tag @cubic-dev-ai to re-run a review.

@@ -0,0 +1,60 @@
{
"name": "@decocms/mcp-local-fs",
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The local-fs package is not listed in the root package.json workspaces array. If this package should be part of the monorepo workspace, ensure it's added to the workspaces array in the root package.json.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At local-fs/package.json, line 2:

<comment>The `local-fs` package is not listed in the root `package.json` workspaces array. If this package should be part of the monorepo workspace, ensure it&#39;s added to the `workspaces` array in the root package.json.</comment>

<file context>
@@ -0,0 +1,60 @@
+{
+  &quot;name&quot;: &quot;@decocms/mcp-local-fs&quot;,
+  &quot;version&quot;: &quot;1.0.0&quot;,
+  &quot;description&quot;: &quot;MCP server that mounts any local filesystem path. Supports stdio (default) and HTTP transports.&quot;,
</file context>
Fix with Cubic

return this.rootDir;
}

private resolvePath(path: string): string {
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Missing path containment check. Even with sanitization, add a verification that the resolved path is within rootDir as defense-in-depth against path traversal attacks.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At local-fs/server/storage.ts, line 119:

<comment>Missing path containment check. Even with sanitization, add a verification that the resolved path is within `rootDir` as defense-in-depth against path traversal attacks.</comment>

<file context>
@@ -0,0 +1,336 @@
+    return this.rootDir;
+  }
+
+  private resolvePath(path: string): string {
+    const sanitized = sanitizePath(path);
+    return join(this.rootDir, sanitized);
</file context>

✅ Addressed in 2f05cf9

return MIME_TYPES[ext] || "application/octet-stream";
}

function sanitizePath(path: string): string {
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: Path traversal vulnerability: sanitizePath only handles / separators. On Windows, paths with \ separators (e.g., ..\..\secret) bypass sanitization and can escape the root directory. Add backslash handling and verify the resolved path starts with rootDir.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At local-fs/server/storage.ts, line 97:

<comment>Path traversal vulnerability: `sanitizePath` only handles `/` separators. On Windows, paths with `\` separators (e.g., `..\..\secret`) bypass sanitization and can escape the root directory. Add backslash handling and verify the resolved path starts with `rootDir`.</comment>

<file context>
@@ -0,0 +1,336 @@
+  return MIME_TYPES[ext] || &quot;application/octet-stream&quot;;
+}
+
+function sanitizePath(path: string): string {
+  return path
+    .split(&quot;/&quot;)
</file context>

✅ Addressed in 2f05cf9

"lockfileVersion": 1,
"workspaces": {
"": {
"name": "@anthropic/mcp-local-fs",
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Package name mismatch: lock file declares @anthropic/mcp-local-fs but package.json defines @decocms/mcp-local-fs. The lock file appears to be stale or incorrectly generated. Run bun install to regenerate it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At local-fs/bun.lock, line 5:

<comment>Package name mismatch: lock file declares `@anthropic/mcp-local-fs` but package.json defines `@decocms/mcp-local-fs`. The lock file appears to be stale or incorrectly generated. Run `bun install` to regenerate it.</comment>

<file context>
@@ -0,0 +1,38 @@
+  &quot;lockfileVersion&quot;: 1,
+  &quot;workspaces&quot;: {
+    &quot;&quot;: {
+      &quot;name&quot;: &quot;@anthropic/mcp-local-fs&quot;,
+      &quot;dependencies&quot;: {
+        &quot;@hono/node-server&quot;: &quot;^1.13.0&quot;,
</file context>

✅ Addressed in 70f5c50

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="local-fs/server/stdio.ts">

<violation number="1" location="local-fs/server/stdio.ts:37">
P1: Unsafe iteration over `conditions` without checking if it&#39;s actually an array. If a malformed where clause has `operator: &quot;and&quot;` but no `conditions` field (or a non-array value), this will throw a TypeError.</violation>
</file>

Reply to cubic to teach it or ask questions. Tag @cubic-dev-ai to re-run a review.

Comment on lines 37 to 38
const conditions = w.conditions as unknown[];
for (const cond of conditions) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Unsafe iteration over conditions without checking if it's actually an array. If a malformed where clause has operator: "and" but no conditions field (or a non-array value), this will throw a TypeError.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At local-fs/server/stdio.ts, line 37:

<comment>Unsafe iteration over `conditions` without checking if it&#39;s actually an array. If a malformed where clause has `operator: &quot;and&quot;` but no `conditions` field (or a non-array value), this will throw a TypeError.</comment>

<file context>
@@ -16,6 +16,34 @@ import { z } from &quot;zod&quot;;
+
+  // Compound condition: { operator: &quot;and&quot;, conditions: [...] }
+  if (w.operator === &quot;and&quot; || w.operator === &quot;or&quot;) {
+    const conditions = w.conditions as unknown[];
+    for (const cond of conditions) {
+      const parent = extractParentFromWhere(cond);
</file context>
Suggested change
const conditions = w.conditions as unknown[];
for (const cond of conditions) {
const conditions = w.conditions;
if (!Array.isArray(conditions)) return "";
for (const cond of conditions) {

✅ Addressed in 2f05cf9

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 5 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="local-fs/server/http.ts">

<violation number="1" location="local-fs/server/http.ts:403">
P1: Memory leak: Sessions stored in `transports` Map are never cleaned up for abandoned connections. Consider adding a session timeout/TTL mechanism to remove stale sessions.</violation>

<violation number="2" location="local-fs/server/http.ts:407">
P2: Missing error handling: The async request handler lacks a try-catch wrapper. Unhandled errors could leave responses hanging. Consider wrapping the handler body in try-catch and sending a 500 response on error.</violation>
</file>

Reply to cubic to teach it or ask questions. Tag @cubic-dev-ai to re-run a review.


// Create HTTP server
const httpServer = createServer(
async (req: IncomingMessage, res: ServerResponse) => {
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Missing error handling: The async request handler lacks a try-catch wrapper. Unhandled errors could leave responses hanging. Consider wrapping the handler body in try-catch and sending a 500 response on error.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At local-fs/server/http.ts, line 407:

<comment>Missing error handling: The async request handler lacks a try-catch wrapper. Unhandled errors could leave responses hanging. Consider wrapping the handler body in try-catch and sending a 500 response on error.</comment>

<file context>
@@ -82,24 +365,181 @@ function getPort(): number {
+
+// Create HTTP server
+const httpServer = createServer(
+  async (req: IncomingMessage, res: ServerResponse) =&gt; {
+    const url = new URL(req.url || &quot;/&quot;, `http://localhost:${port}`);
+
</file context>

✅ Addressed in 2489ca6

const defaultPath = resolve(getDefaultPath());

// Store active transports for session management
const transports = new Map<string, StreamableHTTPServerTransport>();
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Memory leak: Sessions stored in transports Map are never cleaned up for abandoned connections. Consider adding a session timeout/TTL mechanism to remove stale sessions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At local-fs/server/http.ts, line 403:

<comment>Memory leak: Sessions stored in `transports` Map are never cleaned up for abandoned connections. Consider adding a session timeout/TTL mechanism to remove stale sessions.</comment>

<file context>
@@ -82,24 +365,181 @@ function getPort(): number {
+const defaultPath = resolve(getDefaultPath());
+
+// Store active transports for session management
+const transports = new Map&lt;string, StreamableHTTPServerTransport&gt;();
+
+// Create HTTP server
</file context>

✅ Addressed in 2f05cf9

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="local-fs/server/http.ts">

<violation number="1" location="local-fs/server/http.ts:575">
P2: URL construction doesn&#39;t handle Windows paths or special characters correctly. On Windows, paths like `C:\Users\docs` will produce invalid URLs without a separator and with unescaped characters. Use the query string format with proper encoding for cross-platform compatibility.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

);

// Build the full MCP URL
const mcpUrl = `http://localhost:${port}/mcp${defaultPath}`;
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: URL construction doesn't handle Windows paths or special characters correctly. On Windows, paths like C:\Users\docs will produce invalid URLs without a separator and with unescaped characters. Use the query string format with proper encoding for cross-platform compatibility.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At local-fs/server/http.ts, line 575:

<comment>URL construction doesn&#39;t handle Windows paths or special characters correctly. On Windows, paths like `C:\Users\docs` will produce invalid URLs without a separator and with unescaped characters. Use the query string format with proper encoding for cross-platform compatibility.</comment>

<file context>
@@ -523,23 +571,30 @@ const httpServer = createServer(
 
-console.log(`
+// Build the full MCP URL
+const mcpUrl = `http://localhost:${port}/mcp${defaultPath}`;
+
+// Copy to clipboard and show startup banner
</file context>
Suggested change
const mcpUrl = `http://localhost:${port}/mcp${defaultPath}`;
const mcpUrl = `http://localhost:${port}/mcp?path=${encodeURIComponent(defaultPath)}`;

✅ Addressed in 2489ca6

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="local-fs/server/storage.ts">

<violation number="1" location="local-fs/server/storage.ts:266">
P2: Recursive list operations will not be logged because the method returns early when `options.recursive` is true, bypassing this `logOp` call. Consider moving the logging to cover both code paths, or add logging to `listRecursive` as well.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

return a.title.localeCompare(b.title);
});

logOp("LIST", folder || "/", { count: files.length });
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Recursive list operations will not be logged because the method returns early when options.recursive is true, bypassing this logOp call. Consider moving the logging to cover both code paths, or add logging to listRecursive as well.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At local-fs/server/storage.ts, line 266:

<comment>Recursive list operations will not be logged because the method returns early when `options.recursive` is true, bypassing this `logOp` call. Consider moving the logging to cover both code paths, or add logging to `listRecursive` as well.</comment>

<file context>
@@ -259,6 +263,7 @@ export class LocalFileStorage {
       return a.title.localeCompare(b.title);
     });
 
+    logOp(&quot;LIST&quot;, folder || &quot;/&quot;, { count: files.length });
     return files;
   }
</file context>
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="local-fs/server/storage.ts">

<violation number="1" location="local-fs/server/storage.ts:130">
P2: Missing boundary check: `startsWith(this.rootDir)` incorrectly matches paths that share a prefix but aren&#39;t inside the root directory. For example, `/tmp/root` would match `/tmp/rootEvil/file.txt`. Should check for trailing slash or exact match.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

let normalizedPath = path;

// Strip root directory prefix if the path starts with it
if (normalizedPath.startsWith(this.rootDir)) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Missing boundary check: startsWith(this.rootDir) incorrectly matches paths that share a prefix but aren't inside the root directory. For example, /tmp/root would match /tmp/rootEvil/file.txt. Should check for trailing slash or exact match.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At local-fs/server/storage.ts, line 130:

<comment>Missing boundary check: `startsWith(this.rootDir)` incorrectly matches paths that share a prefix but aren&#39;t inside the root directory. For example, `/tmp/root` would match `/tmp/rootEvil/file.txt`. Should check for trailing slash or exact match.</comment>

<file context>
@@ -119,8 +119,38 @@ export class LocalFileStorage {
+    let normalizedPath = path;
+
+    // Strip root directory prefix if the path starts with it
+    if (normalizedPath.startsWith(this.rootDir)) {
+      normalizedPath = normalizedPath.slice(this.rootDir.length);
+    }
</file context>
Fix with Cubic

@vibegui vibegui force-pushed the feat/local-fs branch 3 times, most recently from fd44d45 to 3d69bc5 Compare December 31, 2025 16:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants