You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

124 lines
6.7 KiB

'use strict';
// Реестр MCP-приложений ArtCraft: discovery бинарников, порты, флаги коннекта к окну.
const fs = require('fs');
const path = require('path');
// Имя инструмента -> папка, CLI-бинарь, GUI-бинарь, порт, аргументы коннекта CLI к окну.
// connect: null = флаг в help не найден, CLI стартует без него (headless или дефолтный коннект).
// В connect плейсхолдер {port} заменяется на порт приложения.
const DEFAULT_APPS = {
vectorcraft: { dir: 'VectorCraft', bin: 'vectorcraft-cli.exe', gui: 'vectorcraft.exe', port: 7979, connect: ['--connect', '127.0.0.1:{port}'] },
photocraft: { dir: 'PhotoCraft', bin: 'photocraft-cli.exe', gui: 'photocraft.exe', port: 7980, connect: ['--bridge', '127.0.0.1:{port}'] },
figcraft: { dir: 'FigCraft', bin: 'figcraft-cli.exe', gui: 'figcraft.exe', port: 7981, connect: ['--attach', '{port}'] },
designcraft: { dir: 'DesignCraft', bin: 'designcraft-cli.exe', gui: 'designcraft.exe', port: 7982, connect: ['--connect', '{port}'] },
deckcraft: { dir: 'DeckCraft', bin: 'deckcraft-cli.exe', gui: 'deckcraft.exe', port: 7983, connect: ['--connect', '{port}'] },
cadcraft: { dir: 'CADCraft', bin: 'cadcraft-cli.exe', gui: 'cadcraft.exe', port: 7984, connect: ['--connect', '127.0.0.1:{port}'] },
craftanything: { dir: 'Craft Anything', bin: 'craft-anything-cli.exe', gui: 'craft-anything.exe', port: 7985, connect: null },
effectcraft: { dir: 'EffectCraft', bin: 'effectcraft-cli.exe', gui: 'effectcraft.exe', port: 7986, connect: ['--bridge', '{port}'] },
filmcraft: { dir: 'FilmCraft', bin: 'filmcraft-cli.exe', gui: 'filmcraft.exe', port: 7987, connect: ['--bridge', '127.0.0.1:{port}'] },
gridcraft: { dir: 'GridCraft', bin: 'gridcraft-cli.exe', gui: 'gridcraft.exe', port: 7988, connect: ['--connect', '{port}'] },
lightcraft: { dir: 'LightCraft', bin: 'lightcraft-cli.exe', gui: 'lightcraft.exe', port: 7989, connect: ['--connect', '127.0.0.1:{port}'] },
materialcraft: { dir: 'MaterialCraft', bin: 'materialcraft-cli.exe', gui: 'materialcraft.exe', port: 7990, connect: ['--bridge', '{port}'] },
pdfcraft: { dir: 'PdfCraft', bin: 'pdfcraft-cli.exe', gui: 'pdfcraft.exe', port: 7991, connect: null },
solidcraft: { dir: 'SolidCraft', bin: 'solidcraft-cli.exe', gui: 'solidcraft.exe', port: 7992, connect: ['--connect', '127.0.0.1:{port}'] },
soundcraft: { dir: 'SoundCraft', bin: 'soundcraft-cli.exe', gui: 'soundcraft.exe', port: 7993, connect: ['--connect', '{port}'] },
texturecraft: { dir: 'TextureCraft', bin: 'texturecraft-cli.exe', gui: 'texturecraft.exe', port: 7994, connect: ['--bridge', '{port}'] },
wordcraft: { dir: 'WordCraft', bin: 'wordcraft-cli.exe', gui: 'wordcraft.exe', port: 7995, connect: ['--connect', '127.0.0.1:{port}'] },
};
function artcraftCfg(config) {
return (config && config.mcpServers && config.mcpServers.artcraft) || {};
}
function resolveRoot(config) {
const c = artcraftCfg(config);
return c.root || process.env.ARTCRAFT_ROOT || 'V:\\artcraft';
}
function resolveDisabled(config) {
const list = artcraftCfg(config).disabled || [];
return Array.isArray(list) ? list.map(String) : [];
}
function resolvePorts(config) {
const p = artcraftCfg(config).ports || {};
return (p && typeof p === 'object') ? p : {};
}
function discover(root, spec) {
const full = path.join(root, spec.dir, spec.bin);
try { return fs.existsSync(full) ? full : null; } catch { return null; }
}
function discoverGui(root, spec) {
if (!spec.gui) return null;
const full = path.join(root, spec.dir, spec.gui);
try { return fs.existsSync(full) ? full : null; } catch { return null; }
}
function toolNameFromDir(dirName) {
return String(dirName).toLowerCase().replace(/[^a-z]/g, '');
}
// Автообнаружение: папка с единственным *-cli.exe, которой нет в явном списке.
// Порт и флаг коннекта неизвестны -> null, работает headless.
function autodiscover(root, known) {
const found = {};
let entries = [];
try { entries = fs.readdirSync(root, { withFileTypes: true }); } catch { return found; }
for (const it of entries) {
if (!it.isDirectory()) continue;
const dir = path.join(root, it.name);
let exes = [];
try { exes = fs.readdirSync(dir).filter(n => /-cli\.exe$/i.test(n)); } catch { continue; }
if (exes.length !== 1) continue;
const bin = path.join(dir, exes[0]);
if (Object.values(known).some(s => path.join(root, s.dir, s.bin).toLowerCase() === bin.toLowerCase())) continue;
const name = toolNameFromDir(it.name);
if (!name || known[name] || found[name]) continue;
const guiName = it.name.toLowerCase().replace(/[^a-z0-9]/g, '') + '.exe';
let gui = null;
try {
const guiMatch = fs.readdirSync(dir).find(n => /\.exe$/i.test(n) && !/-cli\.exe$/i.test(n));
if (guiMatch) gui = guiMatch;
} catch {}
found[name] = { dir: it.name, bin: exes[0], gui, port: null, connect: null, auto: true };
}
return found;
}
function buildSpecs(config) {
const root = resolveRoot(config);
const disabled = resolveDisabled(config);
const portOverride = resolvePorts(config);
const known = { ...DEFAULT_APPS, ...autodiscover(root, DEFAULT_APPS) };
const out = {};
for (const [name, spec] of Object.entries(known)) {
if (disabled.includes(name)) { out[name] = { name, enabled: false, reason: 'disabled in config' }; continue; }
const bin = discover(root, spec);
if (!bin) { out[name] = { name, enabled: false, reason: 'not installed: ' + path.join(root, spec.dir, spec.bin) }; continue; }
const port = portOverride[name] != null ? Number(portOverride[name]) : spec.port;
const connect = Array.isArray(spec.connect) ? spec.connect.map(a => String(a).replace('{port}', String(port))) : null;
out[name] = {
name,
enabled: true,
bin,
gui: discoverGui(root, spec),
args: ['mcp'].concat(connect || []),
connectKnown: !!connect,
port,
cwd: path.dirname(bin),
auto: !!spec.auto,
};
}
return { root, apps: out };
}
// Аргументы для запуска GUI с управляющим портом.
function guiLaunchArgs(spec) {
if (!spec || !spec.gui) return null;
if (spec.port == null) return [];
return ['--control', String(spec.port)];
}
module.exports = { DEFAULT_APPS, resolveRoot, resolveDisabled, buildSpecs, toolNameFromDir, guiLaunchArgs };