Merge branch 'main' into claude/issue-2526-20250824-0240

This commit is contained in:
Cliff Hall
2026-02-07 16:57:16 -05:00
committed by GitHub
20 changed files with 3032 additions and 714 deletions

View File

@@ -0,0 +1,100 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { spawn } from 'child_process';
import * as path from 'path';
import * as fs from 'fs/promises';
import * as os from 'os';
const SERVER_PATH = path.join(__dirname, '..', 'dist', 'index.js');
/**
* Spawns the filesystem server with given arguments and returns exit info
*/
async function spawnServer(args: string[], timeoutMs = 2000): Promise<{ exitCode: number | null; stderr: string }> {
return new Promise((resolve) => {
const proc = spawn('node', [SERVER_PATH, ...args], {
stdio: ['pipe', 'pipe', 'pipe'],
});
let stderr = '';
proc.stderr?.on('data', (data) => {
stderr += data.toString();
});
const timeout = setTimeout(() => {
proc.kill('SIGTERM');
}, timeoutMs);
proc.on('close', (code) => {
clearTimeout(timeout);
resolve({ exitCode: code, stderr });
});
proc.on('error', (err) => {
clearTimeout(timeout);
resolve({ exitCode: 1, stderr: err.message });
});
});
}
describe('Startup Directory Validation', () => {
let testDir: string;
let accessibleDir: string;
let accessibleDir2: string;
beforeEach(async () => {
testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'fs-startup-test-'));
accessibleDir = path.join(testDir, 'accessible');
accessibleDir2 = path.join(testDir, 'accessible2');
await fs.mkdir(accessibleDir, { recursive: true });
await fs.mkdir(accessibleDir2, { recursive: true });
});
afterEach(async () => {
await fs.rm(testDir, { recursive: true, force: true });
});
it('should start successfully with all accessible directories', async () => {
const result = await spawnServer([accessibleDir, accessibleDir2]);
// Server starts and runs (we kill it after timeout, so exit code is null or from SIGTERM)
expect(result.stderr).toContain('Secure MCP Filesystem Server running on stdio');
expect(result.stderr).not.toContain('Error:');
});
it('should skip inaccessible directory and continue with accessible one', async () => {
const nonExistentDir = path.join(testDir, 'non-existent-dir-12345');
const result = await spawnServer([nonExistentDir, accessibleDir]);
// Should warn about inaccessible directory
expect(result.stderr).toContain('Warning: Cannot access directory');
expect(result.stderr).toContain(nonExistentDir);
// Should still start successfully
expect(result.stderr).toContain('Secure MCP Filesystem Server running on stdio');
});
it('should exit with error when ALL directories are inaccessible', async () => {
const nonExistent1 = path.join(testDir, 'non-existent-1');
const nonExistent2 = path.join(testDir, 'non-existent-2');
const result = await spawnServer([nonExistent1, nonExistent2]);
// Should exit with error
expect(result.exitCode).toBe(1);
expect(result.stderr).toContain('Error: None of the specified directories are accessible');
});
it('should warn when path is not a directory', async () => {
const filePath = path.join(testDir, 'not-a-directory.txt');
await fs.writeFile(filePath, 'content');
const result = await spawnServer([filePath, accessibleDir]);
// Should warn about non-directory
expect(result.stderr).toContain('Warning:');
expect(result.stderr).toContain('not a directory');
// Should still start with the valid directory
expect(result.stderr).toContain('Secure MCP Filesystem Server running on stdio');
});
});

View File

@@ -56,19 +56,28 @@ let allowedDirectories = await Promise.all(
})
);
// Validate that all directories exist and are accessible
await Promise.all(allowedDirectories.map(async (dir) => {
// Filter to only accessible directories, warn about inaccessible ones
const accessibleDirectories: string[] = [];
for (const dir of allowedDirectories) {
try {
const stats = await fs.stat(dir);
if (!stats.isDirectory()) {
console.error(`Error: ${dir} is not a directory`);
process.exit(1);
if (stats.isDirectory()) {
accessibleDirectories.push(dir);
} else {
console.error(`Warning: ${dir} is not a directory, skipping`);
}
} catch (error) {
console.error(`Error accessing directory ${dir}:`, error);
process.exit(1);
console.error(`Warning: Cannot access directory ${dir}, skipping`);
}
}));
}
// Exit only if ALL paths are inaccessible (and some were specified)
if (accessibleDirectories.length === 0 && allowedDirectories.length > 0) {
console.error("Error: None of the specified directories are accessible");
process.exit(1);
}
allowedDirectories = accessibleDirectories;
// Initialize the global allowedDirectories in lib.ts
setAllowedDirectories(allowedDirectories);

View File

@@ -2,9 +2,9 @@
"name": "@modelcontextprotocol/server-filesystem",
"version": "0.6.3",
"description": "MCP server for filesystem access",
"license": "MIT",
"license": "SEE LICENSE IN LICENSE",
"mcpName": "io.github.modelcontextprotocol/server-filesystem",
"author": "Anthropic, PBC (https://anthropic.com)",
"author": "Model Context Protocol a Series of LF Projects, LLC.",
"homepage": "https://modelcontextprotocol.io",
"bugs": "https://github.com/modelcontextprotocol/servers/issues",
"repository": {
@@ -25,7 +25,7 @@
"test": "vitest run --coverage"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.25.2",
"@modelcontextprotocol/sdk": "^1.26.0",
"diff": "^8.0.3",
"glob": "^10.5.0",
"minimatch": "^10.0.1",