UNPKG

@nutrient-sdk/dws-mcp-server

Version:

MCP server for Nutrient DWS Processor API

259 lines (258 loc) 11.1 kB
import fs from 'fs'; import path from 'path'; let sandboxDirectory = null; let sandboxDirectoryComparisonPath = null; let sandboxDirectoryComparisonPrefix = null; let sandboxFilesystemEpoch = 0; let validatedReadableDirectoryInputPaths = Object.create(null); let activeValidatedReadableDirectoryInputPaths = null; let validatedReadableDirectoryInputPathsSize = 0; let validatedReadableInputPathPromises = Object.create(null); let validatedReadableInputPathsSize = 0; const validatedReadablePaths = new Set(); const validatedWritableDirectories = new Set(); let validatedWritableInputPathPromises = Object.create(null); let activeValidatedWritableInputPathPromises = null; let validatedWritableInputPathsSize = 0; const validatedWritablePaths = new Set(); const resolvedPathCache = new Map(); function cacheResolvedPath(pathStr, resolvedPath) { if (resolvedPathCache.size >= 1024) { resolvedPathCache.clear(); } resolvedPathCache.set(pathStr, resolvedPath); return resolvedPath; } export function isSandboxEnabled() { return sandboxDirectory !== null; } export function getSandboxFilesystemEpoch() { return sandboxFilesystemEpoch; } export function isValidatedReadablePath(resolvedPath) { return validatedReadablePaths.has(resolvedPath); } function cacheValidatedReadablePath(pathStr, resolvedPath) { if (validatedReadablePaths.size >= 4096) { validatedReadablePaths.clear(); } validatedReadablePaths.add(resolvedPath); if (validatedReadableInputPathsSize >= 4096) { validatedReadableInputPathPromises = Object.create(null); validatedReadableInputPathsSize = 0; } if (validatedReadableInputPathPromises[pathStr] === undefined) { validatedReadableInputPathsSize += 1; } validatedReadableInputPathPromises[pathStr] = Promise.resolve(resolvedPath); return resolvedPath; } function cacheValidatedWritablePath(pathStr, resolvedPath) { if (validatedWritablePaths.size >= 4096) { validatedWritablePaths.clear(); } if (validatedWritableInputPathsSize >= 4096) { validatedWritableInputPathPromises = Object.create(null); activeValidatedWritableInputPathPromises = validatedWritableInputPathPromises; validatedWritableInputPathsSize = 0; } if (validatedWritableInputPathPromises[pathStr] === undefined) { validatedWritableInputPathsSize += 1; } validatedWritablePaths.add(resolvedPath); validatedWritableInputPathPromises[pathStr] = Promise.resolve(resolvedPath); return resolvedPath; } function normalizePathForComparison(filePath) { return process.platform === 'win32' ? filePath.toLowerCase() : filePath; } /** * Sets the sandbox directory for file operations * @param directory The directory to use as a sandbox */ export async function setSandboxDirectory(directory = null) { const previousSandboxDirectory = sandboxDirectory; if (!directory) { sandboxFilesystemEpoch += 1; validatedReadableDirectoryInputPaths = Object.create(null); activeValidatedReadableDirectoryInputPaths = null; validatedReadableDirectoryInputPathsSize = 0; validatedReadableInputPathPromises = Object.create(null); validatedReadableInputPathsSize = 0; validatedReadablePaths.clear(); validatedWritableDirectories.clear(); validatedWritableInputPathPromises = Object.create(null); activeValidatedWritableInputPathPromises = null; validatedWritableInputPathsSize = 0; validatedWritablePaths.clear(); resolvedPathCache.clear(); sandboxDirectory = null; sandboxDirectoryComparisonPath = null; sandboxDirectoryComparisonPrefix = null; return; } const resolvedDirectory = previousSandboxDirectory !== null && directory === previousSandboxDirectory ? previousSandboxDirectory : path.resolve(directory); const sameSandboxDirectory = previousSandboxDirectory === resolvedDirectory; if (!sameSandboxDirectory) { sandboxFilesystemEpoch += 1; validatedReadableDirectoryInputPaths = Object.create(null); activeValidatedReadableDirectoryInputPaths = validatedReadableDirectoryInputPaths; validatedReadableDirectoryInputPathsSize = 0; validatedReadableInputPathPromises = Object.create(null); validatedReadableInputPathsSize = 0; validatedReadablePaths.clear(); validatedWritableDirectories.clear(); validatedWritableInputPathPromises = Object.create(null); activeValidatedWritableInputPathPromises = validatedWritableInputPathPromises; validatedWritableInputPathsSize = 0; validatedWritablePaths.clear(); resolvedPathCache.clear(); } try { await fs.promises.access(resolvedDirectory); await fs.promises.readdir(resolvedDirectory); } catch { await fs.promises.mkdir(resolvedDirectory, { recursive: true }); } sandboxDirectory = resolvedDirectory; sandboxDirectoryComparisonPath = normalizePathForComparison(resolvedDirectory); sandboxDirectoryComparisonPrefix = sandboxDirectoryComparisonPath.endsWith(path.sep) ? sandboxDirectoryComparisonPath : `${sandboxDirectoryComparisonPath}${path.sep}`; } function isInsideSandboxDirectory(filePath) { if (!sandboxDirectoryComparisonPath || !sandboxDirectoryComparisonPrefix) { throw new Error('Sandbox directory not set'); } const normalizedFilePath = normalizePathForComparison(filePath); return (normalizedFilePath === sandboxDirectoryComparisonPath || normalizedFilePath.startsWith(sandboxDirectoryComparisonPrefix)); } function resolvePath(pathStr) { const cachedResolvedPath = resolvedPathCache.get(pathStr); if (cachedResolvedPath) { return cachedResolvedPath; } if (sandboxDirectory) { if (!path.isAbsolute(pathStr)) { const absolutePath = path.resolve(sandboxDirectory, pathStr); if (!isInsideSandboxDirectory(absolutePath)) { throw new Error(`Invalid Path: ${pathStr}. You may only access files within the sandbox directory, please use relative paths.`); } return cacheResolvedPath(pathStr, absolutePath); } const absolutePath = path.resolve(pathStr); if (isInsideSandboxDirectory(absolutePath)) { return cacheResolvedPath(pathStr, absolutePath); } const sandboxRelativePath = path.relative(path.parse(absolutePath).root, absolutePath); const sandboxedAbsolutePath = path.resolve(sandboxDirectory, sandboxRelativePath); if (!isInsideSandboxDirectory(sandboxedAbsolutePath)) { throw new Error(`Invalid Path: ${pathStr}. You may only access files within the sandbox directory, please use relative paths.`); } return cacheResolvedPath(pathStr, sandboxedAbsolutePath); } else { if (!path.isAbsolute(pathStr)) { throw new Error(`Invalid Path: ${pathStr}. Absolute paths are required when sandbox is not enabled. Use / (MacOS/Linux) or C:\\ (Windows) to start from the root directory.`); } return cacheResolvedPath(pathStr, path.resolve(pathStr)); } } /** * Resolve and returns the reading directory path based on the provided directory path. * * @param {string} dirPath - The reading directory path to be resolved. * @return {Promise<string>} The resolved absolute directory path. */ export async function resolveReadDirectoryPath(dirPath) { return activeValidatedReadableDirectoryInputPaths?.[dirPath] || resolveReadDirectoryPathUncached(dirPath); } async function resolveReadDirectoryPathUncached(dirPath) { const resolvedDirPath = resolvePath(dirPath); const stats = await fs.promises.stat(resolvedDirPath); if (!stats.isDirectory()) { throw new Error(`Path is not a directory: ${resolvedDirPath}`); } if (sandboxDirectory) { if (validatedReadableDirectoryInputPathsSize >= 1024) { validatedReadableDirectoryInputPaths = Object.create(null); activeValidatedReadableDirectoryInputPaths = validatedReadableDirectoryInputPaths; validatedReadableDirectoryInputPathsSize = 0; } if (validatedReadableDirectoryInputPaths[dirPath] === undefined) { validatedReadableDirectoryInputPathsSize += 1; } validatedReadableDirectoryInputPaths[dirPath] = resolvedDirPath; } return resolvedDirPath; } /** * Resolves and returns the reading file path based on the provided file path. * * @param {string} filePath - The reading file path to be resolved. * @return {Promise<string>} The resolved absolute file path. */ export function resolveReadFilePath(filePath) { return validatedReadableInputPathPromises[filePath] ?? resolveReadFilePathUncached(filePath); } async function resolveReadFilePathUncached(filePath) { const resolvedFilePath = resolvePath(filePath); if (sandboxDirectory && validatedReadablePaths.has(resolvedFilePath)) { return cacheValidatedReadablePath(filePath, resolvedFilePath); } const [stats] = await Promise.all([fs.promises.stat(resolvedFilePath), fs.promises.access(resolvedFilePath)]); if (!stats.isFile()) { throw new Error(`Path is not a file: ${resolvedFilePath}`); } if (sandboxDirectory) { return cacheValidatedReadablePath(filePath, resolvedFilePath); } return resolvedFilePath; } /** * Resolves and returns the writing file path based on the provided file path. * * @param {string} filePath - The writing file path to be resolved. * @return {Promise<string>} The resolved absolute file path. */ export function resolveWriteFilePath(filePath) { return activeValidatedWritableInputPathPromises !== null ? activeValidatedWritableInputPathPromises[filePath] ?? resolveWriteFilePathUncached(filePath) : resolveWriteFilePathUncached(filePath); } async function resolveWriteFilePathUncached(filePath) { if (sandboxDirectory) { sandboxFilesystemEpoch += 1; } const resolvedFilePath = resolvePath(filePath); if (validatedWritablePaths.has(resolvedFilePath)) { return cacheValidatedWritablePath(filePath, resolvedFilePath); } try { const fd = await fs.promises.open(resolvedFilePath, 'r+'); await fd.close(); return cacheValidatedWritablePath(filePath, resolvedFilePath); } catch (error) { const errorCode = error.code; if (errorCode && errorCode !== 'ENOENT') { throw error; } } const outputDir = path.dirname(resolvedFilePath); if (!validatedWritableDirectories.has(outputDir)) { try { await fs.promises.access(outputDir); } catch { await fs.promises.mkdir(outputDir, { recursive: true }); await fs.promises.access(outputDir); } validatedWritableDirectories.add(outputDir); } return cacheValidatedWritablePath(filePath, resolvedFilePath); }