Skip to content
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ Official integrations are maintained by companies building production ready MCP
- <img height="12" width="12" src="https://memgraph.com/favicon.png" alt="Memgraph Logo" /> **[Memgraph](https://github.com/memgraph/mcp-memgraph)** - Query your data in [Memgraph](https://memgraph.com/) graph database.
- <img height="12" width="12" src="https://memgraph.com/favicon.png" alt="Memgraph Logo" /> **[Memgraph](https://github.com/memgraph/ai-toolkit/tree/main/integrations/mcp-memgraph)** - Query your data in [Memgraph](https://memgraph.com/) graph database.
- <img height="12" width="12" src="https://www.mercadopago.com/favicon.ico" alt="MercadoPago Logo" /> **[Mercado Pago](https://mcp.mercadopago.com/)** - Mercado Pago's official MCP server.
- <img height="12" width="12" src="https://metoro.io/static/images/logos/Metoro.svg" alt="Metoro Logo" /> **[Metoro](https://github.com/metoro-io/metoro-mcp-server)** - Query and interact with kubernetes environments monitored by Metoro
- <img height="12" width="12" src="https://metoro.io/static/images/logos/MetoroLogo.png" alt="Metoro Logo" /> **[Metoro](https://github.com/metoro-io/metoro-mcp-server)** - Query and interact with kubernetes environments monitored by Metoro
- <img height="12" width="12" src="https://claritystatic.azureedge.net/images/logo.ico" alt="Microsoft Clarity Logo"/> **[Microsoft Clarity](https://github.com/microsoft/clarity-mcp-server)** - Official MCP Server to get your behavioral analytics data and insights from [Clarity](https://clarity.microsoft.com)
- <img height="12" width="12" src="https://conn-afd-prod-endpoint-bmc9bqahasf3grgk.b01.azurefd.net/releases/v1.0.1735/1.0.1735.4099/commondataserviceforapps/icon.png" alt="Microsoft Dataverse Logo" /> **[Microsoft Dataverse](https://go.microsoft.com/fwlink/?linkid=2320176)** - Chat over your business data using NL - Discover tables, run queries, retrieve data, insert or update records, and execute custom prompts grounded in business knowledge and context.
- <img height="12" width="12" src="https://www.microsoft.com/favicon.ico" alt="microsoft.com favicon" /> **[Microsoft Learn Docs](https://github.com/microsoftdocs/mcp)** - An MCP server that provides structured access to Microsoft’s official documentation. Retrieves accurate, authoritative, and context-aware technical content for code generation, question answering, and workflow grounding.
Expand Down Expand Up @@ -526,7 +526,6 @@ A growing set of community-developed and maintained servers demonstrates various
- **[Feyod](https://github.com/jeroenvdmeer/feyod-mcp)** - A server that answers questions about football matches, and specialised in the football club Feyenoord.
- **[Fibaro HC3](https://github.com/coding-sailor/mcp-server-hc3)** - MCP server for Fibaro Home Center 3 smart home systems.
- **[Figma](https://github.com/GLips/Figma-Context-MCP)** - Give your coding agent direct access to Figma file data, helping it one-shot design implementation.
- **[Fingertip](https://github.com/fingertip-com/fingertip-mcp)** - MCP server for Fingertip.com to search and create new sites.
- **[Firebase](https://github.com/gannonh/firebase-mcp)** - Server to interact with Firebase services including Firebase Authentication, Firestore, and Firebase Storage.
- **[FireCrawl](https://github.com/vrknetha/mcp-server-firecrawl)** - Advanced web scraping with JavaScript rendering, PDF support, and smart rate limiting
- **[FitBit MCP Server](https://github.com/NitayRabi/fitbit-mcp)** - An MCP server that connects to FitBit API using a token obtained from OAuth flow.
Expand Down Expand Up @@ -599,7 +598,6 @@ A growing set of community-developed and maintained servers demonstrates various
- **[iTerm MCP](https://github.com/ferrislucas/iterm-mcp)** - Integration with iTerm2 terminal emulator for macOS, enabling LLMs to execute and monitor terminal commands.
- **[iTerm MCP Server](https://github.com/rishabkoul/iTerm-MCP-Server)** - A Model Context Protocol (MCP) server implementation for iTerm2 terminal integration. Able to manage multiple iTerm Sessions.
- **[Java Decompiler](https://github.com/idachev/mcp-javadc)** - Decompile Java bytecode into readable source code from .class files, package names, or JAR archives using CFR decompiler
- **[JavaFX](https://github.com/mcpso/mcp-server-javafx)** - Make drawings using a JavaFX canvas
- **[JavaFX](https://github.com/quarkiverse/quarkus-mcp-servers/tree/main/jfx)** - Make drawings using a JavaFX canvas
- **[JDBC](https://github.com/quarkiverse/quarkus-mcp-servers/tree/main/jdbc)** - Connect to any JDBC-compatible database and query, insert, update, delete, and more. Supports MySQL, PostgreSQL, Oracle, SQL Server, sqllite and [more](https://github.com/quarkiverse/quarkus-mcp-servers/tree/main/jdbc#supported-jdbc-variants).
- **[JMeter](https://github.com/QAInsights/jmeter-mcp-server)** - Run load testing using Apache JMeter via MCP-compliant tools.
Expand Down Expand Up @@ -798,7 +796,6 @@ A growing set of community-developed and maintained servers demonstrates various
- **[Riot Games](https://github.com/jifrozen0110/mcp-riot)** - MCP server for League of Legends – fetch player info, ranks, champion stats, and match history via Riot API.
- **[Rquest](https://github.com/xxxbrian/mcp-rquest)** - An MCP server providing realistic browser-like HTTP request capabilities with accurate TLS/JA3/JA4 fingerprints for bypassing anti-bot measures.
- **[Rust MCP Filesystem](https://github.com/rust-mcp-stack/rust-mcp-filesystem)** - Fast, asynchronous MCP server for efficient handling of various filesystem operations built with the power of Rust.
- **[Salesforce MCP](https://github.com/salesforce-mcp/salesforce-mcp)** - Salesforce MCP server. Supports cloud version Salesforce-mcp.com and allows both data & metadata functions.
- **[Salesforce MCP](https://github.com/smn2gnt/MCP-Salesforce)** - Interact with Salesforce Data and Metadata
- **[Salesforce MCP (AiondaDotCom)](https://github.com/AiondaDotCom/mcp-salesforce)** - Universal Salesforce integration with OAuth authentication, smart learning system, comprehensive backup capabilities, and full CRUD operations for any Salesforce org including custom objects and fields.
- **[Salesforce MCP Server](https://github.com/tsmztech/mcp-server-salesforce)** - Comprehensive Salesforce integration with tools for querying records, executing Apex, managing fields/objects, and handling debug logs
Expand Down Expand Up @@ -873,7 +870,6 @@ A growing set of community-developed and maintained servers demonstrates various
- **[Trello MCP Server](https://github.com/lioarce01/trello-mcp-server)** - An MCP server that interact with user Trello boards, modifying them with prompting.
- **[Tripadvisor](https://github.com/pab1it0/tripadvisor-mcp)** - A MCP server that enables LLMs to interact with Tripadvisor API, supporting location data, reviews, and photos through standardized MCP interfaces
- **[TrueNAS Core MCP](https://github.com/vespo92/TrueNasCoreMCP)** - An MCP server for interacting with TrueNAS Core.
- **[Tsuki-Mcp-Filesystem-Server](https://github.com/yuutotsuki/tsuki_mcp_filesystem_server)** - A simple, fast, and fully MCP-compliant server for listing local filesystem files. Built with Python + FastAPI. Designed for OpenAI's Agent SDK via `resources/list`.
- **[Tyk API Management](https://github.com/TykTechnologies/tyk-dashboard-mcp)** - Chat with all of your organization's managed APIs and perform other API lifecycle operations, managing tokens, users, analytics, and more.
- **[Typesense](https://github.com/suhail-ak-s/mcp-typesense-server)** - A Model Context Protocol (MCP) server implementation that provides AI models with access to Typesense search capabilities. This server enables LLMs to discover, search, and analyze data stored in Typesense collections.
- **[uniswap-poolspy-mcp](https://github.com/kukapay/uniswap-poolspy-mcp)** - An MCP server that tracks newly created liquidity pools on Uniswap across nine blockchain networks.
Expand Down
52 changes: 51 additions & 1 deletion src/filesystem/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,58 @@ Node.js server implementing Model Context Protocol (MCP) for filesystem operatio
- Move files/directories
- Search files
- Get file metadata
- Dynamic directory access control via [Roots](https://modelcontextprotocol.io/docs/concepts/roots)

## Directory Access Control

The server uses a flexible directory access control system. Directories can be specified via command-line arguments or dynamically via [Roots](https://modelcontextprotocol.io/docs/concepts/roots).

### Method 1: Command-line Arguments
Specify Allowed directories when starting the server:
```bash
mcp-server-filesystem /path/to/dir1 /path/to/dir2
```

### Method 2: MCP Roots (Recommended)
MCP clients that support [Roots](https://modelcontextprotocol.io/docs/concepts/roots) can dynamically update the Allowed directories.

Roots notified by Client to Server, completely replace any server-side Allowed directories when provided.

**Important**: If server starts without command-line arguments AND client doesn't support roots protocol (or provides empty roots), the server will throw an error during initialization.

This is the recommended method, as this enables runtime directory updates via `roots/list_changed` notifications without server restart, providing a more flexible and modern integration experience.

### How It Works

The server's directory access control follows this flow:

1. **Server Startup**
- Server starts with directories from command-line arguments (if provided)
- If no arguments provided, server starts with empty allowed directories

2. **Client Connection & Initialization**
- Client connects and sends `initialize` request with capabilities
- Server checks if client supports roots protocol (`capabilities.roots`)

3. **Roots Protocol Handling** (if client supports roots)
- **On initialization**: Server requests roots from client via `roots/list`
- Client responds with its configured roots
- Server replaces ALL allowed directories with client's roots
- **On runtime updates**: Client can send `notifications/roots/list_changed`
- Server requests updated roots and replaces allowed directories again

4. **Fallback Behavior** (if client doesn't support roots)
- Server continues using command-line directories only
- No dynamic updates possible

5. **Access Control**
- All filesystem operations are restricted to allowed directories
- Use `list_allowed_directories` tool to see current directories
- Server requires at least ONE allowed directory to operate

**Note**: The server will only allow operations within directories specified either via `args` or via Roots.


**Note**: The server will only allow operations within directories specified via `args`.

## API

Expand Down
84 changes: 84 additions & 0 deletions src/filesystem/__tests__/roots-utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import { getValidRootDirectories } from '../roots-utils.js';
import { mkdtempSync, rmSync, mkdirSync, writeFileSync, realpathSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import type { Root } from '@modelcontextprotocol/sdk/types.js';

describe('getValidRootDirectories', () => {
let testDir1: string;
let testDir2: string;
let testDir3: string;
let testFile: string;

beforeEach(() => {
// Create test directories
testDir1 = realpathSync(mkdtempSync(join(tmpdir(), 'mcp-roots-test1-')));
testDir2 = realpathSync(mkdtempSync(join(tmpdir(), 'mcp-roots-test2-')));
testDir3 = realpathSync(mkdtempSync(join(tmpdir(), 'mcp-roots-test3-')));

// Create a test file (not a directory)
testFile = join(testDir1, 'test-file.txt');
writeFileSync(testFile, 'test content');
});

afterEach(() => {
// Cleanup
rmSync(testDir1, { recursive: true, force: true });
rmSync(testDir2, { recursive: true, force: true });
rmSync(testDir3, { recursive: true, force: true });
});

describe('valid directory processing', () => {
it('should process all URI formats and edge cases', async () => {
const roots = [
{ uri: `file://${testDir1}`, name: 'File URI' },
{ uri: testDir2, name: 'Plain path' },
{ uri: testDir3 } // Plain path without name property
];

const result = await getValidRootDirectories(roots);

expect(result).toContain(testDir1);
expect(result).toContain(testDir2);
expect(result).toContain(testDir3);
expect(result).toHaveLength(3);
});

it('should normalize complex paths', async () => {
const subDir = join(testDir1, 'subdir');
mkdirSync(subDir);

const roots = [
{ uri: `file://${testDir1}/./subdir/../subdir`, name: 'Complex Path' }
];

const result = await getValidRootDirectories(roots);

expect(result).toHaveLength(1);
expect(result[0]).toBe(subDir);
});
});

describe('error handling', () => {

it('should handle various error types', async () => {
const nonExistentDir = join(tmpdir(), 'non-existent-directory-12345');
const invalidPath = '\0invalid\0path'; // Null bytes cause different error types
const roots = [
{ uri: `file://${testDir1}`, name: 'Valid Dir' },
{ uri: `file://${nonExistentDir}`, name: 'Non-existent Dir' },
{ uri: `file://${testFile}`, name: 'File Not Dir' },
{ uri: `file://${invalidPath}`, name: 'Invalid Path' }
];

const result = await getValidRootDirectories(roots);

expect(result).toContain(testDir1);
expect(result).not.toContain(nonExistentDir);
expect(result).not.toContain(testFile);
expect(result).not.toContain(invalidPath);
expect(result).toHaveLength(1);
});
});
});
68 changes: 62 additions & 6 deletions src/filesystem/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
CallToolRequestSchema,
ListToolsRequestSchema,
ToolSchema,
RootsListChangedNotificationSchema,
type Root,
} from "@modelcontextprotocol/sdk/types.js";
import fs from "fs/promises";
import path from "path";
Expand All @@ -16,12 +18,16 @@ import { zodToJsonSchema } from "zod-to-json-schema";
import { diffLines, createTwoFilesPatch } from 'diff';
import { minimatch } from 'minimatch';
import { isPathWithinAllowedDirectories } from './path-validation.js';
import { getValidRootDirectories } from './roots-utils.js';

// Command line argument parsing
const args = process.argv.slice(2);
if (args.length === 0) {
console.error("Usage: mcp-server-filesystem <allowed-directory> [additional-directories...]");
process.exit(1);
console.error("Usage: mcp-server-filesystem [allowed-directory] [additional-directories...]");
console.error("Note: Allowed directories can be provided via:");
console.error(" 1. Command-line arguments (shown above)");
console.error(" 2. MCP roots protocol (if client supports it)");
console.error("At least one directory must be provided by EITHER method for the server to operate.");
}

// Normalize all paths consistently
Expand All @@ -37,7 +43,7 @@ function expandHome(filepath: string): string {
}

// Store allowed directories in normalized and resolved form
const allowedDirectories = await Promise.all(
let allowedDirectories = await Promise.all(
args.map(async (dir) => {
const expanded = expandHome(dir);
const absolute = path.resolve(expanded);
Expand Down Expand Up @@ -573,8 +579,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
{
name: "list_allowed_directories",
description:
"Returns the list of directories that this server is allowed to access. " +
"Use this to understand which directories are available before trying to access files.",
"Returns the list of root directories that this server is allowed to access. " +
"Use this to understand which directories are available before trying to access files. ",
inputSchema: {
type: "object",
properties: {},
Expand Down Expand Up @@ -890,12 +896,62 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
}
});

// Updates allowed directories based on MCP client roots
async function updateAllowedDirectoriesFromRoots(requestedRoots: Root[]) {
const validatedRootDirs = await getValidRootDirectories(requestedRoots);
if (validatedRootDirs.length > 0) {
allowedDirectories = [...validatedRootDirs];
console.error(`Updated allowed directories from MCP roots: ${validatedRootDirs.length} valid directories`);
} else {
console.error("No valid root directories provided by client");
}
}

// Handles dynamic roots updates during runtime, when client sends "roots/list_changed" notification, server fetches the updated roots and replaces all allowed directories with the new roots.
server.setNotificationHandler(RootsListChangedNotificationSchema, async () => {
try {
// Request the updated roots list from the client
const response = await server.listRoots();
if (response && 'roots' in response) {
await updateAllowedDirectoriesFromRoots(response.roots);
}
} catch (error) {
console.error("Failed to request roots from client:", error instanceof Error ? error.message : String(error));
}
});

// Handles post-initialization setup, specifically checking for and fetching MCP roots.
server.oninitialized = async () => {
const clientCapabilities = server.getClientCapabilities();

if (clientCapabilities?.roots) {
try {
const response = await server.listRoots();
if (response && 'roots' in response) {
await updateAllowedDirectoriesFromRoots(response.roots);
} else {
console.error("Client returned no roots set, keeping current settings");
}
} catch (error) {
console.error("Failed to request initial roots from client:", error instanceof Error ? error.message : String(error));
}
} else {
if (allowedDirectories.length > 0) {
console.error("Client does not support MCP Roots, using allowed directories set from server args:", allowedDirectories);
}else{
throw new Error(`Server cannot operate: No allowed directories available. Server was started without command-line directories and client either does not support MCP roots protocol or provided empty roots. Please either: 1) Start server with directory arguments, or 2) Use a client that supports MCP roots protocol and provides valid root directories.`);
}
}
};

// Start server
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Secure MCP Filesystem Server running on stdio");
console.error("Allowed directories:", allowedDirectories);
if (allowedDirectories.length === 0) {
console.error("Started without allowed directories - waiting for client to provide roots via MCP protocol");
}
}

runServer().catch((error) => {
Expand Down
Loading