|
|
|
|
@ -4,7 +4,7 @@
@@ -4,7 +4,7 @@
|
|
|
|
|
const net = require('net'); |
|
|
|
|
const fs = require('fs'); |
|
|
|
|
const path = require('path'); |
|
|
|
|
const { spawn } = require('child_process'); |
|
|
|
|
const { spawn, execFile } = require('child_process'); |
|
|
|
|
const { McpClient } = require('./mcp-client'); |
|
|
|
|
const { buildSpecs, guiLaunchArgs } = require('./mcp-registry'); |
|
|
|
|
|
|
|
|
|
@ -25,6 +25,28 @@ function checkPort(port, timeoutMs) {
@@ -25,6 +25,28 @@ function checkPort(port, timeoutMs) {
|
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function findPidByPort(port) { |
|
|
|
|
return new Promise((resolve) => { |
|
|
|
|
if (port == null) return resolve(null); |
|
|
|
|
const ps = 'Get-NetTCPConnection -LocalPort ' + Number(port) + ' -State Listen -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty OwningProcess'; |
|
|
|
|
execFile('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', ps], { timeout: 8000, windowsHide: true }, (err, stdout) => { |
|
|
|
|
if (err) return resolve(null); |
|
|
|
|
const pid = parseInt(String(stdout).trim(), 10); |
|
|
|
|
resolve(Number.isFinite(pid) && pid > 0 ? pid : null); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function softKill(pid) { |
|
|
|
|
return new Promise((resolve) => { |
|
|
|
|
if (!pid) return resolve({ ok: false, reason: 'no pid' }); |
|
|
|
|
// No /F: sends WM_CLOSE so the app can ask to save.
|
|
|
|
|
execFile('taskkill.exe', ['/PID', String(pid)], { timeout: 8000, windowsHide: true }, (err, stdout, stderr) => { |
|
|
|
|
resolve({ ok: !err, out: String(stdout || '').trim(), err: String(stderr || '').trim() }); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async function waitPort(port, totalMs) { |
|
|
|
|
const deadline = Date.now() + (totalMs || 15000); |
|
|
|
|
while (Date.now() < deadline) { |
|
|
|
|
@ -227,13 +249,42 @@ class McpManager {
@@ -227,13 +249,42 @@ class McpManager {
|
|
|
|
|
try { return await p; } finally { delete this.launching[name]; } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Закрыть MCP-клиент приложения (окно не трогаем).
|
|
|
|
|
stop(name) { |
|
|
|
|
// Закрыть MCP-клиент; если клиента нет, но окно открыто — мягко закрыть окно.
|
|
|
|
|
async stop(name) { |
|
|
|
|
const spec = this._spec(name); |
|
|
|
|
const c = this.clients[name]; |
|
|
|
|
const clientStopped = !!c; |
|
|
|
|
if (c) { try { c.close(); } catch {} delete this.clients[name]; } |
|
|
|
|
delete this.starting[name]; |
|
|
|
|
this.lastError[name] = null; |
|
|
|
|
return { app: name, stopped: true }; |
|
|
|
|
|
|
|
|
|
const guiUp = spec.port != null ? await checkPort(spec.port, 600) : false; |
|
|
|
|
if (!guiUp) { |
|
|
|
|
return { app: name, stopped: clientStopped, windowClosed: false }; |
|
|
|
|
} |
|
|
|
|
const pid = await findPidByPort(spec.port); |
|
|
|
|
if (!pid) { |
|
|
|
|
return { app: name, stopped: clientStopped, windowClosed: false, warning: 'port is open but no owning process found' }; |
|
|
|
|
} |
|
|
|
|
console.log('[mcp] closing ' + name + ' window, pid ' + pid + ' (port ' + spec.port + ')'); |
|
|
|
|
const r = await softKill(pid); |
|
|
|
|
if (!r.ok) { |
|
|
|
|
this.lastError[name] = 'close window failed: ' + (r.err || 'unknown'); |
|
|
|
|
return { app: name, stopped: clientStopped, windowClosed: false, error: r.err || 'taskkill failed' }; |
|
|
|
|
} |
|
|
|
|
const closed = await new Promise((resolve) => { |
|
|
|
|
const t0 = Date.now(); |
|
|
|
|
const iv = setInterval(async () => { |
|
|
|
|
if (!(await checkPort(spec.port, 500))) { clearInterval(iv); resolve(true); } |
|
|
|
|
else if (Date.now() - t0 > 5000) { clearInterval(iv); resolve(false); } |
|
|
|
|
}, 400); |
|
|
|
|
}); |
|
|
|
|
if (!closed) { |
|
|
|
|
this.lastError[name] = 'window did not close in 5s (app may be waiting to save)'; |
|
|
|
|
return { app: name, stopped: clientStopped, windowClosed: false, warning: 'app did not close within 5s, it may be asking to save' }; |
|
|
|
|
} |
|
|
|
|
console.log('[mcp] ' + name + ' window closed'); |
|
|
|
|
return { app: name, stopped: clientStopped, windowClosed: true }; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
toolDefs() { |
|
|
|
|
|