UNPKG

sce-tools-mcp

Version:

SCE Tools MCP Server with full Python CLI feature parity - Model Context Protocol server for SCE (Spark Creative Editor) game development

168 lines 5.77 kB
/** * MCP Roots 管理服务 * 负责处理客户端的根目录信息,为路径解析提供准确的工作空间上下文 */ import { existsSync, readdirSync, statSync } from 'fs'; import { join, normalize } from 'path'; export class RootsManager { constructor() { this.roots = []; this.preferredWorkspace = null; } /** * 更新根目录列表(来自客户端) */ updateRoots(roots) { console.error('🔗 Updating MCP roots from client'); this.roots = roots || []; // 重新计算首选工作空间 this.preferredWorkspace = this.determinePreferredWorkspace(); // 记录接收到的根目录 this.roots.forEach(root => { console.error(` 📁 Root: ${root.uri} (${root.name || 'unnamed'})`); }); if (this.preferredWorkspace) { console.error(`✅ Preferred workspace determined: ${this.preferredWorkspace}`); } else { console.error('⚠️ No suitable workspace found in provided roots'); } } /** * 获取首选工作空间路径 */ getPreferredWorkspace() { return this.preferredWorkspace; } /** * 获取所有根目录 */ getRoots() { return [...this.roots]; } /** * 检查是否有有效的根目录 */ hasRoots() { return this.roots.length > 0; } /** * 从根目录列表中确定最适合的工作空间路径 */ determinePreferredWorkspace() { if (this.roots.length === 0) { return null; } // 优先级策略: // 1. 包含 SCE 项目特征的目录(config.ini + GameEntry + project) // 2. 包含 .git 的目录(Git 项目) // 3. 包含 .vscode 的目录(VS Code 项目) // 4. 第一个 file:// URI const workspaceCandidates = []; for (const root of this.roots) { const path = this.uriToPath(root.uri); if (!path || !existsSync(path)) { continue; } workspaceCandidates.push(path); // 检查 SCE 项目特征 if (this.hasSceProjectFeatures(path)) { console.error(`🎯 Found SCE project workspace: ${path}`); return normalize(path); } } // 如果没有找到 SCE 特征,使用其他策略 for (const path of workspaceCandidates) { // Git 项目 if (existsSync(`${path}/.git`)) { console.error(`📦 Found Git workspace: ${path}`); return normalize(path); } // VS Code 项目 if (existsSync(`${path}/.vscode`)) { console.error(`💻 Found VS Code workspace: ${path}`); return normalize(path); } } // 回退到第一个有效路径 if (workspaceCandidates.length > 0) { const fallbackPath = workspaceCandidates[0]; console.error(`📁 Using first valid root as workspace: ${fallbackPath}`); return normalize(fallbackPath); } return null; } /** * 将 URI 转换为本地文件路径 */ uriToPath(uri) { try { // 处理 file:// URI if (uri.startsWith('file://')) { let path = uri.slice(7); // 移除 'file://' // 处理 Windows 路径 if (process.platform === 'win32') { // file:///C:/path 格式 if (path.startsWith('/') && path[2] === ':') { path = path.slice(1); // 移除开头的 '/' } // 将 '/' 转换为 '\' path = path.replace(/\//g, '\\'); } // URL 解码 path = decodeURIComponent(path); return normalize(path); } // 如果不是 file:// URI,尝试直接作为路径使用 if (existsSync(uri)) { return normalize(uri); } return null; } catch (error) { console.error(`⚠️ Failed to convert URI to path: ${uri}, error: ${error}`); return null; } } /** * 检查目录是否包含 SCE 项目特征 */ hasSceProjectFeatures(dirPath) { try { const entries = readdirSync(dirPath); // 检查是否有 config.ini 和 project 目录(SCE 项目特征) const hasConfigIni = entries.includes('config.ini'); const hasProjectDir = entries.includes('project') && existsSync(join(dirPath, 'project')) && statSync(join(dirPath, 'project')).isDirectory(); // 检查是否有 GameEntry 目录(SCE 项目特征) const hasGameEntry = entries.includes('GameEntry') && existsSync(join(dirPath, 'GameEntry')) && statSync(join(dirPath, 'GameEntry')).isDirectory(); return hasConfigIni && hasProjectDir && hasGameEntry; } catch (error) { return false; } } /** * 清除根目录信息 */ clearRoots() { this.roots = []; this.preferredWorkspace = null; console.error('🧹 Cleared all MCP roots'); } /** * 获取调试信息 */ getDebugInfo() { return { rootsCount: this.roots.length, roots: this.roots, preferredWorkspace: this.preferredWorkspace, hasValidWorkspace: this.preferredWorkspace !== null }; } } //# sourceMappingURL=roots-manager.js.map