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
JavaScript
/**
* 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