@vscxml/mcp 0.1.0

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.
@@ -0,0 +1,211 @@
1
+ import { spawn } from 'child_process';
2
+ import { existsSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { DEFAULT_PORTS } from './types.js';
5
+ /**
6
+ * Discovers and optionally auto-spawns VSCXML backend processes.
7
+ *
8
+ * Discovery order for each backend:
9
+ * 1. Environment variable (VSCXML_GENERATOR_URL, etc.)
10
+ * 2. MCP config args (--generator, --simulator)
11
+ * 3. Default ports (probe localhost)
12
+ * 4. Auto-spawn from known install paths
13
+ */
14
+ export class ProcessManager {
15
+ children = [];
16
+ /** Discover the generator URL */
17
+ async discoverGenerator(configUrl) {
18
+ // 1. Env var
19
+ const envUrl = process.env.VSCXML_GENERATOR_URL;
20
+ if (envUrl)
21
+ return envUrl;
22
+ // 2. Config
23
+ if (configUrl)
24
+ return configUrl;
25
+ // 3. Probe default port
26
+ const defaultUrl = `http://localhost:${DEFAULT_PORTS.generator}`;
27
+ if (await this.probeHttp(defaultUrl))
28
+ return defaultUrl;
29
+ return null;
30
+ }
31
+ /** Discover the simulator URL */
32
+ async discoverSimulator(configUrl) {
33
+ // 1. Env var
34
+ const envUrl = process.env.VSCXML_SIMULATOR_URL;
35
+ if (envUrl)
36
+ return envUrl;
37
+ // 2. Config
38
+ if (configUrl)
39
+ return configUrl;
40
+ // 3. Probe default port
41
+ const defaultUrl = `ws://localhost:${DEFAULT_PORTS.simulatorWs}`;
42
+ if (await this.probeWs(defaultUrl))
43
+ return defaultUrl;
44
+ return null;
45
+ }
46
+ /** Discover the editor URL */
47
+ async discoverEditor(configUrl) {
48
+ const envUrl = process.env.VSCXML_EDITOR_URL;
49
+ if (envUrl)
50
+ return envUrl;
51
+ if (configUrl)
52
+ return configUrl;
53
+ const defaultUrl = `ws://localhost:${DEFAULT_PORTS.editorApi}`;
54
+ if (await this.probeWs(defaultUrl))
55
+ return defaultUrl;
56
+ return null;
57
+ }
58
+ /** Get known install paths for the generator CLI */
59
+ getGeneratorPaths() {
60
+ const platform = process.platform;
61
+ const paths = [];
62
+ if (platform === 'win32') {
63
+ const localAppData = process.env.LOCALAPPDATA || '';
64
+ paths.push(join(localAppData, 'VSCXML-Generator-CLI', 'vscxml-generator-cli.exe'));
65
+ }
66
+ else if (platform === 'darwin') {
67
+ paths.push('/Applications/VSCXML-Generator-CLI.app/Contents/MacOS/VSCXML-Generator-CLI');
68
+ }
69
+ else {
70
+ paths.push('/opt/vscxml-generator-cli/bin/vscxml-generator-cli');
71
+ }
72
+ return paths.filter((p) => existsSync(p));
73
+ }
74
+ /** Get known install paths for the simulator */
75
+ getSimulatorPaths() {
76
+ const platform = process.platform;
77
+ const paths = [];
78
+ if (platform === 'win32') {
79
+ const localAppData = process.env.LOCALAPPDATA || '';
80
+ paths.push(join(localAppData, 'VSCXML-Simulator', 'vscxml-simulator.exe'));
81
+ }
82
+ else if (platform === 'darwin') {
83
+ paths.push('/Applications/VSCXML-Simulator.app/Contents/MacOS/VSCXML-Simulator');
84
+ }
85
+ else {
86
+ paths.push('/opt/vscxml-simulator/bin/vscxml-simulator');
87
+ }
88
+ return paths.filter((p) => existsSync(p));
89
+ }
90
+ /** Spawn a generator process */
91
+ spawnGenerator(execPath, port = DEFAULT_PORTS.generator) {
92
+ const child = spawn(execPath, ['serve', '--port', String(port), '--cors'], {
93
+ stdio: 'ignore',
94
+ detached: false,
95
+ });
96
+ this.children.push(child);
97
+ return child;
98
+ }
99
+ /** Spawn a simulator process */
100
+ spawnSimulator(execPath, wsPort = DEFAULT_PORTS.simulatorWs, restPort = DEFAULT_PORTS.simulatorRest) {
101
+ const child = spawn(execPath, ['serve', '--ws-port', String(wsPort), '--rest-port', String(restPort), '--cors'], { stdio: 'ignore', detached: false });
102
+ this.children.push(child);
103
+ return child;
104
+ }
105
+ /** Shut down all spawned child processes */
106
+ shutdown() {
107
+ for (const child of this.children) {
108
+ try {
109
+ child.kill();
110
+ }
111
+ catch {
112
+ // ignore
113
+ }
114
+ }
115
+ this.children = [];
116
+ }
117
+ /**
118
+ * Auto-discover and optionally spawn the generator backend.
119
+ * Returns the URL if available, null if not.
120
+ */
121
+ async ensureGenerator(configUrl) {
122
+ // Try discovery first
123
+ const url = await this.discoverGenerator(configUrl);
124
+ if (url)
125
+ return url;
126
+ // Try auto-spawn
127
+ const paths = this.getGeneratorPaths();
128
+ if (paths.length === 0)
129
+ return null;
130
+ console.error(`[ProcessManager] Generator not found on default port, spawning ${paths[0]}...`);
131
+ this.spawnGenerator(paths[0]);
132
+ // Wait for it to come up (poll health)
133
+ const targetUrl = `http://localhost:${DEFAULT_PORTS.generator}`;
134
+ if (await this.waitForHealth(targetUrl, 15000)) {
135
+ return targetUrl;
136
+ }
137
+ console.error('[ProcessManager] Generator failed to start');
138
+ return null;
139
+ }
140
+ /**
141
+ * Auto-discover and optionally spawn the simulator backend.
142
+ */
143
+ async ensureSimulator(configUrl) {
144
+ const url = await this.discoverSimulator(configUrl);
145
+ if (url)
146
+ return url;
147
+ const paths = this.getSimulatorPaths();
148
+ if (paths.length === 0)
149
+ return null;
150
+ console.error(`[ProcessManager] Simulator not found on default port, spawning ${paths[0]}...`);
151
+ this.spawnSimulator(paths[0]);
152
+ const targetUrl = `ws://localhost:${DEFAULT_PORTS.simulatorWs}`;
153
+ if (await this.waitForWs(targetUrl, 15000)) {
154
+ return targetUrl;
155
+ }
156
+ console.error('[ProcessManager] Simulator failed to start');
157
+ return null;
158
+ }
159
+ async waitForHealth(url, timeoutMs) {
160
+ const deadline = Date.now() + timeoutMs;
161
+ while (Date.now() < deadline) {
162
+ if (await this.probeHttp(url))
163
+ return true;
164
+ await new Promise((r) => setTimeout(r, 500));
165
+ }
166
+ return false;
167
+ }
168
+ async waitForWs(url, timeoutMs) {
169
+ const deadline = Date.now() + timeoutMs;
170
+ while (Date.now() < deadline) {
171
+ if (await this.probeWs(url))
172
+ return true;
173
+ await new Promise((r) => setTimeout(r, 500));
174
+ }
175
+ return false;
176
+ }
177
+ async probeHttp(url) {
178
+ try {
179
+ const res = await fetch(`${url}/api/health`, {
180
+ signal: AbortSignal.timeout(2000),
181
+ });
182
+ return res.ok;
183
+ }
184
+ catch {
185
+ return false;
186
+ }
187
+ }
188
+ async probeWs(url) {
189
+ // Use a simple TCP connection probe instead of full WS handshake.
190
+ // More reliable on Windows where WS open/close races can cause false negatives.
191
+ const { createConnection } = await import('net');
192
+ const parsed = new URL(url);
193
+ const port = parseInt(parsed.port) || 48621;
194
+ const host = parsed.hostname || '127.0.0.1';
195
+ return new Promise((resolve) => {
196
+ try {
197
+ const socket = createConnection({ host, port }, () => {
198
+ socket.destroy();
199
+ resolve(true);
200
+ });
201
+ socket.setTimeout(2000);
202
+ socket.on('timeout', () => { socket.destroy(); resolve(false); });
203
+ socket.on('error', () => { resolve(false); });
204
+ }
205
+ catch {
206
+ resolve(false);
207
+ }
208
+ });
209
+ }
210
+ }
211
+ //# sourceMappingURL=process-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-manager.js","sourceRoot":"","sources":["../src/process-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { McpConfig } from './types.js';
3
+ export declare function createServer(config?: McpConfig): McpServer;