Skip to main content
Glama

Filesystem MCP Server

state.ts7.69 kB
import path from 'path'; import { config } from '../config/index.js'; import { BaseErrorCode, McpError } from '../types-global/errors.js'; import { logger } from '../utils/internal/logger.js'; import { RequestContext, requestContextService } from '../utils/internal/requestContext.js'; import { sanitization } from '../utils/security/sanitization.js'; /** * Simple in-memory state management for the MCP server session. * This state is cleared when the server restarts. */ class ServerState { private defaultFilesystemPath: string | null = null; private fsBaseDirectory: string | null = null; constructor() { this.fsBaseDirectory = config.fsBaseDirectory || null; if (this.fsBaseDirectory) { // Ensure fsBaseDirectory itself is sanitized and absolute for internal use const initContext = requestContextService.createRequestContext({ operation: 'ServerStateInit' }); try { const sanitizedBase = sanitization.sanitizePath(this.fsBaseDirectory, { allowAbsolute: true, toPosix: true }); this.fsBaseDirectory = sanitizedBase.sanitizedPath; logger.info(`Filesystem operations will be restricted to base directory: ${this.fsBaseDirectory}`, initContext); } catch (error) { logger.error(`Invalid FS_BASE_DIRECTORY configured: ${this.fsBaseDirectory}. It will be ignored.`, { ...initContext, error: error instanceof Error ? error.message : String(error) }); this.fsBaseDirectory = null; // Disable if invalid } } } /** * Sets the default filesystem path for the current session. * The path is sanitized and validated. * * @param newPath - The absolute path to set as default. * @param context - The request context for logging. * @throws {McpError} If the path is invalid or not absolute. */ setDefaultFilesystemPath(newPath: string, context: RequestContext): void { logger.debug(`Attempting to set default filesystem path: ${newPath}`, context); try { // Ensure the path is absolute before storing if (!path.isAbsolute(newPath)) { throw new McpError(BaseErrorCode.VALIDATION_ERROR, 'Default path must be absolute.', { ...context, path: newPath }); } // Sanitize the absolute path (mainly for normalization and basic checks) // We don't restrict to a rootDir here as it's a user-provided default. const sanitizedPathInfo = sanitization.sanitizePath(newPath, { allowAbsolute: true, toPosix: true }); this.defaultFilesystemPath = sanitizedPathInfo.sanitizedPath; logger.info(`Default filesystem path set to: ${this.defaultFilesystemPath}`, context); } catch (error) { logger.error(`Failed to set default filesystem path: ${newPath}`, { ...context, error: error instanceof Error ? error.message : String(error) }); // Rethrow McpError or wrap other errors if (error instanceof McpError) { throw error; } throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid default path provided: ${error instanceof Error ? error.message : String(error)}`, { ...context, path: newPath, originalError: error }); } } /** * Gets the currently set default filesystem path. * * @returns The absolute default path or null if not set. */ getDefaultFilesystemPath(): string | null { return this.defaultFilesystemPath; } /** * Clears the default filesystem path. * @param context - The request context for logging. */ clearDefaultFilesystemPath(context: RequestContext): void { logger.info('Clearing default filesystem path.', context); this.defaultFilesystemPath = null; } /** * Resolves a given path against the default path if the given path is relative. * If the given path is absolute, it's returned directly after sanitization. * If the given path is relative and no default path is set, an error is thrown. * * @param requestedPath - The path provided by the user (can be relative or absolute). * @param context - The request context for logging and error handling. * @returns The resolved, sanitized, absolute path. * @throws {McpError} If a relative path is given without a default path set, or if sanitization fails. */ resolvePath(requestedPath: string, context: RequestContext): string { logger.debug(`Resolving path: ${requestedPath}`, { ...context, defaultPath: this.defaultFilesystemPath, fsBaseDirectory: this.fsBaseDirectory }); let absolutePath: string; if (path.isAbsolute(requestedPath)) { absolutePath = requestedPath; logger.debug('Provided path is absolute.', { ...context, path: absolutePath }); } else { if (!this.defaultFilesystemPath) { logger.warning('Relative path provided but no default path is set.', { ...context, path: requestedPath }); throw new McpError( BaseErrorCode.VALIDATION_ERROR, 'Relative path provided, but no default filesystem path has been set for this session. Please provide an absolute path or set a default path first.', { ...context, path: requestedPath } ); } absolutePath = path.join(this.defaultFilesystemPath, requestedPath); logger.debug(`Resolved relative path against default: ${absolutePath}`, { ...context, relativePath: requestedPath, defaultPath: this.defaultFilesystemPath }); } let sanitizedAbsolutePath: string; try { // Sanitize the path first. allowAbsolute is true as we've resolved it. // No rootDir is enforced by sanitizePath itself here; boundary check is next. const sanitizedPathInfo = sanitization.sanitizePath(absolutePath, { allowAbsolute: true, toPosix: true }); sanitizedAbsolutePath = sanitizedPathInfo.sanitizedPath; logger.debug(`Sanitized resolved path: ${sanitizedAbsolutePath}`, { ...context, originalPath: absolutePath }); } catch (error) { logger.error(`Failed to sanitize resolved path: ${absolutePath}`, { ...context, error: error instanceof Error ? error.message : String(error) }); if (error instanceof McpError) { throw error; // Rethrow validation errors from sanitizePath } throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to process path: ${error instanceof Error ? error.message : String(error)}`, { ...context, path: absolutePath, originalError: error }); } // Enforce FS_BASE_DIRECTORY boundary if it's set if (this.fsBaseDirectory) { // Normalize both paths for a reliable comparison const normalizedFsBaseDirectory = path.normalize(this.fsBaseDirectory); const normalizedSanitizedAbsolutePath = path.normalize(sanitizedAbsolutePath); // Check if the sanitized absolute path is within the base directory if (!normalizedSanitizedAbsolutePath.startsWith(normalizedFsBaseDirectory + path.sep) && normalizedSanitizedAbsolutePath !== normalizedFsBaseDirectory) { logger.error( `Path access violation: Attempted to access path "${sanitizedAbsolutePath}" which is outside the configured FS_BASE_DIRECTORY "${this.fsBaseDirectory}".`, { ...context, requestedPath, resolvedPath: sanitizedAbsolutePath, fsBaseDirectory: this.fsBaseDirectory } ); throw new McpError( BaseErrorCode.FORBIDDEN, `Access denied: The path "${requestedPath}" resolves to a location outside the allowed base directory.`, { ...context, requestedPath, resolvedPath: sanitizedAbsolutePath } ); } logger.debug(`Path is within FS_BASE_DIRECTORY: ${sanitizedAbsolutePath}`, context); } return sanitizedAbsolutePath; } } // Export a singleton instance export const serverState = new ServerState();

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cyanheads/filesystem-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server