chore(mcp): 换用 cocos-mcp-server 扩展, .mcp.json 改 HTTP 直连
旧扩展 @iflow-mcp/harady-cocos-creator-mcp(164 个细粒度工具 + stdio-bridge
转发)整个删除, 换成 cocos-mcp-server v1.7.9(16 个聚合工具, 靠 action 分派)。
新扩展自身起 streamable-http 服务, 不再需要 stdio 桥, 故 .mcp.json 从
node + stdio-bridge.js 改为 { type: "http", url: "http://127.0.0.1:3000/mcp" }。
原配置指向的 client/stdio-bridge.js 已随旧扩展删除, MCP 因此加载失败。
服务器名保留 cocos-creator-mcp(而非 INSTALL.md 建议的 cocos-creator), 以免
打断 CLAUDE.md、cocos-mcp 技能与 settings.local.json 里的
mcp__cocos-creator-mcp__* 允许规则。
gitignore: 新扩展是商业付费产品(vberai), 非 npm 可重装, dist/sidecar/static
共约 16M 发行产物+加密载荷+平台二进制不入库; 顺带忽略 Claude Code 个人本地
配置与编辑器运行时端口文件。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+94
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Batch auto_knowledge via MCP HTTP — gets runtime defaults from editor
|
||||
* Requires: editor running with MCP on port 3000
|
||||
*/
|
||||
|
||||
import http from 'http';
|
||||
|
||||
const MCP_URL = 'http://127.0.0.1:3000/mcp';
|
||||
const ALL = [
|
||||
'cc.Label','cc.Sprite','cc.Button','cc.Widget','cc.Layout',
|
||||
'cc.UITransform','cc.RichText','cc.ScrollView','cc.Toggle',
|
||||
'cc.ToggleContainer','cc.ProgressBar','cc.Slider','cc.PageView',
|
||||
'cc.Mask','cc.Graphics','cc.UIOpacity','cc.SafeArea',
|
||||
'cc.BlockInputEvents','cc.LabelOutline','cc.LabelShadow',
|
||||
'cc.PageViewIndicator','cc.EditBox',
|
||||
'cc.AudioSource','cc.VideoPlayer','cc.WebView',
|
||||
'cc.ParticleSystem2D',
|
||||
'cc.Camera','cc.Canvas','cc.Animation','cc.SkeletalAnimation',
|
||||
'cc.MeshRenderer','cc.SkinnedMeshRenderer',
|
||||
'cc.DirectionalLight','cc.PointLight','cc.SpotLight','cc.SphereLight',
|
||||
'cc.RigidBody2D','cc.BoxCollider2D','cc.CircleCollider2D','cc.PolygonCollider2D',
|
||||
'cc.RigidBody','cc.BoxCollider','cc.SphereCollider','cc.CapsuleCollider','cc.MeshCollider',
|
||||
'cc.MotionStreak','cc.Billboard','cc.Line',
|
||||
'cc.TiledMap','cc.TiledLayer',
|
||||
'cc.HingeJoint2D','cc.DistanceJoint2D','cc.SpringJoint2D',
|
||||
'cc.FixedJoint2D','cc.WheelJoint2D','cc.SliderJoint2D',
|
||||
'cc.RelativeJoint2D','cc.MouseJoint2D',
|
||||
'cc.HingeConstraint','cc.FixedConstraint','cc.PointToPointConstraint',
|
||||
'cc.Terrain','cc.LODGroup','cc.ReflectionProbe',
|
||||
'sp.Skeleton','dragonBones.ArmatureDisplay',
|
||||
];
|
||||
|
||||
function callMcp(toolName, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const body = JSON.stringify({
|
||||
jsonrpc: '2.0', id: Date.now(), method: 'tools/call',
|
||||
params: { name: toolName, arguments: args }
|
||||
});
|
||||
const req = http.request(MCP_URL, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
|
||||
timeout: 30000,
|
||||
}, (res) => {
|
||||
const chunks = [];
|
||||
res.on('data', c => chunks.push(c));
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const data = JSON.parse(Buffer.concat(chunks).toString());
|
||||
const content = data.result?.content || [];
|
||||
const text = content.find(c => c.type === 'text')?.text;
|
||||
resolve(text ? JSON.parse(text) : {});
|
||||
} catch (e) { reject(e); }
|
||||
});
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
|
||||
req.write(body);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// Check connectivity
|
||||
try {
|
||||
await callMcp('cocos_editor', { action: 'project_info' });
|
||||
} catch {
|
||||
console.error('❌ Cannot connect to MCP at', MCP_URL);
|
||||
console.error(' Make sure editor is running and plugin is loaded.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Connected. Running auto_knowledge(save:true) for ${ALL.length} components...\n`);
|
||||
|
||||
let ok = 0, fail = 0, withDefaults = 0;
|
||||
|
||||
for (const comp of ALL) {
|
||||
try {
|
||||
const r = await callMcp('cocos_devtools', { action: 'auto_knowledge', componentType: comp, save: true });
|
||||
if (r.success) {
|
||||
ok++;
|
||||
const hasRuntime = r.data?.hasRuntimeDefaults;
|
||||
if (hasRuntime) withDefaults++;
|
||||
const propCount = Object.keys(r.data?.recommendedKnowledge?.properties || {}).length;
|
||||
console.log(` ✅ ${comp.padEnd(35)} ${propCount} props ${hasRuntime ? '(+defaults)' : '(dts only)'}`);
|
||||
} else {
|
||||
fail++;
|
||||
console.log(` ❌ ${comp}: ${r.error}`);
|
||||
}
|
||||
} catch (e) {
|
||||
fail++;
|
||||
console.log(` ❌ ${comp}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nDone: ${ok} ok, ${fail} fail, ${withDefaults} with runtime defaults`);
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Batch auto_knowledge — generate complete COMPONENT_PROPERTIES from d.ts
|
||||
* Output: knowledge JSON files + combined TS snippet for knowledge-handler.ts
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const mod = require(path.resolve(__dirname, '../dist/tools/cocos/utils/component-knowledge'));
|
||||
|
||||
const ALL_COMPONENTS = [
|
||||
'cc.Label', 'cc.Sprite', 'cc.Button', 'cc.Widget', 'cc.Layout',
|
||||
'cc.UITransform', 'cc.RichText', 'cc.ScrollView', 'cc.Toggle',
|
||||
'cc.ToggleContainer', 'cc.ProgressBar', 'cc.Slider', 'cc.PageView',
|
||||
'cc.Mask', 'cc.Graphics', 'cc.UIOpacity', 'cc.SafeArea',
|
||||
'cc.BlockInputEvents', 'cc.LabelOutline', 'cc.LabelShadow',
|
||||
'cc.PageViewIndicator', 'cc.EditBox',
|
||||
'cc.AudioSource', 'cc.VideoPlayer', 'cc.WebView',
|
||||
'cc.ParticleSystem2D', 'cc.ParticleSystem',
|
||||
'cc.Camera', 'cc.Canvas', 'cc.Animation', 'cc.SkeletalAnimation',
|
||||
'cc.MeshRenderer', 'cc.SkinnedMeshRenderer',
|
||||
'cc.DirectionalLight', 'cc.PointLight', 'cc.SpotLight', 'cc.SphereLight',
|
||||
'cc.RigidBody2D', 'cc.BoxCollider2D', 'cc.CircleCollider2D', 'cc.PolygonCollider2D',
|
||||
'cc.RigidBody', 'cc.BoxCollider', 'cc.SphereCollider', 'cc.CapsuleCollider', 'cc.MeshCollider',
|
||||
'cc.MotionStreak', 'cc.Billboard', 'cc.Line',
|
||||
'cc.TiledMap', 'cc.TiledLayer',
|
||||
'cc.HingeJoint2D', 'cc.DistanceJoint2D', 'cc.SpringJoint2D',
|
||||
'cc.FixedJoint2D', 'cc.WheelJoint2D', 'cc.SliderJoint2D',
|
||||
'cc.RelativeJoint2D', 'cc.MouseJoint2D',
|
||||
'cc.HingeConstraint', 'cc.FixedConstraint', 'cc.PointToPointConstraint',
|
||||
'cc.Terrain', 'cc.LODGroup', 'cc.ReflectionProbe',
|
||||
'sp.Skeleton', 'dragonBones.ArmatureDisplay',
|
||||
];
|
||||
|
||||
const snippets = [];
|
||||
let saved = 0, failed = 0;
|
||||
|
||||
for (const comp of ALL_COMPONENTS) {
|
||||
const className = mod.componentTypeToClassName(comp);
|
||||
const dtsInfo = mod.parseDtsClass(className, { recursive: true });
|
||||
|
||||
if (!dtsInfo) {
|
||||
console.log(` ❌ ${comp}: not found in d.ts`);
|
||||
failed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const classified = mod.classifyProperties(dtsInfo, null);
|
||||
const knowledge = mod.buildRecommendedKnowledge(comp, dtsInfo, classified);
|
||||
|
||||
// Save knowledge file
|
||||
mod.saveKnowledgeFile(comp, knowledge);
|
||||
|
||||
// Generate TS snippet
|
||||
const snippet = mod.generateTsSnippet(comp, knowledge);
|
||||
snippets.push(snippet);
|
||||
|
||||
const propCount = Object.keys(knowledge.properties).length;
|
||||
console.log(` ✅ ${comp}: ${propCount} props`);
|
||||
saved++;
|
||||
}
|
||||
|
||||
// Write combined snippets to file
|
||||
const outputPath = path.resolve(__dirname, '../dev-knowledge/COMPONENT_PROPERTIES_GENERATED.ts');
|
||||
const outputDir = path.dirname(outputPath);
|
||||
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
|
||||
|
||||
const header = `/**
|
||||
* AUTO-GENERATED by batch-auto-knowledge.js
|
||||
* Date: ${new Date().toISOString()}
|
||||
* Components: ${saved}
|
||||
*
|
||||
* Paste this into COMPONENT_PROPERTIES in knowledge-handler.ts
|
||||
*/
|
||||
|
||||
const COMPONENT_PROPERTIES: Record<string, any> = {\n`;
|
||||
|
||||
const footer = `\n};\n`;
|
||||
|
||||
fs.writeFileSync(outputPath, header + snippets.join('\n') + footer, 'utf-8');
|
||||
|
||||
console.log(`\n Saved: ${saved} | Failed: ${failed}`);
|
||||
console.log(` Knowledge files: dev-knowledge/components/`);
|
||||
console.log(` TS output: ${outputPath}`);
|
||||
@@ -0,0 +1,111 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { spawnSync } = require("child_process");
|
||||
|
||||
const PATH = {
|
||||
packageJSON: path.join(__dirname, "../package.json")
|
||||
};
|
||||
|
||||
function checkCreatorTypesVersion(version) {
|
||||
try {
|
||||
// 根据平台选择合适的npm命令
|
||||
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
||||
|
||||
// 检查npm命令是否可用
|
||||
const npmCheck = spawnSync(npmCmd, ["--version"], {
|
||||
stdio: 'pipe',
|
||||
shell: process.platform === "win32"
|
||||
});
|
||||
|
||||
if (npmCheck.error || npmCheck.status !== 0) {
|
||||
console.warn("Warning: npm command not available, skipping version check");
|
||||
return true; // 如果npm不可用,跳过检查
|
||||
}
|
||||
|
||||
// 获取版本列表
|
||||
const result = spawnSync(npmCmd, ["view", "@cocos/creator-types", "versions"], {
|
||||
stdio: 'pipe',
|
||||
shell: process.platform === "win32"
|
||||
});
|
||||
|
||||
if (result.error || result.status !== 0) {
|
||||
console.warn("Warning: Failed to fetch @cocos/creator-types versions, skipping check");
|
||||
return true; // 如果获取失败,跳过检查
|
||||
}
|
||||
|
||||
let output = result.stdout.toString().trim();
|
||||
|
||||
// 尝试解析JSON
|
||||
try {
|
||||
const versions = JSON.parse(output);
|
||||
if (Array.isArray(versions)) {
|
||||
return versions.includes(version);
|
||||
} else if (typeof versions === 'string') {
|
||||
return versions.includes(version);
|
||||
}
|
||||
} catch (parseError) {
|
||||
// 如果JSON解析失败,尝试作为字符串处理
|
||||
return output.includes(version);
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.warn("Warning: Version check failed:", error.message);
|
||||
return true; // 出错时跳过检查
|
||||
}
|
||||
}
|
||||
|
||||
function getCreatorTypesVersion() {
|
||||
try {
|
||||
// 检查package.json文件是否存在
|
||||
if (!fs.existsSync(PATH.packageJSON)) {
|
||||
console.warn("Warning: package.json not found");
|
||||
return null;
|
||||
}
|
||||
|
||||
const packageContent = fs.readFileSync(PATH.packageJSON, "utf8");
|
||||
const packageJson = JSON.parse(packageContent);
|
||||
|
||||
// 检查devDependencies是否存在
|
||||
if (!packageJson.devDependencies || !packageJson.devDependencies["@cocos/creator-types"]) {
|
||||
console.warn("Warning: @cocos/creator-types not found in devDependencies");
|
||||
return null;
|
||||
}
|
||||
|
||||
const versionString = packageJson.devDependencies["@cocos/creator-types"];
|
||||
return versionString.replace(/^[^\d]+/, "");
|
||||
} catch (error) {
|
||||
console.warn("Warning: Failed to read package.json:", error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
try {
|
||||
const creatorTypesVersion = getCreatorTypesVersion();
|
||||
|
||||
if (!creatorTypesVersion) {
|
||||
console.log("Skipping @cocos/creator-types version check");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkCreatorTypesVersion(creatorTypesVersion)) {
|
||||
console.log("\x1b[33mWarning:\x1b[0m");
|
||||
console.log(" @en");
|
||||
console.log(" Version check of @cocos/creator-types failed.");
|
||||
console.log(` The definition of ${creatorTypesVersion} has not been released yet. Please export the definition to the ./node_modules directory by selecting "Developer -> Export Interface Definition" in the menu of the Creator editor.`);
|
||||
console.log(" The definition of the corresponding version will be released on npm after the editor is officially released.");
|
||||
console.log(" @zh");
|
||||
console.log(" @cocos/creator-types 版本检查失败。");
|
||||
console.log(` ${creatorTypesVersion} 定义还未发布,请先通过 Creator 编辑器菜单 "开发者 -> 导出接口定义",导出定义到 ./node_modules 目录。`);
|
||||
console.log(" 对应版本的定义会在编辑器正式发布后同步发布到 npm 上。");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Preinstall script error:", error.message);
|
||||
// 不要抛出错误,让安装继续进行
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 执行主函数
|
||||
main();
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Generate COMPONENT_PROPERTIES from saved dev-knowledge JSON files.
|
||||
* Does NOT re-parse d.ts — uses the saved knowledge (which has runtime defaults).
|
||||
*/
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const mod = require(path.resolve(__dirname, '../dist/tools/cocos/utils/component-knowledge'));
|
||||
|
||||
const files = mod.listKnowledgeFiles();
|
||||
const snippets = [];
|
||||
|
||||
for (const comp of files) {
|
||||
const knowledge = mod.loadKnowledgeFile(comp);
|
||||
if (!knowledge) continue;
|
||||
snippets.push(mod.generateTsSnippet(comp, knowledge));
|
||||
const propCount = Object.keys(knowledge.properties).length;
|
||||
const defaultCount = Object.values(knowledge.properties).filter(p => p.default !== undefined).length;
|
||||
console.log(` ${comp.padEnd(35)} ${propCount} props, ${defaultCount} with defaults`);
|
||||
}
|
||||
|
||||
const outputPath = path.resolve(__dirname, '../dev-knowledge/COMPONENT_PROPERTIES_GENERATED.txt');
|
||||
const header = `const COMPONENT_PROPERTIES: Record<string, any> = {\n`;
|
||||
const footer = `\n};\n`;
|
||||
fs.writeFileSync(outputPath, header + snippets.join('\n') + footer, 'utf-8');
|
||||
console.log(`\nGenerated ${files.length} components → ${outputPath}`);
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* validate-schemas.js
|
||||
* 构建后校验所有 MCP 工具的 inputSchema,检测常见 JSON Schema 错误:
|
||||
* - array 类型缺少 items
|
||||
* - object 类型缺少 properties
|
||||
* - enum 为空数组
|
||||
* - type 字段缺失
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
// 颜色
|
||||
const RED = '\x1b[31m';
|
||||
const YELLOW = '\x1b[33m';
|
||||
const GREEN = '\x1b[32m';
|
||||
const RESET = '\x1b[0m';
|
||||
|
||||
let errors = 0;
|
||||
let warnings = 0;
|
||||
|
||||
function validateSchema(schema, context, toolName) {
|
||||
if (!schema || typeof schema !== 'object') return;
|
||||
|
||||
// 检查 array 缺少 items
|
||||
if (schema.type === 'array' && !schema.items) {
|
||||
console.error(`${RED}ERROR${RESET} [${toolName}] ${context}: type "array" missing "items"`);
|
||||
errors++;
|
||||
}
|
||||
|
||||
// 检查 items 但不是 array
|
||||
if (schema.items && schema.type !== 'array') {
|
||||
console.warn(`${YELLOW}WARN${RESET} [${toolName}] ${context}: has "items" but type is "${schema.type}", not "array"`);
|
||||
warnings++;
|
||||
}
|
||||
|
||||
// 检查 enum 为空
|
||||
if (schema.enum && Array.isArray(schema.enum) && schema.enum.length === 0) {
|
||||
console.warn(`${YELLOW}WARN${RESET} [${toolName}] ${context}: empty "enum" array`);
|
||||
warnings++;
|
||||
}
|
||||
|
||||
// 递归检查 properties
|
||||
if (schema.properties && typeof schema.properties === 'object') {
|
||||
for (const [key, value] of Object.entries(schema.properties)) {
|
||||
validateSchema(value, `${context}.${key}`, toolName);
|
||||
}
|
||||
}
|
||||
|
||||
// 递归检查 items
|
||||
if (schema.items && typeof schema.items === 'object') {
|
||||
validateSchema(schema.items, `${context}.items`, toolName);
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const distDir = path.join(__dirname, '..', 'dist');
|
||||
if (!fs.existsSync(distDir)) {
|
||||
console.error(`${RED}ERROR${RESET} dist/ not found. Run "npm run build" first.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 收集所有工具定义
|
||||
const tools = [];
|
||||
|
||||
// 1. 加载 CocosTools
|
||||
try {
|
||||
const { CocosTools } = require(path.join(distDir, 'tools', 'cocos', 'cocos-tools.js'));
|
||||
const cocosTools = new CocosTools();
|
||||
tools.push(...cocosTools.getTools());
|
||||
} catch (e) {
|
||||
console.warn(`${YELLOW}WARN${RESET} Could not load CocosTools: ${e.message}`);
|
||||
}
|
||||
|
||||
// 2. 加载独立工具集
|
||||
const toolFiles = [
|
||||
'tools/scene-tools.js',
|
||||
'tools/node-tools.js',
|
||||
'tools/component-tools.js',
|
||||
'tools/prefab-tools.js',
|
||||
'tools/project-tools.js',
|
||||
'tools/scene-view-tools.js',
|
||||
'tools/reference-image-tools.js',
|
||||
'tools/preferences-tools.js',
|
||||
'tools/asset-advanced-tools.js',
|
||||
'tools/debug-tools.js',
|
||||
'tools/server-tools.js',
|
||||
];
|
||||
|
||||
for (const file of toolFiles) {
|
||||
try {
|
||||
const fullPath = path.join(distDir, file);
|
||||
if (!fs.existsSync(fullPath)) continue;
|
||||
const mod = require(fullPath);
|
||||
// 找到导出的类并实例化
|
||||
for (const [, ExportedClass] of Object.entries(mod)) {
|
||||
if (typeof ExportedClass === 'function' && ExportedClass.prototype && typeof ExportedClass.prototype.getTools === 'function') {
|
||||
const instance = new ExportedClass();
|
||||
tools.push(...instance.getTools());
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`${YELLOW}WARN${RESET} Could not load ${file}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nValidating ${tools.length} tool schemas...\n`);
|
||||
|
||||
for (const tool of tools) {
|
||||
if (tool.inputSchema) {
|
||||
validateSchema(tool.inputSchema, 'inputSchema', tool.name);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('');
|
||||
if (errors > 0) {
|
||||
console.error(`${RED}FAILED${RESET}: ${errors} error(s), ${warnings} warning(s)`);
|
||||
process.exit(1);
|
||||
} else if (warnings > 0) {
|
||||
console.log(`${YELLOW}PASSED${RESET} with ${warnings} warning(s)`);
|
||||
} else {
|
||||
console.log(`${GREEN}PASSED${RESET}: All schemas valid`);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user