@geminilight/mindos 0.6.5 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/app/api/mcp/restart/route.ts +41 -8
- package/app/app/api/monitoring/route.ts +1 -1
- package/bin/cli.js +1 -1
- package/bin/lib/mcp-spawn.js +11 -49
- package/bin/lib/stop.js +1 -1
- package/mcp/dist/index.cjs +409 -0
- package/mcp/package.json +4 -1
- package/package.json +2 -3
- package/scripts/postinstall.js +0 -39
|
@@ -42,26 +42,40 @@ function killByPort(port: number) {
|
|
|
42
42
|
*
|
|
43
43
|
* Unlike /api/restart which restarts the entire MindOS (Web + MCP),
|
|
44
44
|
* this endpoint only restarts the MCP server. The Web UI stays up.
|
|
45
|
+
*
|
|
46
|
+
* When running under Desktop's ProcessManager, the crash handler
|
|
47
|
+
* auto-respawns MCP when it exits. We wait for the port to free,
|
|
48
|
+
* then spawn only if nothing re-bound (i.e. CLI mode with no crash
|
|
49
|
+
* handler). This avoids spawning a duplicate that races for the port.
|
|
45
50
|
*/
|
|
46
51
|
export async function POST() {
|
|
47
52
|
try {
|
|
48
53
|
const cfg = readConfig();
|
|
49
|
-
const mcpPort = (cfg.mcpPort
|
|
54
|
+
const mcpPort = Number(process.env.MINDOS_MCP_PORT) || Number(cfg.mcpPort) || 8781;
|
|
50
55
|
const webPort = process.env.MINDOS_WEB_PORT || '3456';
|
|
51
|
-
const authToken = cfg.authToken as string | undefined;
|
|
56
|
+
const authToken = process.env.AUTH_TOKEN || (cfg.authToken as string | undefined);
|
|
57
|
+
const managed = process.env.MINDOS_MANAGED === '1';
|
|
52
58
|
|
|
53
59
|
// Step 1: Kill process on MCP port
|
|
54
60
|
killByPort(mcpPort);
|
|
55
61
|
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
if (managed) {
|
|
63
|
+
// Desktop ProcessManager will auto-respawn MCP via its crash handler.
|
|
64
|
+
return NextResponse.json({ ok: true, port: mcpPort, note: 'ProcessManager will respawn' });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Step 2 (CLI mode only): Wait for port to free, then spawn a new MCP
|
|
68
|
+
const portFree = await waitForPortFree(mcpPort, 5000);
|
|
69
|
+
if (!portFree) {
|
|
70
|
+
return NextResponse.json({ error: `MCP port ${mcpPort} still in use after kill` }, { status: 500 });
|
|
71
|
+
}
|
|
58
72
|
|
|
59
|
-
// Step 3: Spawn new MCP server
|
|
60
73
|
const root = process.env.MINDOS_PROJECT_ROOT || resolve(process.cwd(), '..');
|
|
61
74
|
const mcpDir = resolve(root, 'mcp');
|
|
62
75
|
|
|
63
|
-
|
|
64
|
-
|
|
76
|
+
const mcpBundle = resolve(mcpDir, 'dist', 'index.cjs');
|
|
77
|
+
if (!existsSync(mcpBundle)) {
|
|
78
|
+
return NextResponse.json({ error: 'MCP bundle not found — reinstall @geminilight/mindos' }, { status: 500 });
|
|
65
79
|
}
|
|
66
80
|
|
|
67
81
|
const env: NodeJS.ProcessEnv = {
|
|
@@ -73,7 +87,7 @@ export async function POST() {
|
|
|
73
87
|
...(authToken ? { AUTH_TOKEN: authToken } : {}),
|
|
74
88
|
};
|
|
75
89
|
|
|
76
|
-
const child = spawn(
|
|
90
|
+
const child = spawn(process.execPath, [mcpBundle], {
|
|
77
91
|
cwd: mcpDir,
|
|
78
92
|
detached: true,
|
|
79
93
|
stdio: 'ignore',
|
|
@@ -87,3 +101,22 @@ export async function POST() {
|
|
|
87
101
|
return NextResponse.json({ error: message }, { status: 500 });
|
|
88
102
|
}
|
|
89
103
|
}
|
|
104
|
+
|
|
105
|
+
function isPortInUse(port: number): Promise<boolean> {
|
|
106
|
+
return new Promise((res) => {
|
|
107
|
+
const net = require('net');
|
|
108
|
+
const server = net.createServer();
|
|
109
|
+
server.once('error', () => res(true));
|
|
110
|
+
server.once('listening', () => { server.close(); res(false); });
|
|
111
|
+
server.listen(port, '127.0.0.1');
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function waitForPortFree(port: number, timeoutMs: number): Promise<boolean> {
|
|
116
|
+
const start = Date.now();
|
|
117
|
+
while (Date.now() - start < timeoutMs) {
|
|
118
|
+
if (!(await isPortInUse(port))) return true;
|
|
119
|
+
await new Promise(r => setTimeout(r, 300));
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
@@ -63,7 +63,7 @@ export async function GET() {
|
|
|
63
63
|
const kbStats = getCachedKbStats(mindRoot);
|
|
64
64
|
|
|
65
65
|
// Detect MCP status from environment / config
|
|
66
|
-
const mcpPort = Number(process.env.MCP_PORT) ||
|
|
66
|
+
const mcpPort = Number(process.env.MINDOS_MCP_PORT) || Number(process.env.MCP_PORT) || 8781;
|
|
67
67
|
|
|
68
68
|
return NextResponse.json({
|
|
69
69
|
system: {
|
package/bin/cli.js
CHANGED
|
@@ -433,7 +433,7 @@ const commands = {
|
|
|
433
433
|
if (process.env.MCP_TRANSPORT === 'http') {
|
|
434
434
|
process.env.MCP_PORT = process.env.MINDOS_MCP_PORT || '8781';
|
|
435
435
|
}
|
|
436
|
-
run(`
|
|
436
|
+
run(`node dist/index.cjs`, resolve(ROOT, 'mcp'));
|
|
437
437
|
},
|
|
438
438
|
|
|
439
439
|
// ── stop / restart ─────────────────────────────────────────────────────────
|
package/bin/lib/mcp-spawn.js
CHANGED
|
@@ -1,59 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { existsSync, readFileSync
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
|
-
import { arch, platform } from 'node:os';
|
|
5
4
|
import { ROOT, CONFIG_PATH } from './constants.js';
|
|
6
|
-
import { bold, red
|
|
7
|
-
import { npmInstall } from './utils.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* If mcp/node_modules was installed on a different platform (e.g. Linux CI → macOS user),
|
|
11
|
-
* native packages like esbuild will crash. Detect via stamp or @esbuild heuristics, then reinstall.
|
|
12
|
-
*/
|
|
13
|
-
function ensureMcpNativeDeps() {
|
|
14
|
-
const mcpDir = resolve(ROOT, 'mcp');
|
|
15
|
-
const nm = resolve(mcpDir, 'node_modules');
|
|
16
|
-
if (!existsSync(nm)) return;
|
|
17
|
-
|
|
18
|
-
const host = `${platform()}-${arch()}`;
|
|
19
|
-
const stamp = resolve(mcpDir, '.mindos-npm-ci-platform');
|
|
20
|
-
let needsReinstall = false;
|
|
21
|
-
|
|
22
|
-
if (existsSync(stamp)) {
|
|
23
|
-
try {
|
|
24
|
-
needsReinstall = readFileSync(stamp, 'utf-8').trim() !== host;
|
|
25
|
-
} catch { /* fall through */ }
|
|
26
|
-
} else {
|
|
27
|
-
const esbuildDir = resolve(nm, '@esbuild');
|
|
28
|
-
if (existsSync(esbuildDir)) {
|
|
29
|
-
try {
|
|
30
|
-
const names = readdirSync(esbuildDir);
|
|
31
|
-
if (names.length > 0 && !names.includes(host)) needsReinstall = true;
|
|
32
|
-
} catch { /* ignore */ }
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (!needsReinstall) return;
|
|
37
|
-
|
|
38
|
-
console.log(yellow('MCP dependencies were built for another platform — reinstalling...'));
|
|
39
|
-
rmSync(nm, { recursive: true, force: true });
|
|
40
|
-
npmInstall(mcpDir, '--no-workspaces');
|
|
41
|
-
try { writeFileSync(stamp, host, 'utf-8'); } catch { /* non-fatal */ }
|
|
42
|
-
}
|
|
5
|
+
import { bold, red } from './colors.js';
|
|
43
6
|
|
|
44
7
|
export function spawnMcp(verbose = false) {
|
|
45
8
|
const mcpPort = process.env.MINDOS_MCP_PORT || '8781';
|
|
46
9
|
const webPort = process.env.MINDOS_WEB_PORT || '3456';
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
if (!existsSync(
|
|
50
|
-
|
|
51
|
-
|
|
10
|
+
|
|
11
|
+
const mcpBundle = resolve(ROOT, 'mcp', 'dist', 'index.cjs');
|
|
12
|
+
if (!existsSync(mcpBundle)) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
`MCP bundle not found: ${mcpBundle}\n` +
|
|
15
|
+
`This MindOS installation may be corrupted. Try: npm install -g @geminilight/mindos@latest`,
|
|
16
|
+
);
|
|
52
17
|
}
|
|
53
|
-
ensureMcpNativeDeps();
|
|
54
18
|
|
|
55
|
-
// Read AUTH_TOKEN directly from config to avoid stale system env overriding
|
|
56
|
-
// the user's configured token. Config is the source of truth for auth.
|
|
57
19
|
let configAuthToken;
|
|
58
20
|
try {
|
|
59
21
|
const cfg = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
|
|
@@ -69,7 +31,7 @@ export function spawnMcp(verbose = false) {
|
|
|
69
31
|
...(configAuthToken ? { AUTH_TOKEN: configAuthToken } : {}),
|
|
70
32
|
...(verbose ? { MCP_VERBOSE: '1' } : {}),
|
|
71
33
|
};
|
|
72
|
-
const child = spawn(
|
|
34
|
+
const child = spawn(process.execPath, [mcpBundle], {
|
|
73
35
|
cwd: resolve(ROOT, 'mcp'),
|
|
74
36
|
stdio: 'inherit',
|
|
75
37
|
env,
|
package/bin/lib/stop.js
CHANGED
|
@@ -111,7 +111,7 @@ export function stopMindos(opts = {}) {
|
|
|
111
111
|
if (!pids.length && portKilled === 0) {
|
|
112
112
|
// Last resort: pattern match (for envs without lsof)
|
|
113
113
|
try { execSync('pkill -f "next start|next dev" 2>/dev/null || true', { stdio: ['ignore', 'inherit', 'inherit'] }); } catch {}
|
|
114
|
-
try { execSync('pkill -f "mcp/src/index"
|
|
114
|
+
try { execSync('pkill -f "mcp/(src/index|dist/index)" 2>/dev/null || true', { stdio: ['ignore', 'inherit', 'inherit'] }); } catch {}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
if (!pids.length) console.log(green('\u2714 Done'));
|