修复了一些挂载bug,将面板技术从纯html变更成了Vue3,界面更有好些,后期扩展更方便。增加了工具配置功能,可以自行选择需要使用的工具。

This commit is contained in:
root
2025-07-25 01:41:31 +08:00
parent f395b9f1bc
commit 7e238a0581
26 changed files with 4826 additions and 655 deletions

View File

@@ -1,8 +1,10 @@
import { MCPServer } from './mcp-server';
import { readSettings, saveSettings } from './settings';
import { MCPServerSettings } from './types';
import { ToolManager } from './tools/tool-manager';
let mcpServer: MCPServer | null = null;
let toolManager: ToolManager;
/**
* @en Registration method for the main process of Extension
@@ -17,12 +19,17 @@ export const methods: { [key: string]: (...any: any) => any } = {
Editor.Panel.open('cocos-mcp-server');
},
/**
* @en Start the MCP server
* @zh 启动 MCP 服务器
*/
async startServer() {
if (mcpServer) {
// 确保使用最新的工具配置
const enabledTools = toolManager.getEnabledTools();
mcpServer.updateEnabledTools(enabledTools);
await mcpServer.start();
} else {
console.warn('[MCP插件] mcpServer 未初始化');
@@ -46,7 +53,12 @@ export const methods: { [key: string]: (...any: any) => any } = {
* @zh 获取服务器状态
*/
getServerStatus() {
return mcpServer ? mcpServer.getStatus() : { running: false, port: 0, clients: 0 };
const status = mcpServer ? mcpServer.getStatus() : { running: false, port: 0, clients: 0 };
const settings = mcpServer ? mcpServer.getSettings() : readSettings();
return {
...status,
settings: settings
};
},
/**
@@ -72,12 +84,136 @@ export const methods: { [key: string]: (...any: any) => any } = {
getToolsList() {
return mcpServer ? mcpServer.getAvailableTools() : [];
},
getFilteredToolsList() {
if (!mcpServer) return [];
// 获取当前启用的工具
const enabledTools = toolManager.getEnabledTools();
// 更新MCP服务器的启用工具列表
mcpServer.updateEnabledTools(enabledTools);
return mcpServer.getFilteredTools(enabledTools);
},
/**
* @en Get server settings
* @zh 获取服务器设置
*/
getServerSettings() {
async getServerSettings() {
return mcpServer ? mcpServer.getSettings() : readSettings();
},
/**
* @en Get server settings (alternative method)
* @zh 获取服务器设置(替代方法)
*/
async getSettings() {
return mcpServer ? mcpServer.getSettings() : readSettings();
},
// 工具管理器相关方法
async getToolManagerState() {
return toolManager.getToolManagerState();
},
async createToolConfiguration(name: string, description?: string) {
try {
const config = toolManager.createConfiguration(name, description);
return { success: true, id: config.id, config };
} catch (error: any) {
throw new Error(`创建配置失败: ${error.message}`);
}
},
async updateToolConfiguration(configId: string, updates: any) {
try {
return toolManager.updateConfiguration(configId, updates);
} catch (error: any) {
throw new Error(`更新配置失败: ${error.message}`);
}
},
async deleteToolConfiguration(configId: string) {
try {
toolManager.deleteConfiguration(configId);
return { success: true };
} catch (error: any) {
throw new Error(`删除配置失败: ${error.message}`);
}
},
async setCurrentToolConfiguration(configId: string) {
try {
toolManager.setCurrentConfiguration(configId);
return { success: true };
} catch (error: any) {
throw new Error(`设置当前配置失败: ${error.message}`);
}
},
async updateToolStatus(category: string, toolName: string, enabled: boolean) {
try {
const currentConfig = toolManager.getCurrentConfiguration();
if (!currentConfig) {
throw new Error('没有当前配置');
}
toolManager.updateToolStatus(currentConfig.id, category, toolName, enabled);
// 更新MCP服务器的工具列表
if (mcpServer) {
const enabledTools = toolManager.getEnabledTools();
mcpServer.updateEnabledTools(enabledTools);
}
return { success: true };
} catch (error: any) {
throw new Error(`更新工具状态失败: ${error.message}`);
}
},
async updateToolStatusBatch(updates: any[]) {
try {
console.log(`[Main] updateToolStatusBatch called with updates count:`, updates ? updates.length : 0);
const currentConfig = toolManager.getCurrentConfiguration();
if (!currentConfig) {
throw new Error('没有当前配置');
}
toolManager.updateToolStatusBatch(currentConfig.id, updates);
// 更新MCP服务器的工具列表
if (mcpServer) {
const enabledTools = toolManager.getEnabledTools();
mcpServer.updateEnabledTools(enabledTools);
}
return { success: true };
} catch (error: any) {
throw new Error(`批量更新工具状态失败: ${error.message}`);
}
},
async exportToolConfiguration(configId: string) {
try {
return { configJson: toolManager.exportConfiguration(configId) };
} catch (error: any) {
throw new Error(`导出配置失败: ${error.message}`);
}
},
async importToolConfiguration(configJson: string) {
try {
return toolManager.importConfiguration(configJson);
} catch (error: any) {
throw new Error(`导入配置失败: ${error.message}`);
}
},
async getEnabledTools() {
return toolManager.getEnabledTools();
}
};
@@ -86,24 +222,24 @@ export const methods: { [key: string]: (...any: any) => any } = {
* @zh 扩展启动时触发的方法
*/
export function load() {
console.log('[MCP Plugin] Loading MCP server plugin...');
try {
const settings = readSettings();
console.log('[MCP Plugin] Settings loaded:', settings);
mcpServer = new MCPServer(settings);
// 如果设置了自动启动,则启动服务器
if (settings.autoStart) {
console.log('[MCP Plugin] Auto-starting MCP server...');
mcpServer.start().catch(error => {
console.error('[MCP Plugin] Failed to auto-start server:', error);
});
} else {
console.log('[MCP Plugin] MCP server created but not started (autoStart=false)');
console.log('[MCP Plugin] Use the MCP panel or call startServer() to start the server');
}
} catch (error) {
console.error('[MCP Plugin] Failed to load MCP server:', error);
console.log('Cocos MCP Server extension loaded');
// 初始化工具管理器
toolManager = new ToolManager();
// 读取设置
const settings = readSettings();
mcpServer = new MCPServer(settings);
// 初始化MCP服务器的工具列表
const enabledTools = toolManager.getEnabledTools();
mcpServer.updateEnabledTools(enabledTools);
// 如果设置了自动启动,则启动服务器
if (settings.autoStart) {
mcpServer.start().catch(err => {
console.error('Failed to auto-start MCP server:', err);
});
}
}

View File

@@ -23,6 +23,7 @@ export class MCPServer {
private clients: Map<string, MCPClient> = new Map();
private tools: Record<string, any> = {};
private toolsList: ToolDefinition[] = [];
private enabledTools: any[] = []; // 存储启用的工具列表
constructor(settings: MCPServerSettings) {
this.settings = settings;
@@ -90,17 +91,47 @@ export class MCPServer {
private setupTools(): void {
this.toolsList = [];
for (const [category, toolSet] of Object.entries(this.tools)) {
const tools = toolSet.getTools();
for (const tool of tools) {
this.toolsList.push({
name: `${category}_${tool.name}`,
description: tool.description,
inputSchema: tool.inputSchema
});
// 如果没有启用工具配置,返回所有工具
if (!this.enabledTools || this.enabledTools.length === 0) {
for (const [category, toolSet] of Object.entries(this.tools)) {
const tools = toolSet.getTools();
for (const tool of tools) {
this.toolsList.push({
name: `${category}_${tool.name}`,
description: tool.description,
inputSchema: tool.inputSchema
});
}
}
} else {
// 根据启用的工具配置过滤
const enabledToolNames = new Set(this.enabledTools.map(tool => `${tool.category}_${tool.name}`));
for (const [category, toolSet] of Object.entries(this.tools)) {
const tools = toolSet.getTools();
for (const tool of tools) {
const toolName = `${category}_${tool.name}`;
if (enabledToolNames.has(toolName)) {
this.toolsList.push({
name: toolName,
description: tool.description,
inputSchema: tool.inputSchema
});
}
}
}
}
console.log(`[MCPServer] Setup tools: ${this.toolsList.length} tools available`);
}
public getFilteredTools(enabledTools: any[]): ToolDefinition[] {
if (!enabledTools || enabledTools.length === 0) {
return this.toolsList; // 如果没有过滤配置,返回所有工具
}
const enabledToolNames = new Set(enabledTools.map(tool => `${tool.category}_${tool.name}`));
return this.toolsList.filter(tool => enabledToolNames.has(tool.name));
}
public async executeToolCall(toolName: string, args: any): Promise<any> {
@@ -122,6 +153,12 @@ export class MCPServer {
return this.toolsList;
}
public updateEnabledTools(enabledTools: any[]): void {
console.log(`[MCPServer] Updating enabled tools: ${enabledTools.length} tools`);
this.enabledTools = enabledTools;
this.setupTools(); // 重新设置工具列表
}
public getSettings(): MCPServerSettings {
return this.settings;
}

View File

@@ -1,229 +1,386 @@
import { readSettings } from '../../settings';
/* eslint-disable vue/one-component-per-file */
import { readFileSync } from 'fs-extra';
import { join } from 'path';
import { createApp, App, defineComponent, ref, computed, onMounted, watch, nextTick } from 'vue';
/**
* @zh 如果希望兼容 3.3 之前的版本可以使用下方的代码
* @en You can add the code below if you want compatibility with versions prior to 3.3
*/
// Editor.Panel.define = Editor.Panel.define || function(options: any) { return options }
const panelDataMap = new WeakMap<any, App>();
// 定义工具配置接口
interface ToolConfig {
category: string;
name: string;
enabled: boolean;
description: string;
}
// 定义配置接口
interface Configuration {
id: string;
name: string;
description: string;
tools: ToolConfig[];
createdAt: string;
updatedAt: string;
}
// 定义服务器设置接口
interface ServerSettings {
port: number;
autoStart: boolean;
debugLog: boolean;
maxConnections: number;
}
module.exports = Editor.Panel.define({
listeners: {
show() { console.log('MCP Server panel shown'); },
hide() { console.log('MCP Server panel hidden'); }
show() {
console.log('[MCP Panel] Panel shown');
},
hide() {
console.log('[MCP Panel] Panel hidden');
},
},
template: readFileSync(join(__dirname, '../../../static/template/default/index.html'), 'utf-8'),
style: readFileSync(join(__dirname, '../../../static/style/default/index.css'), 'utf-8'),
$: {
app: '#app',
panelTitle: '#panelTitle',
serverStatusLabel: '#serverStatusLabel',
serverStatusLabelProp: '#serverStatusLabelProp',
serverStatusValue: '#serverStatusValue',
connectedLabel: '#connectedLabel',
connectedClients: '#connectedClients',
toggleServerBtn: '#toggleServerBtn',
settingsLabel: '#settingsLabel',
portLabel: '#portLabel',
autoStartLabel: '#autoStartLabel',
debugLogLabel: '#debugLogLabel',
maxConnectionsLabel: '#maxConnectionsLabel',
connectionInfoLabel: '#connectionInfoLabel',
httpUrlLabel: '#httpUrlLabel',
httpUrlInput: '#httpUrlInput',
copyBtn: '#copyBtn',
saveSettingsBtn: '#saveSettingsBtn',
// 新增输入控件id
portInput: '#portInput',
maxConnInput: '#maxConnInput',
autoStartInput: '#autoStartInput',
debugLogInput: '#debugLogInput',
},
methods: {
async updateServerStatus(this: any) {
try {
const status = await Editor.Message.request('cocos-mcp-server', 'get-server-status');
this.serverRunning = status.running;
this.connectedClients = status.clients || 0;
this.serverStatus = this.serverRunning ?
Editor.I18n.t('cocos-mcp-server.connected') :
Editor.I18n.t('cocos-mcp-server.disconnected');
this.statusClass = this.serverRunning ? 'running' : 'stopped';
this.buttonText = this.serverRunning ?
Editor.I18n.t('cocos-mcp-server.stop_server') :
Editor.I18n.t('cocos-mcp-server.start_server');
// 刷新UI
this.$.serverStatusValue.innerText = this.serverStatus;
this.$.connectedClients.innerText = this.connectedClients;
this.$.toggleServerBtn.innerText = this.buttonText;
if (this.serverRunning) {
this.httpUrl = `http://localhost:${this.settings.port}/mcp`;
this.$.httpUrlInput.value = this.httpUrl;
} else {
this.httpUrl = '';
this.$.httpUrlInput.value = '';
}
} catch (err) {
console.error('Failed to update server status:', err);
}
},
async toggleServer(this: any) {
this.isProcessing = true;
try {
if (this.serverRunning) {
await this.stopServer();
} else {
await this.startServer();
}
} finally {
this.isProcessing = false;
}
},
async startServer(this: any) {
try {
await Editor.Message.request('cocos-mcp-server', 'start-server');
Editor.Dialog.info(Editor.I18n.t('cocos-mcp-server.server_started'), {
detail: Editor.I18n.t('cocos-mcp-server.server_running').replace('{0}', this.settings.port.toString())
});
await this.updateServerStatus();
} catch (err: any) {
Editor.Dialog.error(Editor.I18n.t('cocos-mcp-server.failed_to_start'), err.message);
}
},
async stopServer(this: any) {
try {
await Editor.Message.request('cocos-mcp-server', 'stop-server');
Editor.Dialog.info(Editor.I18n.t('cocos-mcp-server.server_stopped_msg'), {
detail: Editor.I18n.t('cocos-mcp-server.server_stopped')
});
await this.updateServerStatus();
} catch (err: any) {
Editor.Dialog.error(Editor.I18n.t('cocos-mcp-server.failed_to_stop'), err.message);
}
},
async saveSettings(this: any) {
try {
// 直接用 this.$ 获取所有输入控件的当前值
const port = this.$.portInput ? Number((this.$.portInput as any).value) : 3000;
const maxConnections = this.$.maxConnInput ? Number((this.$.maxConnInput as any).value) : 10;
const autoStart = this.$.autoStartInput ? !!(this.$.autoStartInput as any).checked : false;
const enableDebugLog = this.$.debugLogInput ? !!(this.$.debugLogInput as any).checked : false;
// 组装 settings
const settings = {
...this.settings,
port,
maxConnections,
autoStart,
enableDebugLog,
};
await Editor.Message.request('cocos-mcp-server', 'update-settings', settings);
// 重新拉取设置
const newSettings = await Editor.Message.request('cocos-mcp-server', 'get-server-settings');
this.settings = newSettings;
this.originalSettings = JSON.stringify(newSettings);
Editor.Dialog.info(Editor.I18n.t('cocos-mcp-server.settings_saved'));
} catch (err: any) {
Editor.Dialog.error(Editor.I18n.t('cocos-mcp-server.failed_to_save'), err.message);
}
},
copyUrl(this: any) {
Editor.Clipboard.write('text', this.httpUrl);
Editor.Dialog.info(Editor.I18n.t('cocos-mcp-server.url_copied'));
},
settingsChanged(this: any) {
return JSON.stringify(this.settings) !== this.originalSettings;
},
bindSettingsEvents(this: any) {
// 端口输入框
const portInput = document.querySelectorAll('ui-num-input[slot="content"]')[0];
if (portInput) {
portInput.addEventListener('change', (e: any) => {
this.settings.port = Number(e.detail.value);
});
}
// 最大连接数
const maxConnInput = document.querySelectorAll('ui-num-input[slot="content"]')[1];
if (maxConnInput) {
maxConnInput.addEventListener('change', (e: any) => {
this.settings.maxConnections = Number(e.detail.value);
});
}
// 复选框
const checkboxes = document.querySelectorAll('ui-checkbox[slot="content"]');
if (checkboxes && checkboxes.length >= 2) {
checkboxes[0].addEventListener('change', (e: any) => {
this.settings.autoStart = !!e.detail.value;
});
checkboxes[1].addEventListener('change', (e: any) => {
this.settings.enableDebugLog = !!e.detail.value;
});
}
},
},
ready() {
Editor.Message.request('cocos-mcp-server', 'get-server-settings').then((settings) => {
this.settings = settings;
this.originalSettings = JSON.stringify(settings);
// 本地化label赋值
this.$.panelTitle.innerText = Editor.I18n.t('cocos-mcp-server.panel_title');
this.$.serverStatusLabel.innerText = Editor.I18n.t('cocos-mcp-server.server_status');
this.$.serverStatusLabelProp.innerText = Editor.I18n.t('cocos-mcp-server.server_status');
this.$.connectedLabel.innerText = Editor.I18n.t('cocos-mcp-server.connected');
this.$.settingsLabel.innerText = Editor.I18n.t('cocos-mcp-server.settings');
this.$.portLabel.innerText = Editor.I18n.t('cocos-mcp-server.port');
this.$.autoStartLabel.innerText = Editor.I18n.t('cocos-mcp-server.auto_start');
this.$.debugLogLabel.innerText = Editor.I18n.t('cocos-mcp-server.debug_log');
this.$.maxConnectionsLabel.innerText = Editor.I18n.t('cocos-mcp-server.max_connections');
this.$.connectionInfoLabel.innerText = Editor.I18n.t('cocos-mcp-server.connection_info');
this.$.httpUrlLabel.innerText = Editor.I18n.t('cocos-mcp-server.http_url');
this.$.copyBtn.innerText = Editor.I18n.t('cocos-mcp-server.copy');
this.$.saveSettingsBtn.innerText = Editor.I18n.t('cocos-mcp-server.save_settings');
// 动态内容初始化
this.$.serverStatusValue.innerText = '';
this.$.connectedClients.innerText = '';
this.$.toggleServerBtn.innerText = '';
this.$.httpUrlInput.value = '';
// 绑定按钮事件
this.$.toggleServerBtn.addEventListener('confirm', this.toggleServer.bind(this));
this.$.saveSettingsBtn.addEventListener('confirm', this.saveSettings.bind(this));
this.$.copyBtn.addEventListener('confirm', this.copyUrl.bind(this));
// 延迟绑定事件,确保 UI 组件已渲染
setTimeout(() => {
this.bindSettingsEvents();
}, 100);
// Set up periodic status updates
(this as any).statusInterval = setInterval(() => {
(this as any).updateServerStatus();
}, 2000);
// 不再自动启动服务器,用户点击才启动
(this as any).updateServerStatus();
});
},
beforeClose() {
if ((this as any).statusInterval) {
clearInterval((this as any).statusInterval);
if (this.$.app) {
const app = createApp({});
app.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('ui-');
// 创建主应用组件
app.component('McpServerApp', defineComponent({
setup() {
// 响应式数据
const activeTab = ref('server');
const serverRunning = ref(false);
const serverStatus = ref('已停止');
const connectedClients = ref(0);
const httpUrl = ref('');
const isProcessing = ref(false);
const settings = ref<ServerSettings>({
port: 3000,
autoStart: false,
debugLog: false,
maxConnections: 10
});
const availableTools = ref<ToolConfig[]>([]);
const toolCategories = ref<string[]>([]);
// 计算属性
const statusClass = computed(() => ({
'status-running': serverRunning.value,
'status-stopped': !serverRunning.value
}));
const totalTools = computed(() => availableTools.value.length);
const enabledTools = computed(() => availableTools.value.filter(t => t.enabled).length);
const disabledTools = computed(() => totalTools.value - enabledTools.value);
const settingsChanged = ref(false);
// 方法
const switchTab = (tabName: string) => {
activeTab.value = tabName;
if (tabName === 'tools') {
loadToolManagerState();
}
};
const toggleServer = async () => {
try {
if (serverRunning.value) {
await Editor.Message.request('cocos-mcp-server', 'stop-server');
} else {
// 启动服务器时使用当前面板设置
const currentSettings = {
port: settings.value.port,
autoStart: settings.value.autoStart,
enableDebugLog: settings.value.debugLog,
maxConnections: settings.value.maxConnections
};
await Editor.Message.request('cocos-mcp-server', 'update-settings', currentSettings);
await Editor.Message.request('cocos-mcp-server', 'start-server');
}
console.log('[Vue App] Server toggled');
} catch (error) {
console.error('[Vue App] Failed to toggle server:', error);
}
};
const saveSettings = async () => {
try {
// 创建一个简单的对象,避免克隆错误
const settingsData = {
port: settings.value.port,
autoStart: settings.value.autoStart,
debugLog: settings.value.debugLog,
maxConnections: settings.value.maxConnections
};
const result = await Editor.Message.request('cocos-mcp-server', 'update-settings', settingsData);
console.log('[Vue App] Save settings result:', result);
settingsChanged.value = false;
} catch (error) {
console.error('[Vue App] Failed to save settings:', error);
}
};
const copyUrl = async () => {
try {
await navigator.clipboard.writeText(httpUrl.value);
console.log('[Vue App] URL copied to clipboard');
} catch (error) {
console.error('[Vue App] Failed to copy URL:', error);
}
};
const loadToolManagerState = async () => {
try {
const result = await Editor.Message.request('cocos-mcp-server', 'getToolManagerState');
if (result && result.success) {
// 总是加载后端状态,确保数据是最新的
availableTools.value = result.availableTools || [];
console.log('[Vue App] Loaded tools:', availableTools.value.length);
// 更新工具分类
const categories = new Set(availableTools.value.map(tool => tool.category));
toolCategories.value = Array.from(categories);
}
} catch (error) {
console.error('[Vue App] Failed to load tool manager state:', error);
}
};
const updateToolStatus = async (category: string, name: string, enabled: boolean) => {
try {
console.log('[Vue App] updateToolStatus called:', category, name, enabled);
// 先更新本地状态
const toolIndex = availableTools.value.findIndex(t => t.category === category && t.name === name);
if (toolIndex !== -1) {
availableTools.value[toolIndex].enabled = enabled;
// 强制触发响应式更新
availableTools.value = [...availableTools.value];
console.log('[Vue App] Local state updated, tool enabled:', availableTools.value[toolIndex].enabled);
}
// 调用后端更新
const result = await Editor.Message.request('cocos-mcp-server', 'updateToolStatus', category, name, enabled);
if (!result || !result.success) {
// 如果后端更新失败,回滚本地状态
if (toolIndex !== -1) {
availableTools.value[toolIndex].enabled = !enabled;
availableTools.value = [...availableTools.value];
}
console.error('[Vue App] Backend update failed, rolled back local state');
} else {
console.log('[Vue App] Backend update successful');
}
} catch (error) {
// 如果发生错误,回滚本地状态
const toolIndex = availableTools.value.findIndex(t => t.category === category && t.name === name);
if (toolIndex !== -1) {
availableTools.value[toolIndex].enabled = !enabled;
availableTools.value = [...availableTools.value];
}
console.error('[Vue App] Failed to update tool status:', error);
}
};
const selectAllTools = async () => {
try {
// 直接更新本地状态,然后保存
availableTools.value.forEach(tool => tool.enabled = true);
await saveChanges();
} catch (error) {
console.error('[Vue App] Failed to select all tools:', error);
}
};
const deselectAllTools = async () => {
try {
// 直接更新本地状态,然后保存
availableTools.value.forEach(tool => tool.enabled = false);
await saveChanges();
} catch (error) {
console.error('[Vue App] Failed to deselect all tools:', error);
}
};
const saveChanges = async () => {
try {
// 创建普通对象避免Vue3响应式对象克隆错误
const updates = availableTools.value.map(tool => ({
category: String(tool.category),
name: String(tool.name),
enabled: Boolean(tool.enabled)
}));
console.log('[Vue App] Sending updates:', updates.length, 'tools');
const result = await Editor.Message.request('cocos-mcp-server', 'updateToolStatusBatch', updates);
if (result && result.success) {
console.log('[Vue App] Tool changes saved successfully');
}
} catch (error) {
console.error('[Vue App] Failed to save tool changes:', error);
}
};
const toggleCategoryTools = async (category: string, enabled: boolean) => {
try {
// 直接更新本地状态,然后保存
availableTools.value.forEach(tool => {
if (tool.category === category) {
tool.enabled = enabled;
}
});
await saveChanges();
} catch (error) {
console.error('[Vue App] Failed to toggle category tools:', error);
}
};
const getToolsByCategory = (category: string) => {
return availableTools.value.filter(tool => tool.category === category);
};
const getCategoryDisplayName = (category: string): string => {
const categoryNames: { [key: string]: string } = {
'scene': '场景工具',
'node': '节点工具',
'component': '组件工具',
'prefab': '预制体工具',
'project': '项目工具',
'debug': '调试工具',
'preferences': '偏好设置工具',
'server': '服务器工具',
'broadcast': '广播工具',
'sceneAdvanced': '高级场景工具',
'sceneView': '场景视图工具',
'referenceImage': '参考图片工具',
'assetAdvanced': '高级资源工具',
'validation': '验证工具'
};
return categoryNames[category] || category;
};
// 监听设置变化
watch(settings, () => {
settingsChanged.value = true;
}, { deep: true });
// 组件挂载时加载数据
onMounted(async () => {
// 加载工具管理器状态
await loadToolManagerState();
// 从服务器状态获取设置信息
try {
const serverStatus = await Editor.Message.request('cocos-mcp-server', 'get-server-status');
if (serverStatus && serverStatus.settings) {
settings.value = {
port: serverStatus.settings.port || 3000,
autoStart: serverStatus.settings.autoStart || false,
debugLog: serverStatus.settings.enableDebugLog || false,
maxConnections: serverStatus.settings.maxConnections || 10
};
console.log('[Vue App] Server settings loaded from status:', serverStatus.settings);
} else if (serverStatus && serverStatus.port) {
// 兼容旧版本,只获取端口信息
settings.value.port = serverStatus.port;
console.log('[Vue App] Port loaded from server status:', serverStatus.port);
}
} catch (error) {
console.error('[Vue App] Failed to get server status:', error);
console.log('[Vue App] Using default server settings');
}
// 定期更新服务器状态
setInterval(async () => {
try {
const result = await Editor.Message.request('cocos-mcp-server', 'get-server-status');
if (result) {
serverRunning.value = result.running;
serverStatus.value = result.running ? '运行中' : '已停止';
connectedClients.value = result.clients || 0;
httpUrl.value = result.running ? `http://localhost:${result.port}` : '';
isProcessing.value = false;
}
} catch (error) {
console.error('[Vue App] Failed to get server status:', error);
}
}, 2000);
});
return {
// 数据
activeTab,
serverRunning,
serverStatus,
connectedClients,
httpUrl,
isProcessing,
settings,
availableTools,
toolCategories,
settingsChanged,
// 计算属性
statusClass,
totalTools,
enabledTools,
disabledTools,
// 方法
switchTab,
toggleServer,
saveSettings,
copyUrl,
loadToolManagerState,
updateToolStatus,
selectAllTools,
deselectAllTools,
saveChanges,
toggleCategoryTools,
getToolsByCategory,
getCategoryDisplayName
};
},
template: readFileSync(join(__dirname, '../../../static/template/vue/mcp-server-app.html'), 'utf-8'),
}));
app.mount(this.$.app);
panelDataMap.set(this, app);
console.log('[MCP Panel] Vue3 app mounted successfully');
}
},
beforeClose() { },
close() {
// Panel close cleanup
const app = panelDataMap.get(this);
if (app) {
app.unmount();
}
},
// Direct properties for data access
serverRunning: false,
connectedClients: 0,
serverStatus: '',
statusClass: 'stopped',
buttonText: '',
isProcessing: false,
settings: {},
httpUrl: '',
statusInterval: null as any,
originalSettings: ''
} as any);
});

View File

@@ -0,0 +1,585 @@
import { readFileSync } from 'fs-extra';
import { join } from 'path';
module.exports = Editor.Panel.define({
listeners: {
show() { console.log('Tool Manager panel shown'); },
hide() { console.log('Tool Manager panel hidden'); }
},
template: readFileSync(join(__dirname, '../../../static/template/default/tool-manager.html'), 'utf-8'),
style: readFileSync(join(__dirname, '../../../static/style/default/index.css'), 'utf-8'),
$: {
panelTitle: '#panelTitle',
createConfigBtn: '#createConfigBtn',
importConfigBtn: '#importConfigBtn',
exportConfigBtn: '#exportConfigBtn',
configSelector: '#configSelector',
applyConfigBtn: '#applyConfigBtn',
editConfigBtn: '#editConfigBtn',
deleteConfigBtn: '#deleteConfigBtn',
toolsContainer: '#toolsContainer',
selectAllBtn: '#selectAllBtn',
deselectAllBtn: '#deselectAllBtn',
saveChangesBtn: '#saveChangesBtn',
totalToolsCount: '#totalToolsCount',
enabledToolsCount: '#enabledToolsCount',
disabledToolsCount: '#disabledToolsCount',
configModal: '#configModal',
modalTitle: '#modalTitle',
configForm: '#configForm',
configName: '#configName',
configDescription: '#configDescription',
closeModal: '#closeModal',
cancelConfigBtn: '#cancelConfigBtn',
saveConfigBtn: '#saveConfigBtn',
importModal: '#importModal',
importConfigJson: '#importConfigJson',
closeImportModal: '#closeImportModal',
cancelImportBtn: '#cancelImportBtn',
confirmImportBtn: '#confirmImportBtn'
},
methods: {
async loadToolManagerState(this: any) {
try {
this.toolManagerState = await Editor.Message.request('cocos-mcp-server', 'getToolManagerState');
this.currentConfiguration = this.toolManagerState.currentConfiguration;
this.configurations = this.toolManagerState.configurations;
this.availableTools = this.toolManagerState.availableTools;
this.updateUI();
} catch (error) {
console.error('Failed to load tool manager state:', error);
this.showError('加载工具管理器状态失败');
}
},
updateUI(this: any) {
this.updateConfigSelector();
this.updateToolsDisplay();
this.updateStatusBar();
this.updateButtons();
},
updateConfigSelector(this: any) {
const selector = this.$.configSelector;
selector.innerHTML = '<option value="">选择配置...</option>';
this.configurations.forEach((config: any) => {
const option = document.createElement('option');
option.value = config.id;
option.textContent = config.name;
if (this.currentConfiguration && config.id === this.currentConfiguration.id) {
option.selected = true;
}
selector.appendChild(option);
});
},
updateToolsDisplay(this: any) {
const container = this.$.toolsContainer;
if (!this.currentConfiguration) {
container.innerHTML = `
<div class="empty-state">
<h3>没有选择配置</h3>
<p>请先选择一个配置或创建新配置</p>
</div>
`;
return;
}
const toolsByCategory: any = {};
this.currentConfiguration.tools.forEach((tool: any) => {
if (!toolsByCategory[tool.category]) {
toolsByCategory[tool.category] = [];
}
toolsByCategory[tool.category].push(tool);
});
container.innerHTML = '';
Object.entries(toolsByCategory).forEach(([category, tools]: [string, any]) => {
const categoryDiv = document.createElement('div');
categoryDiv.className = 'tool-category';
const enabledCount = tools.filter((t: any) => t.enabled).length;
const totalCount = tools.length;
categoryDiv.innerHTML = `
<div class="category-header">
<div class="category-name">${this.getCategoryDisplayName(category)}</div>
<div class="category-toggle">
<span>${enabledCount}/${totalCount}</span>
<input type="checkbox" class="checkbox category-checkbox"
data-category="${category}"
${enabledCount === totalCount ? 'checked' : ''}>
</div>
</div>
<div class="tool-list">
${tools.map((tool: any) => `
<div class="tool-item">
<div class="tool-info">
<div class="tool-name">${tool.name}</div>
<div class="tool-description">${tool.description}</div>
</div>
<div class="tool-toggle">
<input type="checkbox" class="checkbox tool-checkbox"
data-category="${tool.category}"
data-name="${tool.name}"
${tool.enabled ? 'checked' : ''}>
</div>
</div>
`).join('')}
</div>
`;
container.appendChild(categoryDiv);
});
this.bindToolEvents();
},
bindToolEvents(this: any) {
document.querySelectorAll('.category-checkbox').forEach((checkbox: any) => {
checkbox.addEventListener('change', (e: any) => {
const category = e.target.dataset.category;
const checked = e.target.checked;
this.toggleCategoryTools(category, checked);
});
});
document.querySelectorAll('.tool-checkbox').forEach((checkbox: any) => {
checkbox.addEventListener('change', (e: any) => {
const category = e.target.dataset.category;
const name = e.target.dataset.name;
const enabled = e.target.checked;
this.updateToolStatus(category, name, enabled);
});
});
},
async toggleCategoryTools(this: any, category: string, enabled: boolean) {
if (!this.currentConfiguration) return;
console.log(`Toggling category tools: ${category} = ${enabled}`);
const categoryTools = this.currentConfiguration.tools.filter((tool: any) => tool.category === category);
if (categoryTools.length === 0) return;
const updates = categoryTools.map((tool: any) => ({
category: tool.category,
name: tool.name,
enabled: enabled
}));
try {
// 先更新本地状态
categoryTools.forEach((tool: any) => {
tool.enabled = enabled;
});
console.log(`Updated local category state: ${category} = ${enabled}`);
// 立即更新UI
this.updateStatusBar();
this.updateCategoryCounts();
this.updateToolCheckboxes(category, enabled);
// 然后发送到后端
await Editor.Message.request('cocos-mcp-server', 'updateToolStatusBatch',
this.currentConfiguration.id, updates);
} catch (error) {
console.error('Failed to toggle category tools:', error);
this.showError('切换类别工具失败');
// 如果后端更新失败,回滚本地状态
categoryTools.forEach((tool: any) => {
tool.enabled = !enabled;
});
this.updateStatusBar();
this.updateCategoryCounts();
this.updateToolCheckboxes(category, !enabled);
}
},
async updateToolStatus(this: any, category: string, name: string, enabled: boolean) {
if (!this.currentConfiguration) return;
console.log(`Updating tool status: ${category}.${name} = ${enabled}`);
console.log(`Current config ID: ${this.currentConfiguration.id}`);
// 先更新本地状态
const tool = this.currentConfiguration.tools.find((t: any) =>
t.category === category && t.name === name);
if (!tool) {
console.error(`Tool not found: ${category}.${name}`);
return;
}
try {
tool.enabled = enabled;
console.log(`Updated local tool state: ${tool.name} = ${tool.enabled}`);
// 立即更新UI只更新统计信息不重新渲染工具列表
this.updateStatusBar();
this.updateCategoryCounts();
// 然后发送到后端
console.log(`Sending to backend: configId=${this.currentConfiguration.id}, category=${category}, name=${name}, enabled=${enabled}`);
const result = await Editor.Message.request('cocos-mcp-server', 'updateToolStatus',
this.currentConfiguration.id, category, name, enabled);
console.log('Backend response:', result);
} catch (error) {
console.error('Failed to update tool status:', error);
this.showError('更新工具状态失败');
// 如果后端更新失败,回滚本地状态
tool.enabled = !enabled;
this.updateStatusBar();
this.updateCategoryCounts();
}
},
updateStatusBar(this: any) {
if (!this.currentConfiguration) {
this.$.totalToolsCount.textContent = '0';
this.$.enabledToolsCount.textContent = '0';
this.$.disabledToolsCount.textContent = '0';
return;
}
const total = this.currentConfiguration.tools.length;
const enabled = this.currentConfiguration.tools.filter((t: any) => t.enabled).length;
const disabled = total - enabled;
console.log(`Status bar update: total=${total}, enabled=${enabled}, disabled=${disabled}`);
this.$.totalToolsCount.textContent = total.toString();
this.$.enabledToolsCount.textContent = enabled.toString();
this.$.disabledToolsCount.textContent = disabled.toString();
},
updateCategoryCounts(this: any) {
if (!this.currentConfiguration) return;
// 更新每个类别的计数显示
document.querySelectorAll('.category-checkbox').forEach((checkbox: any) => {
const category = checkbox.dataset.category;
const categoryTools = this.currentConfiguration.tools.filter((t: any) => t.category === category);
const enabledCount = categoryTools.filter((t: any) => t.enabled).length;
const totalCount = categoryTools.length;
// 更新计数显示
const countSpan = checkbox.parentElement.querySelector('span');
if (countSpan) {
countSpan.textContent = `${enabledCount}/${totalCount}`;
}
// 更新类别复选框状态
checkbox.checked = enabledCount === totalCount;
});
},
updateToolCheckboxes(this: any, category: string, enabled: boolean) {
// 更新特定类别的所有工具复选框
document.querySelectorAll(`.tool-checkbox[data-category="${category}"]`).forEach((checkbox: any) => {
checkbox.checked = enabled;
});
},
updateButtons(this: any) {
const hasCurrentConfig = !!this.currentConfiguration;
this.$.editConfigBtn.disabled = !hasCurrentConfig;
this.$.deleteConfigBtn.disabled = !hasCurrentConfig;
this.$.exportConfigBtn.disabled = !hasCurrentConfig;
this.$.applyConfigBtn.disabled = !hasCurrentConfig;
},
async createConfiguration(this: any) {
this.editingConfig = null;
this.$.modalTitle.textContent = '新建配置';
this.$.configName.value = '';
this.$.configDescription.value = '';
this.showModal('configModal');
},
async editConfiguration(this: any) {
if (!this.currentConfiguration) return;
this.editingConfig = this.currentConfiguration;
this.$.modalTitle.textContent = '编辑配置';
this.$.configName.value = this.currentConfiguration.name;
this.$.configDescription.value = this.currentConfiguration.description || '';
this.showModal('configModal');
},
async saveConfiguration(this: any) {
const name = this.$.configName.value.trim();
const description = this.$.configDescription.value.trim();
if (!name) {
this.showError('配置名称不能为空');
return;
}
try {
if (this.editingConfig) {
await Editor.Message.request('cocos-mcp-server', 'updateToolConfiguration',
this.editingConfig.id, { name, description });
} else {
await Editor.Message.request('cocos-mcp-server', 'createToolConfiguration', name, description);
}
this.hideModal('configModal');
await this.loadToolManagerState();
} catch (error) {
console.error('Failed to save configuration:', error);
this.showError('保存配置失败');
}
},
async deleteConfiguration(this: any) {
if (!this.currentConfiguration) return;
const confirmed = await Editor.Dialog.warn('确认删除', {
detail: `确定要删除配置 "${this.currentConfiguration.name}" 吗?此操作不可撤销。`
});
if (confirmed) {
try {
await Editor.Message.request('cocos-mcp-server', 'deleteToolConfiguration',
this.currentConfiguration.id);
await this.loadToolManagerState();
} catch (error) {
console.error('Failed to delete configuration:', error);
this.showError('删除配置失败');
}
}
},
async applyConfiguration(this: any) {
const configId = this.$.configSelector.value;
if (!configId) return;
try {
await Editor.Message.request('cocos-mcp-server', 'setCurrentToolConfiguration', configId);
await this.loadToolManagerState();
} catch (error) {
console.error('Failed to apply configuration:', error);
this.showError('应用配置失败');
}
},
async exportConfiguration(this: any) {
if (!this.currentConfiguration) return;
try {
const result = await Editor.Message.request('cocos-mcp-server', 'exportToolConfiguration',
this.currentConfiguration.id);
Editor.Clipboard.write('text', result.configJson);
Editor.Dialog.info('导出成功', { detail: '配置已复制到剪贴板' });
} catch (error) {
console.error('Failed to export configuration:', error);
this.showError('导出配置失败');
}
},
async importConfiguration(this: any) {
this.$.importConfigJson.value = '';
this.showModal('importModal');
},
async confirmImport(this: any) {
const configJson = this.$.importConfigJson.value.trim();
if (!configJson) {
this.showError('请输入配置JSON');
return;
}
try {
await Editor.Message.request('cocos-mcp-server', 'importToolConfiguration', configJson);
this.hideModal('importModal');
await this.loadToolManagerState();
Editor.Dialog.info('导入成功', { detail: '配置已成功导入' });
} catch (error) {
console.error('Failed to import configuration:', error);
this.showError('导入配置失败');
}
},
async selectAllTools(this: any) {
if (!this.currentConfiguration) return;
console.log('Selecting all tools');
const updates = this.currentConfiguration.tools.map((tool: any) => ({
category: tool.category,
name: tool.name,
enabled: true
}));
try {
// 先更新本地状态
this.currentConfiguration.tools.forEach((tool: any) => {
tool.enabled = true;
});
console.log('Updated local state: all tools enabled');
// 立即更新UI
this.updateStatusBar();
this.updateToolsDisplay();
// 然后发送到后端
await Editor.Message.request('cocos-mcp-server', 'updateToolStatusBatch',
this.currentConfiguration.id, updates);
} catch (error) {
console.error('Failed to select all tools:', error);
this.showError('全选工具失败');
// 如果后端更新失败,回滚本地状态
this.currentConfiguration.tools.forEach((tool: any) => {
tool.enabled = false;
});
this.updateStatusBar();
this.updateToolsDisplay();
}
},
async deselectAllTools(this: any) {
if (!this.currentConfiguration) return;
console.log('Deselecting all tools');
const updates = this.currentConfiguration.tools.map((tool: any) => ({
category: tool.category,
name: tool.name,
enabled: false
}));
try {
// 先更新本地状态
this.currentConfiguration.tools.forEach((tool: any) => {
tool.enabled = false;
});
console.log('Updated local state: all tools disabled');
// 立即更新UI
this.updateStatusBar();
this.updateToolsDisplay();
// 然后发送到后端
await Editor.Message.request('cocos-mcp-server', 'updateToolStatusBatch',
this.currentConfiguration.id, updates);
} catch (error) {
console.error('Failed to deselect all tools:', error);
this.showError('取消全选工具失败');
// 如果后端更新失败,回滚本地状态
this.currentConfiguration.tools.forEach((tool: any) => {
tool.enabled = true;
});
this.updateStatusBar();
this.updateToolsDisplay();
}
},
getCategoryDisplayName(this: any, category: string): string {
const categoryNames: any = {
'scene': '场景工具',
'node': '节点工具',
'component': '组件工具',
'prefab': '预制体工具',
'project': '项目工具',
'debug': '调试工具',
'preferences': '偏好设置工具',
'server': '服务器工具',
'broadcast': '广播工具',
'sceneAdvanced': '高级场景工具',
'sceneView': '场景视图工具',
'referenceImage': '参考图片工具',
'assetAdvanced': '高级资源工具',
'validation': '验证工具'
};
return categoryNames[category] || category;
},
showModal(this: any, modalId: string) {
this.$[modalId].style.display = 'block';
},
hideModal(this: any, modalId: string) {
this.$[modalId].style.display = 'none';
},
showError(this: any, message: string) {
Editor.Dialog.error('错误', { detail: message });
},
async saveChanges(this: any) {
if (!this.currentConfiguration) {
this.showError('没有选择配置');
return;
}
try {
// 确保当前配置已保存到后端
await Editor.Message.request('cocos-mcp-server', 'updateToolConfiguration',
this.currentConfiguration.id, {
name: this.currentConfiguration.name,
description: this.currentConfiguration.description,
tools: this.currentConfiguration.tools
});
Editor.Dialog.info('保存成功', { detail: '配置更改已保存' });
} catch (error) {
console.error('Failed to save changes:', error);
this.showError('保存更改失败');
}
},
bindEvents(this: any) {
this.$.createConfigBtn.addEventListener('click', this.createConfiguration.bind(this));
this.$.editConfigBtn.addEventListener('click', this.editConfiguration.bind(this));
this.$.deleteConfigBtn.addEventListener('click', this.deleteConfiguration.bind(this));
this.$.applyConfigBtn.addEventListener('click', this.applyConfiguration.bind(this));
this.$.exportConfigBtn.addEventListener('click', this.exportConfiguration.bind(this));
this.$.importConfigBtn.addEventListener('click', this.importConfiguration.bind(this));
this.$.selectAllBtn.addEventListener('click', this.selectAllTools.bind(this));
this.$.deselectAllBtn.addEventListener('click', this.deselectAllTools.bind(this));
this.$.saveChangesBtn.addEventListener('click', this.saveChanges.bind(this));
this.$.closeModal.addEventListener('click', () => this.hideModal('configModal'));
this.$.cancelConfigBtn.addEventListener('click', () => this.hideModal('configModal'));
this.$.configForm.addEventListener('submit', (e: any) => {
e.preventDefault();
this.saveConfiguration();
});
this.$.closeImportModal.addEventListener('click', () => this.hideModal('importModal'));
this.$.cancelImportBtn.addEventListener('click', () => this.hideModal('importModal'));
this.$.confirmImportBtn.addEventListener('click', this.confirmImport.bind(this));
this.$.configSelector.addEventListener('change', this.applyConfiguration.bind(this));
}
},
ready() {
(this as any).toolManagerState = null;
(this as any).currentConfiguration = null;
(this as any).configurations = [];
(this as any).availableTools = [];
(this as any).editingConfig = null;
(this as any).bindEvents();
(this as any).loadToolManagerState();
},
beforeClose() {
// 清理工作
},
close() {
// 面板关闭清理
}
} as any);

View File

@@ -1,6 +1,6 @@
import * as fs from 'fs';
import * as path from 'path';
import { MCPServerSettings } from './types';
import { MCPServerSettings, ToolManagerSettings, ToolConfiguration, ToolConfig } from './types';
const DEFAULT_SETTINGS: MCPServerSettings = {
port: 3000,
@@ -10,10 +10,20 @@ const DEFAULT_SETTINGS: MCPServerSettings = {
maxConnections: 10
};
const DEFAULT_TOOL_MANAGER_SETTINGS: ToolManagerSettings = {
configurations: [],
currentConfigId: '',
maxConfigSlots: 5
};
function getSettingsPath(): string {
return path.join(Editor.Project.path, 'settings', 'mcp-server.json');
}
function getToolManagerSettingsPath(): string {
return path.join(Editor.Project.path, 'settings', 'tool-manager.json');
}
function ensureSettingsDir(): void {
const settingsDir = path.dirname(getSettingsPath());
if (!fs.existsSync(settingsDir)) {
@@ -46,4 +56,48 @@ export function saveSettings(settings: MCPServerSettings): void {
}
}
export { DEFAULT_SETTINGS };
// 工具管理器设置相关函数
export function readToolManagerSettings(): ToolManagerSettings {
try {
ensureSettingsDir();
const settingsFile = getToolManagerSettingsPath();
if (fs.existsSync(settingsFile)) {
const content = fs.readFileSync(settingsFile, 'utf8');
return { ...DEFAULT_TOOL_MANAGER_SETTINGS, ...JSON.parse(content) };
}
} catch (e) {
console.error('Failed to read tool manager settings:', e);
}
return DEFAULT_TOOL_MANAGER_SETTINGS;
}
export function saveToolManagerSettings(settings: ToolManagerSettings): void {
try {
ensureSettingsDir();
const settingsFile = getToolManagerSettingsPath();
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
} catch (e) {
console.error('Failed to save tool manager settings:', e);
throw e;
}
}
export function exportToolConfiguration(config: ToolConfiguration): string {
return JSON.stringify(config, null, 2);
}
export function importToolConfiguration(configJson: string): ToolConfiguration {
try {
const config = JSON.parse(configJson);
// 验证配置格式
if (!config.id || !config.name || !Array.isArray(config.tools)) {
throw new Error('Invalid configuration format');
}
return config;
} catch (e) {
console.error('Failed to parse tool configuration:', e);
throw new Error('Invalid JSON format or configuration structure');
}
}
export { DEFAULT_SETTINGS, DEFAULT_TOOL_MANAGER_SETTINGS };

View File

@@ -0,0 +1,425 @@
import { v4 as uuidv4 } from 'uuid';
import { ToolConfig, ToolConfiguration, ToolManagerSettings, ToolDefinition } from '../types';
import * as fs from 'fs';
import * as path from 'path';
export class ToolManager {
private settings: ToolManagerSettings;
private availableTools: ToolConfig[] = [];
constructor() {
this.settings = this.readToolManagerSettings();
this.initializeAvailableTools();
// 如果没有配置,自动创建一个默认配置
if (this.settings.configurations.length === 0) {
console.log('[ToolManager] No configurations found, creating default configuration...');
this.createConfiguration('默认配置', '自动创建的默认工具配置');
}
}
private getToolManagerSettingsPath(): string {
return path.join(Editor.Project.path, 'settings', 'tool-manager.json');
}
private ensureSettingsDir(): void {
const settingsDir = path.dirname(this.getToolManagerSettingsPath());
if (!fs.existsSync(settingsDir)) {
fs.mkdirSync(settingsDir, { recursive: true });
}
}
private readToolManagerSettings(): ToolManagerSettings {
const DEFAULT_TOOL_MANAGER_SETTINGS: ToolManagerSettings = {
configurations: [],
currentConfigId: '',
maxConfigSlots: 5
};
try {
this.ensureSettingsDir();
const settingsFile = this.getToolManagerSettingsPath();
if (fs.existsSync(settingsFile)) {
const content = fs.readFileSync(settingsFile, 'utf8');
return { ...DEFAULT_TOOL_MANAGER_SETTINGS, ...JSON.parse(content) };
}
} catch (e) {
console.error('Failed to read tool manager settings:', e);
}
return DEFAULT_TOOL_MANAGER_SETTINGS;
}
private saveToolManagerSettings(settings: ToolManagerSettings): void {
try {
this.ensureSettingsDir();
const settingsFile = this.getToolManagerSettingsPath();
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
} catch (e) {
console.error('Failed to save tool manager settings:', e);
throw e;
}
}
private exportToolConfiguration(config: ToolConfiguration): string {
return JSON.stringify(config, null, 2);
}
private importToolConfiguration(configJson: string): ToolConfiguration {
try {
const config = JSON.parse(configJson);
// 验证配置格式
if (!config.id || !config.name || !Array.isArray(config.tools)) {
throw new Error('Invalid configuration format');
}
return config;
} catch (e) {
console.error('Failed to parse tool configuration:', e);
throw new Error('Invalid JSON format or configuration structure');
}
}
private initializeAvailableTools(): void {
// 从MCP服务器获取真实的工具列表
try {
// 导入所有工具类
const { SceneTools } = require('./scene-tools');
const { NodeTools } = require('./node-tools');
const { ComponentTools } = require('./component-tools');
const { PrefabTools } = require('./prefab-tools');
const { ProjectTools } = require('./project-tools');
const { DebugTools } = require('./debug-tools');
const { PreferencesTools } = require('./preferences-tools');
const { ServerTools } = require('./server-tools');
const { BroadcastTools } = require('./broadcast-tools');
const { SceneAdvancedTools } = require('./scene-advanced-tools');
const { SceneViewTools } = require('./scene-view-tools');
const { ReferenceImageTools } = require('./reference-image-tools');
const { AssetAdvancedTools } = require('./asset-advanced-tools');
const { ValidationTools } = require('./validation-tools');
// 初始化工具实例
const tools = {
scene: new SceneTools(),
node: new NodeTools(),
component: new ComponentTools(),
prefab: new PrefabTools(),
project: new ProjectTools(),
debug: new DebugTools(),
preferences: new PreferencesTools(),
server: new ServerTools(),
broadcast: new BroadcastTools(),
sceneAdvanced: new SceneAdvancedTools(),
sceneView: new SceneViewTools(),
referenceImage: new ReferenceImageTools(),
assetAdvanced: new AssetAdvancedTools(),
validation: new ValidationTools()
};
// 从每个工具类获取工具列表
this.availableTools = [];
for (const [category, toolSet] of Object.entries(tools)) {
const toolDefinitions = toolSet.getTools();
toolDefinitions.forEach((tool: any) => {
this.availableTools.push({
category: category,
name: tool.name,
enabled: true, // 默认启用
description: tool.description
});
});
}
console.log(`[ToolManager] Initialized ${this.availableTools.length} tools from MCP server`);
} catch (error) {
console.error('[ToolManager] Failed to initialize tools from MCP server:', error);
// 如果获取失败,使用默认工具列表作为后备
this.initializeDefaultTools();
}
}
private initializeDefaultTools(): void {
// 默认工具列表作为后备方案
const toolCategories = [
{ category: 'scene', name: '场景工具', tools: [
{ name: 'getCurrentSceneInfo', description: '获取当前场景信息' },
{ name: 'getSceneHierarchy', description: '获取场景层级结构' },
{ name: 'createNewScene', description: '创建新场景' },
{ name: 'saveScene', description: '保存场景' },
{ name: 'loadScene', description: '加载场景' }
]},
{ category: 'node', name: '节点工具', tools: [
{ name: 'getAllNodes', description: '获取所有节点' },
{ name: 'findNodeByName', description: '根据名称查找节点' },
{ name: 'createNode', description: '创建节点' },
{ name: 'deleteNode', description: '删除节点' },
{ name: 'setNodeProperty', description: '设置节点属性' },
{ name: 'getNodeInfo', description: '获取节点信息' }
]},
{ category: 'component', name: '组件工具', tools: [
{ name: 'addComponentToNode', description: '添加组件到节点' },
{ name: 'removeComponentFromNode', description: '从节点移除组件' },
{ name: 'setComponentProperty', description: '设置组件属性' },
{ name: 'getComponentInfo', description: '获取组件信息' }
]},
{ category: 'prefab', name: '预制体工具', tools: [
{ name: 'createPrefabFromNode', description: '从节点创建预制体' },
{ name: 'instantiatePrefab', description: '实例化预制体' },
{ name: 'getPrefabInfo', description: '获取预制体信息' },
{ name: 'savePrefab', description: '保存预制体' }
]},
{ category: 'project', name: '项目工具', tools: [
{ name: 'getProjectInfo', description: '获取项目信息' },
{ name: 'getAssetList', description: '获取资源列表' },
{ name: 'createAsset', description: '创建资源' },
{ name: 'deleteAsset', description: '删除资源' }
]},
{ category: 'debug', name: '调试工具', tools: [
{ name: 'getConsoleLogs', description: '获取控制台日志' },
{ name: 'getPerformanceStats', description: '获取性能统计' },
{ name: 'validateScene', description: '验证场景' },
{ name: 'getErrorLogs', description: '获取错误日志' }
]},
{ category: 'preferences', name: '偏好设置工具', tools: [
{ name: 'getPreferences', description: '获取偏好设置' },
{ name: 'setPreferences', description: '设置偏好设置' },
{ name: 'resetPreferences', description: '重置偏好设置' }
]},
{ category: 'server', name: '服务器工具', tools: [
{ name: 'getServerStatus', description: '获取服务器状态' },
{ name: 'getConnectedClients', description: '获取连接的客户端' },
{ name: 'getServerLogs', description: '获取服务器日志' }
]},
{ category: 'broadcast', name: '广播工具', tools: [
{ name: 'broadcastMessage', description: '广播消息' },
{ name: 'getBroadcastHistory', description: '获取广播历史' }
]},
{ category: 'sceneAdvanced', name: '高级场景工具', tools: [
{ name: 'optimizeScene', description: '优化场景' },
{ name: 'analyzeScene', description: '分析场景' },
{ name: 'batchOperation', description: '批量操作' }
]},
{ category: 'sceneView', name: '场景视图工具', tools: [
{ name: 'getViewportInfo', description: '获取视口信息' },
{ name: 'setViewportCamera', description: '设置视口相机' },
{ name: 'focusOnNode', description: '聚焦到节点' }
]},
{ category: 'referenceImage', name: '参考图片工具', tools: [
{ name: 'addReferenceImage', description: '添加参考图片' },
{ name: 'removeReferenceImage', description: '移除参考图片' },
{ name: 'getReferenceImages', description: '获取参考图片列表' }
]},
{ category: 'assetAdvanced', name: '高级资源工具', tools: [
{ name: 'importAsset', description: '导入资源' },
{ name: 'exportAsset', description: '导出资源' },
{ name: 'processAsset', description: '处理资源' }
]},
{ category: 'validation', name: '验证工具', tools: [
{ name: 'validateProject', description: '验证项目' },
{ name: 'validateAssets', description: '验证资源' },
{ name: 'generateReport', description: '生成报告' }
]}
];
this.availableTools = [];
toolCategories.forEach(category => {
category.tools.forEach(tool => {
this.availableTools.push({
category: category.category,
name: tool.name,
enabled: true, // 默认启用
description: tool.description
});
});
});
console.log(`[ToolManager] Initialized ${this.availableTools.length} default tools`);
}
public getAvailableTools(): ToolConfig[] {
return [...this.availableTools];
}
public getConfigurations(): ToolConfiguration[] {
return [...this.settings.configurations];
}
public getCurrentConfiguration(): ToolConfiguration | null {
if (!this.settings.currentConfigId) {
return null;
}
return this.settings.configurations.find(config => config.id === this.settings.currentConfigId) || null;
}
public createConfiguration(name: string, description?: string): ToolConfiguration {
if (this.settings.configurations.length >= this.settings.maxConfigSlots) {
throw new Error(`已达到最大配置槽位数量 (${this.settings.maxConfigSlots})`);
}
const config: ToolConfiguration = {
id: uuidv4(),
name,
description,
tools: this.availableTools.map(tool => ({ ...tool })),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
this.settings.configurations.push(config);
this.settings.currentConfigId = config.id;
this.saveSettings();
return config;
}
public updateConfiguration(configId: string, updates: Partial<ToolConfiguration>): ToolConfiguration {
const configIndex = this.settings.configurations.findIndex(config => config.id === configId);
if (configIndex === -1) {
throw new Error('配置不存在');
}
const config = this.settings.configurations[configIndex];
const updatedConfig: ToolConfiguration = {
...config,
...updates,
updatedAt: new Date().toISOString()
};
this.settings.configurations[configIndex] = updatedConfig;
this.saveSettings();
return updatedConfig;
}
public deleteConfiguration(configId: string): void {
const configIndex = this.settings.configurations.findIndex(config => config.id === configId);
if (configIndex === -1) {
throw new Error('配置不存在');
}
this.settings.configurations.splice(configIndex, 1);
// 如果删除的是当前配置清空当前配置ID
if (this.settings.currentConfigId === configId) {
this.settings.currentConfigId = this.settings.configurations.length > 0
? this.settings.configurations[0].id
: '';
}
this.saveSettings();
}
public setCurrentConfiguration(configId: string): void {
const config = this.settings.configurations.find(config => config.id === configId);
if (!config) {
throw new Error('配置不存在');
}
this.settings.currentConfigId = configId;
this.saveSettings();
}
public updateToolStatus(configId: string, category: string, toolName: string, enabled: boolean): void {
console.log(`Backend: Updating tool status - configId: ${configId}, category: ${category}, toolName: ${toolName}, enabled: ${enabled}`);
const config = this.settings.configurations.find(config => config.id === configId);
if (!config) {
console.error(`Backend: Config not found with ID: ${configId}`);
throw new Error('配置不存在');
}
console.log(`Backend: Found config: ${config.name}`);
const tool = config.tools.find(t => t.category === category && t.name === toolName);
if (!tool) {
console.error(`Backend: Tool not found - category: ${category}, name: ${toolName}`);
throw new Error('工具不存在');
}
console.log(`Backend: Found tool: ${tool.name}, current enabled: ${tool.enabled}, new enabled: ${enabled}`);
tool.enabled = enabled;
config.updatedAt = new Date().toISOString();
console.log(`Backend: Tool updated, saving settings...`);
this.saveSettings();
console.log(`Backend: Settings saved successfully`);
}
public updateToolStatusBatch(configId: string, updates: { category: string; name: string; enabled: boolean }[]): void {
console.log(`Backend: updateToolStatusBatch called with configId: ${configId}`);
console.log(`Backend: Current configurations count: ${this.settings.configurations.length}`);
console.log(`Backend: Current config IDs:`, this.settings.configurations.map(c => c.id));
const config = this.settings.configurations.find(config => config.id === configId);
if (!config) {
console.error(`Backend: Config not found with ID: ${configId}`);
console.error(`Backend: Available config IDs:`, this.settings.configurations.map(c => c.id));
throw new Error('配置不存在');
}
console.log(`Backend: Found config: ${config.name}, updating ${updates.length} tools`);
updates.forEach(update => {
const tool = config.tools.find(t => t.category === update.category && t.name === update.name);
if (tool) {
tool.enabled = update.enabled;
}
});
config.updatedAt = new Date().toISOString();
this.saveSettings();
console.log(`Backend: Batch update completed successfully`);
}
public exportConfiguration(configId: string): string {
const config = this.settings.configurations.find(config => config.id === configId);
if (!config) {
throw new Error('配置不存在');
}
return this.exportToolConfiguration(config);
}
public importConfiguration(configJson: string): ToolConfiguration {
const config = this.importToolConfiguration(configJson);
// 生成新的ID和时间戳
config.id = uuidv4();
config.createdAt = new Date().toISOString();
config.updatedAt = new Date().toISOString();
if (this.settings.configurations.length >= this.settings.maxConfigSlots) {
throw new Error(`已达到最大配置槽位数量 (${this.settings.maxConfigSlots})`);
}
this.settings.configurations.push(config);
this.saveSettings();
return config;
}
public getEnabledTools(): ToolConfig[] {
const currentConfig = this.getCurrentConfiguration();
if (!currentConfig) {
return this.availableTools.filter(tool => tool.enabled);
}
return currentConfig.tools.filter(tool => tool.enabled);
}
public getToolManagerState() {
const currentConfig = this.getCurrentConfiguration();
return {
success: true,
availableTools: currentConfig ? currentConfig.tools : this.getAvailableTools(),
selectedConfigId: this.settings.currentConfigId,
configurations: this.getConfigurations(),
maxConfigSlots: this.settings.maxConfigSlots
};
}
private saveSettings(): void {
console.log(`Backend: Saving settings, current configs count: ${this.settings.configurations.length}`);
this.saveToolManagerSettings(this.settings);
console.log(`Backend: Settings saved to file`);
}
}

View File

@@ -124,4 +124,33 @@ export interface MCPClient {
export interface ToolExecutor {
getTools(): ToolDefinition[];
execute(toolName: string, args: any): Promise<ToolResponse>;
}
// 工具配置管理相关接口
export interface ToolConfig {
category: string;
name: string;
enabled: boolean;
description: string;
}
export interface ToolConfiguration {
id: string;
name: string;
description?: string;
tools: ToolConfig[];
createdAt: string;
updatedAt: string;
}
export interface ToolManagerSettings {
configurations: ToolConfiguration[];
currentConfigId: string;
maxConfigSlots: number;
}
export interface ToolManagerState {
availableTools: ToolConfig[];
currentConfiguration: ToolConfiguration | null;
configurations: ToolConfiguration[];
}