Skip to content

Commit 4ed232a

Browse files
authored
Merge branch 'main' into elicitation-support
2 parents 0fc34ab + f13adbd commit 4ed232a

File tree

5 files changed

+274
-12
lines changed

5 files changed

+274
-12
lines changed

README.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ Official integrations are maintained by companies building production ready MCP
226226
- <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.
227227
- <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.
228228
- <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.
229-
- <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
229+
- <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
230230
- <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)
231231
- <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.
232232
- <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.
@@ -526,7 +526,6 @@ A growing set of community-developed and maintained servers demonstrates various
526526
- **[Feyod](https://github.com/jeroenvdmeer/feyod-mcp)** - A server that answers questions about football matches, and specialised in the football club Feyenoord.
527527
- **[Fibaro HC3](https://github.com/coding-sailor/mcp-server-hc3)** - MCP server for Fibaro Home Center 3 smart home systems.
528528
- **[Figma](https://github.com/GLips/Figma-Context-MCP)** - Give your coding agent direct access to Figma file data, helping it one-shot design implementation.
529-
- **[Fingertip](https://github.com/fingertip-com/fingertip-mcp)** - MCP server for Fingertip.com to search and create new sites.
530529
- **[Firebase](https://github.com/gannonh/firebase-mcp)** - Server to interact with Firebase services including Firebase Authentication, Firestore, and Firebase Storage.
531530
- **[FireCrawl](https://github.com/vrknetha/mcp-server-firecrawl)** - Advanced web scraping with JavaScript rendering, PDF support, and smart rate limiting
532531
- **[FitBit MCP Server](https://github.com/NitayRabi/fitbit-mcp)** - An MCP server that connects to FitBit API using a token obtained from OAuth flow.
@@ -599,7 +598,6 @@ A growing set of community-developed and maintained servers demonstrates various
599598
- **[iTerm MCP](https://github.com/ferrislucas/iterm-mcp)** - Integration with iTerm2 terminal emulator for macOS, enabling LLMs to execute and monitor terminal commands.
600599
- **[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.
601600
- **[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
602-
- **[JavaFX](https://github.com/mcpso/mcp-server-javafx)** - Make drawings using a JavaFX canvas
603601
- **[JavaFX](https://github.com/quarkiverse/quarkus-mcp-servers/tree/main/jfx)** - Make drawings using a JavaFX canvas
604602
- **[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).
605603
- **[JMeter](https://github.com/QAInsights/jmeter-mcp-server)** - Run load testing using Apache JMeter via MCP-compliant tools.
@@ -798,7 +796,6 @@ A growing set of community-developed and maintained servers demonstrates various
798796
- **[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.
799797
- **[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.
800798
- **[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.
801-
- **[Salesforce MCP](https://github.com/salesforce-mcp/salesforce-mcp)** - Salesforce MCP server. Supports cloud version Salesforce-mcp.com and allows both data & metadata functions.
802799
- **[Salesforce MCP](https://github.com/smn2gnt/MCP-Salesforce)** - Interact with Salesforce Data and Metadata
803800
- **[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.
804801
- **[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
@@ -873,7 +870,6 @@ A growing set of community-developed and maintained servers demonstrates various
873870
- **[Trello MCP Server](https://github.com/lioarce01/trello-mcp-server)** - An MCP server that interact with user Trello boards, modifying them with prompting.
874871
- **[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
875872
- **[TrueNAS Core MCP](https://github.com/vespo92/TrueNasCoreMCP)** - An MCP server for interacting with TrueNAS Core.
876-
- **[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`.
877873
- **[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.
878874
- **[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.
879875
- **[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.

src/filesystem/README.md

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,58 @@ Node.js server implementing Model Context Protocol (MCP) for filesystem operatio
99
- Move files/directories
1010
- Search files
1111
- Get file metadata
12+
- Dynamic directory access control via [Roots](https://modelcontextprotocol.io/docs/concepts/roots)
13+
14+
## Directory Access Control
15+
16+
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).
17+
18+
### Method 1: Command-line Arguments
19+
Specify Allowed directories when starting the server:
20+
```bash
21+
mcp-server-filesystem /path/to/dir1 /path/to/dir2
22+
```
23+
24+
### Method 2: MCP Roots (Recommended)
25+
MCP clients that support [Roots](https://modelcontextprotocol.io/docs/concepts/roots) can dynamically update the Allowed directories.
26+
27+
Roots notified by Client to Server, completely replace any server-side Allowed directories when provided.
28+
29+
**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.
30+
31+
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.
32+
33+
### How It Works
34+
35+
The server's directory access control follows this flow:
36+
37+
1. **Server Startup**
38+
- Server starts with directories from command-line arguments (if provided)
39+
- If no arguments provided, server starts with empty allowed directories
40+
41+
2. **Client Connection & Initialization**
42+
- Client connects and sends `initialize` request with capabilities
43+
- Server checks if client supports roots protocol (`capabilities.roots`)
44+
45+
3. **Roots Protocol Handling** (if client supports roots)
46+
- **On initialization**: Server requests roots from client via `roots/list`
47+
- Client responds with its configured roots
48+
- Server replaces ALL allowed directories with client's roots
49+
- **On runtime updates**: Client can send `notifications/roots/list_changed`
50+
- Server requests updated roots and replaces allowed directories again
51+
52+
4. **Fallback Behavior** (if client doesn't support roots)
53+
- Server continues using command-line directories only
54+
- No dynamic updates possible
55+
56+
5. **Access Control**
57+
- All filesystem operations are restricted to allowed directories
58+
- Use `list_allowed_directories` tool to see current directories
59+
- Server requires at least ONE allowed directory to operate
60+
61+
**Note**: The server will only allow operations within directories specified either via `args` or via Roots.
62+
1263

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

1565
## API
1666

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
2+
import { getValidRootDirectories } from '../roots-utils.js';
3+
import { mkdtempSync, rmSync, mkdirSync, writeFileSync, realpathSync } from 'fs';
4+
import { tmpdir } from 'os';
5+
import { join } from 'path';
6+
import type { Root } from '@modelcontextprotocol/sdk/types.js';
7+
8+
describe('getValidRootDirectories', () => {
9+
let testDir1: string;
10+
let testDir2: string;
11+
let testDir3: string;
12+
let testFile: string;
13+
14+
beforeEach(() => {
15+
// Create test directories
16+
testDir1 = realpathSync(mkdtempSync(join(tmpdir(), 'mcp-roots-test1-')));
17+
testDir2 = realpathSync(mkdtempSync(join(tmpdir(), 'mcp-roots-test2-')));
18+
testDir3 = realpathSync(mkdtempSync(join(tmpdir(), 'mcp-roots-test3-')));
19+
20+
// Create a test file (not a directory)
21+
testFile = join(testDir1, 'test-file.txt');
22+
writeFileSync(testFile, 'test content');
23+
});
24+
25+
afterEach(() => {
26+
// Cleanup
27+
rmSync(testDir1, { recursive: true, force: true });
28+
rmSync(testDir2, { recursive: true, force: true });
29+
rmSync(testDir3, { recursive: true, force: true });
30+
});
31+
32+
describe('valid directory processing', () => {
33+
it('should process all URI formats and edge cases', async () => {
34+
const roots = [
35+
{ uri: `file://${testDir1}`, name: 'File URI' },
36+
{ uri: testDir2, name: 'Plain path' },
37+
{ uri: testDir3 } // Plain path without name property
38+
];
39+
40+
const result = await getValidRootDirectories(roots);
41+
42+
expect(result).toContain(testDir1);
43+
expect(result).toContain(testDir2);
44+
expect(result).toContain(testDir3);
45+
expect(result).toHaveLength(3);
46+
});
47+
48+
it('should normalize complex paths', async () => {
49+
const subDir = join(testDir1, 'subdir');
50+
mkdirSync(subDir);
51+
52+
const roots = [
53+
{ uri: `file://${testDir1}/./subdir/../subdir`, name: 'Complex Path' }
54+
];
55+
56+
const result = await getValidRootDirectories(roots);
57+
58+
expect(result).toHaveLength(1);
59+
expect(result[0]).toBe(subDir);
60+
});
61+
});
62+
63+
describe('error handling', () => {
64+
65+
it('should handle various error types', async () => {
66+
const nonExistentDir = join(tmpdir(), 'non-existent-directory-12345');
67+
const invalidPath = '\0invalid\0path'; // Null bytes cause different error types
68+
const roots = [
69+
{ uri: `file://${testDir1}`, name: 'Valid Dir' },
70+
{ uri: `file://${nonExistentDir}`, name: 'Non-existent Dir' },
71+
{ uri: `file://${testFile}`, name: 'File Not Dir' },
72+
{ uri: `file://${invalidPath}`, name: 'Invalid Path' }
73+
];
74+
75+
const result = await getValidRootDirectories(roots);
76+
77+
expect(result).toContain(testDir1);
78+
expect(result).not.toContain(nonExistentDir);
79+
expect(result).not.toContain(testFile);
80+
expect(result).not.toContain(invalidPath);
81+
expect(result).toHaveLength(1);
82+
});
83+
});
84+
});

src/filesystem/index.ts

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
CallToolRequestSchema,
77
ListToolsRequestSchema,
88
ToolSchema,
9+
RootsListChangedNotificationSchema,
10+
type Root,
911
} from "@modelcontextprotocol/sdk/types.js";
1012
import fs from "fs/promises";
1113
import path from "path";
@@ -16,12 +18,16 @@ import { zodToJsonSchema } from "zod-to-json-schema";
1618
import { diffLines, createTwoFilesPatch } from 'diff';
1719
import { minimatch } from 'minimatch';
1820
import { isPathWithinAllowedDirectories } from './path-validation.js';
21+
import { getValidRootDirectories } from './roots-utils.js';
1922

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

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

3945
// Store allowed directories in normalized and resolved form
40-
const allowedDirectories = await Promise.all(
46+
let allowedDirectories = await Promise.all(
4147
args.map(async (dir) => {
4248
const expanded = expandHome(dir);
4349
const absolute = path.resolve(expanded);
@@ -573,8 +579,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
573579
{
574580
name: "list_allowed_directories",
575581
description:
576-
"Returns the list of directories that this server is allowed to access. " +
577-
"Use this to understand which directories are available before trying to access files.",
582+
"Returns the list of root directories that this server is allowed to access. " +
583+
"Use this to understand which directories are available before trying to access files. ",
578584
inputSchema: {
579585
type: "object",
580586
properties: {},
@@ -890,12 +896,62 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
890896
}
891897
});
892898

899+
// Updates allowed directories based on MCP client roots
900+
async function updateAllowedDirectoriesFromRoots(requestedRoots: Root[]) {
901+
const validatedRootDirs = await getValidRootDirectories(requestedRoots);
902+
if (validatedRootDirs.length > 0) {
903+
allowedDirectories = [...validatedRootDirs];
904+
console.error(`Updated allowed directories from MCP roots: ${validatedRootDirs.length} valid directories`);
905+
} else {
906+
console.error("No valid root directories provided by client");
907+
}
908+
}
909+
910+
// 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.
911+
server.setNotificationHandler(RootsListChangedNotificationSchema, async () => {
912+
try {
913+
// Request the updated roots list from the client
914+
const response = await server.listRoots();
915+
if (response && 'roots' in response) {
916+
await updateAllowedDirectoriesFromRoots(response.roots);
917+
}
918+
} catch (error) {
919+
console.error("Failed to request roots from client:", error instanceof Error ? error.message : String(error));
920+
}
921+
});
922+
923+
// Handles post-initialization setup, specifically checking for and fetching MCP roots.
924+
server.oninitialized = async () => {
925+
const clientCapabilities = server.getClientCapabilities();
926+
927+
if (clientCapabilities?.roots) {
928+
try {
929+
const response = await server.listRoots();
930+
if (response && 'roots' in response) {
931+
await updateAllowedDirectoriesFromRoots(response.roots);
932+
} else {
933+
console.error("Client returned no roots set, keeping current settings");
934+
}
935+
} catch (error) {
936+
console.error("Failed to request initial roots from client:", error instanceof Error ? error.message : String(error));
937+
}
938+
} else {
939+
if (allowedDirectories.length > 0) {
940+
console.error("Client does not support MCP Roots, using allowed directories set from server args:", allowedDirectories);
941+
}else{
942+
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.`);
943+
}
944+
}
945+
};
946+
893947
// Start server
894948
async function runServer() {
895949
const transport = new StdioServerTransport();
896950
await server.connect(transport);
897951
console.error("Secure MCP Filesystem Server running on stdio");
898-
console.error("Allowed directories:", allowedDirectories);
952+
if (allowedDirectories.length === 0) {
953+
console.error("Started without allowed directories - waiting for client to provide roots via MCP protocol");
954+
}
899955
}
900956

901957
runServer().catch((error) => {

0 commit comments

Comments
 (0)