docker-flutter-ios-simulator-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.
- package/CLAUDE.md +74 -0
- package/LICENSE +21 -0
- package/README.md +370 -0
- package/dist/flutter/log-buffer.d.ts +14 -0
- package/dist/flutter/log-buffer.d.ts.map +1 -0
- package/dist/flutter/log-buffer.js +49 -0
- package/dist/flutter/log-buffer.js.map +1 -0
- package/dist/flutter/process.d.ts +30 -0
- package/dist/flutter/process.d.ts.map +1 -0
- package/dist/flutter/process.js +241 -0
- package/dist/flutter/process.js.map +1 -0
- package/dist/flutter/types.d.ts +31 -0
- package/dist/flutter/types.d.ts.map +1 -0
- package/dist/flutter/types.js +2 -0
- package/dist/flutter/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +207 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +18 -0
- package/dist/server.js.map +1 -0
- package/dist/session/manager.d.ts +74 -0
- package/dist/session/manager.d.ts.map +1 -0
- package/dist/session/manager.js +209 -0
- package/dist/session/manager.js.map +1 -0
- package/dist/session/state.d.ts +14 -0
- package/dist/session/state.d.ts.map +1 -0
- package/dist/session/state.js +42 -0
- package/dist/session/state.js.map +1 -0
- package/dist/session/types.d.ts +26 -0
- package/dist/session/types.d.ts.map +1 -0
- package/dist/session/types.js +2 -0
- package/dist/session/types.js.map +1 -0
- package/dist/simulator/idb.d.ts +42 -0
- package/dist/simulator/idb.d.ts.map +1 -0
- package/dist/simulator/idb.js +127 -0
- package/dist/simulator/idb.js.map +1 -0
- package/dist/simulator/simctl.d.ts +9 -0
- package/dist/simulator/simctl.d.ts.map +1 -0
- package/dist/simulator/simctl.js +96 -0
- package/dist/simulator/simctl.js.map +1 -0
- package/dist/simulator/types.d.ts +25 -0
- package/dist/simulator/types.d.ts.map +1 -0
- package/dist/simulator/types.js +2 -0
- package/dist/simulator/types.js.map +1 -0
- package/dist/tools/flutter-commands.d.ts +128 -0
- package/dist/tools/flutter-commands.d.ts.map +1 -0
- package/dist/tools/flutter-commands.js +275 -0
- package/dist/tools/flutter-commands.js.map +1 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +499 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/session.d.ts +38 -0
- package/dist/tools/session.d.ts.map +1 -0
- package/dist/tools/session.js +40 -0
- package/dist/tools/session.js.map +1 -0
- package/dist/tools/simulator-ui.d.ts +102 -0
- package/dist/tools/simulator-ui.d.ts.map +1 -0
- package/dist/tools/simulator-ui.js +117 -0
- package/dist/tools/simulator-ui.js.map +1 -0
- package/dist/tools/simulator.d.ts +7 -0
- package/dist/tools/simulator.d.ts.map +1 -0
- package/dist/tools/simulator.js +8 -0
- package/dist/tools/simulator.js.map +1 -0
- package/dist/transport.d.ts +4 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +20 -0
- package/dist/transport.js.map +1 -0
- package/dist/utils/exec.d.ts +29 -0
- package/dist/utils/exec.d.ts.map +1 -0
- package/dist/utils/exec.js +109 -0
- package/dist/utils/exec.js.map +1 -0
- package/dist/utils/logger.d.ts +17 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +60 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { spawnStreaming } from '../utils/exec.js';
|
|
2
|
+
import { logger } from '../utils/logger.js';
|
|
3
|
+
import { LogBuffer } from './log-buffer.js';
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
import { resolve } from 'path';
|
|
6
|
+
export class FlutterProcessManager {
|
|
7
|
+
process;
|
|
8
|
+
flutterProcess;
|
|
9
|
+
logBuffer;
|
|
10
|
+
logSubscribers = new Set();
|
|
11
|
+
constructor(maxLogLines = 1000) {
|
|
12
|
+
this.logBuffer = new LogBuffer(maxLogLines);
|
|
13
|
+
}
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
15
|
+
async start(options) {
|
|
16
|
+
if (this.process) {
|
|
17
|
+
throw new Error('Flutter process already running');
|
|
18
|
+
}
|
|
19
|
+
logger.info('Starting Flutter process', {
|
|
20
|
+
worktreePath: options.worktreePath,
|
|
21
|
+
deviceId: options.deviceId,
|
|
22
|
+
});
|
|
23
|
+
// Security: Validate target file
|
|
24
|
+
// Prevents malicious clients from accessing arbitrary files on the system
|
|
25
|
+
if (options.target) {
|
|
26
|
+
const targetPath = resolve(options.worktreePath, options.target);
|
|
27
|
+
// Prevent path traversal attacks (e.g., "../../../etc/passwd")
|
|
28
|
+
// Ensures the target file is within the project directory boundary
|
|
29
|
+
if (!targetPath.startsWith(resolve(options.worktreePath))) {
|
|
30
|
+
throw new Error(`Security: Target file must be within project directory. ` +
|
|
31
|
+
`Target: ${options.target}`);
|
|
32
|
+
}
|
|
33
|
+
// Validate file exists to provide early feedback
|
|
34
|
+
if (!existsSync(targetPath)) {
|
|
35
|
+
throw new Error(`Target file does not exist: ${options.target}`);
|
|
36
|
+
}
|
|
37
|
+
// Only .dart files are valid Flutter entry points
|
|
38
|
+
// Prevents attempts to execute arbitrary file types
|
|
39
|
+
if (!targetPath.endsWith('.dart')) {
|
|
40
|
+
throw new Error(`Target must be a Dart file (.dart extension). ` +
|
|
41
|
+
`Provided: ${options.target}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Security: Validate flavor name
|
|
45
|
+
// Prevents command injection through flavor parameter
|
|
46
|
+
if (options.flavor) {
|
|
47
|
+
// Only allow safe characters that cannot break shell command parsing
|
|
48
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(options.flavor)) {
|
|
49
|
+
throw new Error(`Invalid flavor name: ${options.flavor}. ` +
|
|
50
|
+
`Flavor must contain only letters, numbers, hyphens, and underscores.`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Security: Validate additionalArgs against allowlist
|
|
54
|
+
// Prevents command injection by only allowing known-safe Flutter arguments
|
|
55
|
+
// This protects against malicious arguments like "--dart-define=KEY=$(rm -rf /)"
|
|
56
|
+
if (options.additionalArgs) {
|
|
57
|
+
const ALLOWED_ARGS = new Set([
|
|
58
|
+
'--debug',
|
|
59
|
+
'--release',
|
|
60
|
+
'--profile',
|
|
61
|
+
'--no-sound-null-safety',
|
|
62
|
+
'--enable-software-rendering',
|
|
63
|
+
'--verbose',
|
|
64
|
+
'-v',
|
|
65
|
+
]);
|
|
66
|
+
for (const arg of options.additionalArgs) {
|
|
67
|
+
// Check if argument is in the allowlist
|
|
68
|
+
if (ALLOWED_ARGS.has(arg)) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
// Validate --dart-define format more strictly to prevent injection
|
|
72
|
+
// Example attack prevented: --dart-define=X=$(malicious_command)
|
|
73
|
+
if (arg.startsWith('--dart-define=')) {
|
|
74
|
+
const defineValue = arg.substring('--dart-define='.length);
|
|
75
|
+
// Enforce KEY=VALUE where KEY follows identifier rules (alphanumeric/underscore)
|
|
76
|
+
// This prevents shell metacharacters and command substitution
|
|
77
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*=.*$/.test(defineValue)) {
|
|
78
|
+
throw new Error(`Invalid --dart-define format: ${arg}. ` +
|
|
79
|
+
`Expected --dart-define=KEY=VALUE where KEY is alphanumeric/underscore starting with letter or underscore`);
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// If we get here, argument is not allowed - reject it
|
|
84
|
+
throw new Error(`Invalid Flutter argument: ${arg}. ` +
|
|
85
|
+
`Allowed arguments: ${Array.from(ALLOWED_ARGS).join(', ')}, --dart-define=KEY=VALUE`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const args = ['run', '-d', options.deviceId];
|
|
89
|
+
if (options.target) {
|
|
90
|
+
args.push('-t', options.target);
|
|
91
|
+
}
|
|
92
|
+
if (options.flavor) {
|
|
93
|
+
args.push('--flavor', options.flavor);
|
|
94
|
+
}
|
|
95
|
+
if (options.additionalArgs) {
|
|
96
|
+
args.push(...options.additionalArgs);
|
|
97
|
+
}
|
|
98
|
+
this.flutterProcess = {
|
|
99
|
+
pid: 0,
|
|
100
|
+
status: 'starting',
|
|
101
|
+
startedAt: new Date(),
|
|
102
|
+
};
|
|
103
|
+
this.process = spawnStreaming('flutter', args, {
|
|
104
|
+
cwd: options.worktreePath,
|
|
105
|
+
onStdout: (data) => {
|
|
106
|
+
this.handleOutput(data);
|
|
107
|
+
},
|
|
108
|
+
onStderr: (data) => {
|
|
109
|
+
this.handleOutput(data);
|
|
110
|
+
},
|
|
111
|
+
onExit: (code, signal) => {
|
|
112
|
+
this.handleExit(code, signal);
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
if (this.process.pid) {
|
|
116
|
+
this.flutterProcess.pid = this.process.pid;
|
|
117
|
+
this.flutterProcess.status = 'running';
|
|
118
|
+
logger.info('Flutter process started', { pid: this.process.pid });
|
|
119
|
+
}
|
|
120
|
+
return this.flutterProcess;
|
|
121
|
+
}
|
|
122
|
+
handleOutput(data) {
|
|
123
|
+
const lines = data.split('\n');
|
|
124
|
+
for (const line of lines) {
|
|
125
|
+
if (line.trim()) {
|
|
126
|
+
this.logBuffer.append(line);
|
|
127
|
+
this.logSubscribers.forEach((subscriber) => {
|
|
128
|
+
try {
|
|
129
|
+
subscriber(line);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
logger.error('Error in log subscriber', { error: String(error) });
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
this.detectStatusChanges(line);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
detectStatusChanges(line) {
|
|
140
|
+
if (!this.flutterProcess)
|
|
141
|
+
return;
|
|
142
|
+
if (line.includes('Hot reload') || line.includes('Reloaded')) {
|
|
143
|
+
this.flutterProcess.status = 'hot-reloading';
|
|
144
|
+
setTimeout(() => {
|
|
145
|
+
if (this.flutterProcess) {
|
|
146
|
+
this.flutterProcess.status = 'running';
|
|
147
|
+
}
|
|
148
|
+
}, 1000);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
handleExit(code, signal) {
|
|
152
|
+
logger.info('Flutter process exited', { code, signal });
|
|
153
|
+
if (this.flutterProcess) {
|
|
154
|
+
this.flutterProcess.status = code === 0 ? 'stopped' : 'failed';
|
|
155
|
+
this.flutterProcess.stoppedAt = new Date();
|
|
156
|
+
this.flutterProcess.exitCode = code ?? undefined;
|
|
157
|
+
}
|
|
158
|
+
this.process = undefined;
|
|
159
|
+
}
|
|
160
|
+
stop() {
|
|
161
|
+
if (!this.process) {
|
|
162
|
+
logger.warn('No Flutter process to stop');
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
logger.info('Stopping Flutter process', { pid: this.process.pid });
|
|
166
|
+
this.process.stdin.write('q\n');
|
|
167
|
+
this.process.stdin.end();
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
hotReload() {
|
|
171
|
+
if (!this.process) {
|
|
172
|
+
logger.warn('No Flutter process for hot reload');
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
logger.info('Triggering hot reload', { pid: this.process.pid });
|
|
176
|
+
this.process.stdin.write('r\n');
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
hotRestart() {
|
|
180
|
+
if (!this.process) {
|
|
181
|
+
logger.warn('No Flutter process for hot restart');
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
logger.info('Triggering hot restart', { pid: this.process.pid });
|
|
185
|
+
this.process.stdin.write('R\n');
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
kill(signal = 'SIGTERM') {
|
|
189
|
+
if (!this.process) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
logger.info('Killing Flutter process', { pid: this.process.pid, signal });
|
|
193
|
+
return this.process.kill(signal);
|
|
194
|
+
}
|
|
195
|
+
getStatus() {
|
|
196
|
+
return this.flutterProcess;
|
|
197
|
+
}
|
|
198
|
+
getLogs(fromIndex, limit) {
|
|
199
|
+
const logs = this.logBuffer.getLogs(fromIndex, limit);
|
|
200
|
+
return {
|
|
201
|
+
logs,
|
|
202
|
+
nextIndex: this.logBuffer.getNextIndex(),
|
|
203
|
+
totalLines: this.logBuffer.getTotalLines(),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
subscribeToLogs(callback) {
|
|
207
|
+
this.logSubscribers.add(callback);
|
|
208
|
+
return () => {
|
|
209
|
+
this.logSubscribers.delete(callback);
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
clearLogs() {
|
|
213
|
+
this.logBuffer.clear();
|
|
214
|
+
}
|
|
215
|
+
async cleanup() {
|
|
216
|
+
logger.debug('Cleaning up Flutter process manager');
|
|
217
|
+
this.logSubscribers.clear();
|
|
218
|
+
// Capture process reference to avoid race condition
|
|
219
|
+
const currentProcess = this.process;
|
|
220
|
+
if (currentProcess) {
|
|
221
|
+
this.stop();
|
|
222
|
+
await new Promise((resolve) => {
|
|
223
|
+
const timeout = setTimeout(() => {
|
|
224
|
+
logger.warn('Flutter process did not stop gracefully, killing');
|
|
225
|
+
this.kill('SIGKILL');
|
|
226
|
+
resolve();
|
|
227
|
+
}, 5000);
|
|
228
|
+
currentProcess.wait().then(() => {
|
|
229
|
+
clearTimeout(timeout);
|
|
230
|
+
resolve();
|
|
231
|
+
}).catch((error) => {
|
|
232
|
+
logger.error('Error waiting for process', { error: String(error) });
|
|
233
|
+
clearTimeout(timeout);
|
|
234
|
+
resolve();
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
this.clearLogs();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../src/flutter/process.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type FlutterProcessStatus = 'starting' | 'running' | 'hot-reloading' | 'stopped' | 'failed';
|
|
2
|
+
export interface FlutterProcess {
|
|
3
|
+
pid: number;
|
|
4
|
+
status: FlutterProcessStatus;
|
|
5
|
+
startedAt: Date;
|
|
6
|
+
stoppedAt?: Date;
|
|
7
|
+
exitCode?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface LogEntry {
|
|
10
|
+
line: string;
|
|
11
|
+
timestamp: Date;
|
|
12
|
+
index: number;
|
|
13
|
+
}
|
|
14
|
+
export interface FlutterRunOptions {
|
|
15
|
+
worktreePath: string;
|
|
16
|
+
deviceId: string;
|
|
17
|
+
flavor?: string;
|
|
18
|
+
target?: string;
|
|
19
|
+
additionalArgs?: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface FlutterLogsQuery {
|
|
22
|
+
sessionId: string;
|
|
23
|
+
fromIndex?: number;
|
|
24
|
+
limit?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface FlutterLogsResult {
|
|
27
|
+
logs: LogEntry[];
|
|
28
|
+
nextIndex: number;
|
|
29
|
+
totalLines: number;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/flutter/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAC5B,UAAU,GACV,SAAS,GACT,eAAe,GACf,SAAS,GACT,QAAQ,CAAC;AAEb,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,oBAAoB,CAAC;IAC7B,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,IAAI,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/flutter/types.ts"],"names":[],"mappings":""}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import { createMCPServer } from './server.js';
|
|
4
|
+
import { setupTransport } from './transport.js';
|
|
5
|
+
import { sessionManager } from './session/manager.js';
|
|
6
|
+
import { logger } from './utils/logger.js';
|
|
7
|
+
function parseArgs() {
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
let port = parseInt(process.env.PORT || '3000', 10);
|
|
10
|
+
let host = process.env.HOST || '127.0.0.1';
|
|
11
|
+
let help = false;
|
|
12
|
+
let allowOnly = process.env.ALLOW_ONLY || '/Users/';
|
|
13
|
+
let maxSessions = parseInt(process.env.MAX_SESSIONS || '10', 10);
|
|
14
|
+
let preBuildScript = process.env.PRE_BUILD_SCRIPT;
|
|
15
|
+
let postBuildScript = process.env.POST_BUILD_SCRIPT;
|
|
16
|
+
for (let i = 0; i < args.length; i++) {
|
|
17
|
+
const arg = args[i];
|
|
18
|
+
if (arg === '--help' || arg === '-h') {
|
|
19
|
+
help = true;
|
|
20
|
+
}
|
|
21
|
+
else if (arg === '--port' || arg === '-p') {
|
|
22
|
+
const portValue = args[++i];
|
|
23
|
+
if (!portValue || isNaN(parseInt(portValue, 10))) {
|
|
24
|
+
console.error('Error: --port requires a numeric value');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
port = parseInt(portValue, 10);
|
|
28
|
+
}
|
|
29
|
+
else if (arg === '--host') {
|
|
30
|
+
const hostValue = args[++i];
|
|
31
|
+
if (!hostValue) {
|
|
32
|
+
console.error('Error: --host requires a value');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
host = hostValue;
|
|
36
|
+
}
|
|
37
|
+
else if (arg === '--allow-only') {
|
|
38
|
+
const pathValue = args[++i];
|
|
39
|
+
if (!pathValue) {
|
|
40
|
+
console.error('Error: --allow-only requires a path value');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
allowOnly = pathValue;
|
|
44
|
+
}
|
|
45
|
+
else if (arg === '--max-sessions') {
|
|
46
|
+
const maxValue = args[++i];
|
|
47
|
+
if (!maxValue || isNaN(parseInt(maxValue, 10))) {
|
|
48
|
+
console.error('Error: --max-sessions requires a numeric value');
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
maxSessions = parseInt(maxValue, 10);
|
|
52
|
+
if (maxSessions < 1) {
|
|
53
|
+
console.error('Error: --max-sessions must be at least 1');
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if (arg === '--pre-build-script') {
|
|
58
|
+
const scriptValue = args[++i];
|
|
59
|
+
if (!scriptValue) {
|
|
60
|
+
console.error('Error: --pre-build-script requires a command value');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
preBuildScript = scriptValue;
|
|
64
|
+
}
|
|
65
|
+
else if (arg === '--post-build-script') {
|
|
66
|
+
const scriptValue = args[++i];
|
|
67
|
+
if (!scriptValue) {
|
|
68
|
+
console.error('Error: --post-build-script requires a command value');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
postBuildScript = scriptValue;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
console.error(`Error: Unknown argument: ${arg}`);
|
|
75
|
+
console.error('Use --help to see available options');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { port, host, help, allowOnly, maxSessions, preBuildScript, postBuildScript };
|
|
80
|
+
}
|
|
81
|
+
function showHelp() {
|
|
82
|
+
console.log(`
|
|
83
|
+
docker-flutter-ios-simulator-mcp - MCP server for containerized Flutter iOS development
|
|
84
|
+
|
|
85
|
+
USAGE:
|
|
86
|
+
docker-flutter-ios-simulator-mcp [OPTIONS]
|
|
87
|
+
|
|
88
|
+
OPTIONS:
|
|
89
|
+
-p, --port <port> Port to listen on (default: 3000)
|
|
90
|
+
--host <host> Host address to bind to (default: 127.0.0.1)
|
|
91
|
+
--allow-only <path> Only allow Flutter projects under this path (default: /Users/)
|
|
92
|
+
--max-sessions <number> Maximum number of concurrent sessions (default: 10)
|
|
93
|
+
--pre-build-script <cmd> Command to run before flutter build/run (e.g., "git pull")
|
|
94
|
+
--post-build-script <cmd> Command to run after flutter build/run completes
|
|
95
|
+
-h, --help Show this help message
|
|
96
|
+
|
|
97
|
+
ENVIRONMENT VARIABLES:
|
|
98
|
+
PORT Port to listen on (overridden by --port)
|
|
99
|
+
HOST Host address to bind to (overridden by --host)
|
|
100
|
+
ALLOW_ONLY Path prefix for allowed projects (overridden by --allow-only)
|
|
101
|
+
MAX_SESSIONS Maximum concurrent sessions (overridden by --max-sessions)
|
|
102
|
+
PRE_BUILD_SCRIPT Command to run before builds (overridden by --pre-build-script)
|
|
103
|
+
POST_BUILD_SCRIPT Command to run after builds (overridden by --post-build-script)
|
|
104
|
+
LOG_LEVEL Logging level (debug, info, warn, error)
|
|
105
|
+
|
|
106
|
+
EXAMPLES:
|
|
107
|
+
docker-flutter-ios-simulator-mcp
|
|
108
|
+
docker-flutter-ios-simulator-mcp --port 8080
|
|
109
|
+
docker-flutter-ios-simulator-mcp --port 3000 --host localhost
|
|
110
|
+
docker-flutter-ios-simulator-mcp --allow-only /Users/alice/projects
|
|
111
|
+
docker-flutter-ios-simulator-mcp --max-sessions 20
|
|
112
|
+
docker-flutter-ios-simulator-mcp --pre-build-script "git pull" --post-build-script "echo Done"
|
|
113
|
+
PORT=8080 docker-flutter-ios-simulator-mcp
|
|
114
|
+
|
|
115
|
+
SECURITY:
|
|
116
|
+
By default, only Flutter projects under /Users/ are allowed to prevent
|
|
117
|
+
malicious MCP clients from accessing system directories like /etc/, /usr/, etc.
|
|
118
|
+
|
|
119
|
+
For more information, visit: https://github.com/zafnz/docker-flutter-ios-simulator-mcp
|
|
120
|
+
`);
|
|
121
|
+
}
|
|
122
|
+
async function main() {
|
|
123
|
+
const { port, host, help, allowOnly, maxSessions, preBuildScript, postBuildScript } = parseArgs();
|
|
124
|
+
if (help) {
|
|
125
|
+
showHelp();
|
|
126
|
+
process.exit(0);
|
|
127
|
+
}
|
|
128
|
+
// Configure session manager with allowed path prefix and session limit
|
|
129
|
+
sessionManager.configure(allowOnly, maxSessions, preBuildScript, postBuildScript);
|
|
130
|
+
const PORT = port;
|
|
131
|
+
const HOST = host;
|
|
132
|
+
const app = express();
|
|
133
|
+
// Middleware
|
|
134
|
+
app.use(express.json());
|
|
135
|
+
app.use(express.urlencoded({ extended: true }));
|
|
136
|
+
// Error handling middleware for JSON parse errors
|
|
137
|
+
app.use((err, req, res, next) => {
|
|
138
|
+
if (err instanceof SyntaxError && 'body' in err) {
|
|
139
|
+
logger.warn('Invalid JSON in request', { error: err.message, path: req.path });
|
|
140
|
+
res.status(400).json({ error: 'Invalid JSON in request body' });
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
next(err);
|
|
144
|
+
});
|
|
145
|
+
const mcpServer = createMCPServer();
|
|
146
|
+
const transport = setupTransport(app);
|
|
147
|
+
await mcpServer.connect(transport);
|
|
148
|
+
app.get('/health', (_req, res) => {
|
|
149
|
+
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
150
|
+
});
|
|
151
|
+
const server = app.listen(PORT, HOST, () => {
|
|
152
|
+
logger.info('Server started', {
|
|
153
|
+
host: HOST,
|
|
154
|
+
port: String(PORT),
|
|
155
|
+
mcpEndpoint: `http://${HOST}:${String(PORT)}/mcp`,
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
server.on('error', (error) => {
|
|
159
|
+
if (error.code === 'EADDRINUSE') {
|
|
160
|
+
logger.error(`Port ${String(PORT)} is already in use`);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
logger.error('Server error', { error: error.message });
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
const shutdown = async () => {
|
|
169
|
+
logger.info('Shutting down server');
|
|
170
|
+
// Forcefully exit after timeout to prevent hanging
|
|
171
|
+
const forceExitTimeout = setTimeout(() => {
|
|
172
|
+
logger.warn('Forced shutdown after timeout');
|
|
173
|
+
process.exit(0);
|
|
174
|
+
}, 10000);
|
|
175
|
+
try {
|
|
176
|
+
await sessionManager.cleanup();
|
|
177
|
+
await transport.close();
|
|
178
|
+
server.close(() => {
|
|
179
|
+
clearTimeout(forceExitTimeout);
|
|
180
|
+
logger.info('Server closed');
|
|
181
|
+
process.exit(0);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
logger.error('Error during shutdown', { error: String(error) });
|
|
186
|
+
clearTimeout(forceExitTimeout);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
process.on('SIGTERM', () => {
|
|
191
|
+
shutdown().catch((error) => {
|
|
192
|
+
logger.error('Fatal error during shutdown', { error: String(error) });
|
|
193
|
+
process.exit(1);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
process.on('SIGINT', () => {
|
|
197
|
+
shutdown().catch((error) => {
|
|
198
|
+
logger.error('Fatal error during shutdown', { error: String(error) });
|
|
199
|
+
process.exit(1);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
main().catch((error) => {
|
|
204
|
+
logger.error('Fatal error', { error: String(error) });
|
|
205
|
+
process.exit(1);
|
|
206
|
+
});
|
|
207
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,wBAAgB,eAAe,IAAI,SAAS,CAmB3C"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { registerTools } from './tools/index.js';
|
|
3
|
+
import { logger } from './utils/logger.js';
|
|
4
|
+
export function createMCPServer() {
|
|
5
|
+
logger.info('Creating MCP server');
|
|
6
|
+
const server = new McpServer({
|
|
7
|
+
name: 'docker-flutter-ios-simulator-mcp',
|
|
8
|
+
version: '0.1.0',
|
|
9
|
+
}, {
|
|
10
|
+
capabilities: {
|
|
11
|
+
tools: {},
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
registerTools(server);
|
|
15
|
+
logger.info('MCP server created');
|
|
16
|
+
return server;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3C,MAAM,UAAU,eAAe;IAC7B,MAAM,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IAEnC,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B;QACE,IAAI,EAAE,kCAAkC;QACxC,OAAO,EAAE,OAAO;KACjB,EACD;QACE,YAAY,EAAE;YACZ,KAAK,EAAE,EAAE;SACV;KACF,CACF,CAAC;IAEF,aAAa,CAAC,MAAM,CAAC,CAAC;IAEtB,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IAClC,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { CreateSessionParams, Session, SessionInfo } from './types.js';
|
|
2
|
+
export declare class SessionManager {
|
|
3
|
+
private allowedPathPrefix;
|
|
4
|
+
private maxSessions;
|
|
5
|
+
private preBuildScript?;
|
|
6
|
+
private postBuildScript?;
|
|
7
|
+
constructor(allowedPathPrefix?: string, maxSessions?: number);
|
|
8
|
+
/**
|
|
9
|
+
* Configure the session manager with security settings.
|
|
10
|
+
*
|
|
11
|
+
* @param allowedPathPrefix - Absolute path prefix for allowed Flutter projects (e.g., "/Users/")
|
|
12
|
+
* @param maxSessions - Optional maximum number of concurrent sessions
|
|
13
|
+
* @param preBuildScript - Optional command to run before flutter build/run
|
|
14
|
+
* @param postBuildScript - Optional command to run after flutter build/run
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* sessionManager.configure('/Users/alice/projects', 20, 'git pull', 'echo Done');
|
|
18
|
+
*/
|
|
19
|
+
configure(allowedPathPrefix: string, maxSessions?: number, preBuildScript?: string, postBuildScript?: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* Create a new development session with an iOS Simulator.
|
|
22
|
+
*
|
|
23
|
+
* Creates a fresh iOS Simulator, boots it, and associates it with the Flutter project directory.
|
|
24
|
+
* Validates that the project path is within allowed directories and contains a valid Flutter project.
|
|
25
|
+
*
|
|
26
|
+
* @param params - Session creation parameters
|
|
27
|
+
* @param params.worktreePath - Absolute path to Flutter project directory (must contain pubspec.yaml)
|
|
28
|
+
* @param params.deviceType - iOS device type to simulate (default: "iPhone 16 Pro")
|
|
29
|
+
*
|
|
30
|
+
* @returns Session information including unique ID and simulator UDID
|
|
31
|
+
*
|
|
32
|
+
* @throws {Error} If session limit is reached
|
|
33
|
+
* @throws {Error} If path is outside allowed prefix
|
|
34
|
+
* @throws {Error} If project directory doesn't exist or isn't valid
|
|
35
|
+
* @throws {Error} If simulator creation or boot fails
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* const session = await sessionManager.createSession({
|
|
39
|
+
* worktreePath: '/Users/alice/my-flutter-app',
|
|
40
|
+
* deviceType: 'iPhone 16 Pro'
|
|
41
|
+
* });
|
|
42
|
+
* // Returns: { id: 'uuid...', worktreePath: '...', simulatorUdid: '...', deviceType: '...' }
|
|
43
|
+
*/
|
|
44
|
+
createSession(params: CreateSessionParams): Promise<SessionInfo>;
|
|
45
|
+
endSession(sessionId: string): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* List all active sessions.
|
|
48
|
+
*
|
|
49
|
+
* @returns Array of session information for all active sessions
|
|
50
|
+
*/
|
|
51
|
+
listSessions(): SessionInfo[];
|
|
52
|
+
/**
|
|
53
|
+
* Get detailed information about a specific session.
|
|
54
|
+
*
|
|
55
|
+
* @param sessionId - Unique session identifier from createSession()
|
|
56
|
+
* @returns Session object with full state, or undefined if not found
|
|
57
|
+
*/
|
|
58
|
+
getSession(sessionId: string): Session | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Get the pre-build script command if configured.
|
|
61
|
+
*
|
|
62
|
+
* @returns Pre-build script command, or undefined if not set
|
|
63
|
+
*/
|
|
64
|
+
getPreBuildScript(): string | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Get the post-build script command if configured.
|
|
67
|
+
*
|
|
68
|
+
* @returns Post-build script command, or undefined if not set
|
|
69
|
+
*/
|
|
70
|
+
getPostBuildScript(): string | undefined;
|
|
71
|
+
cleanup(): Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
export declare const sessionManager: SessionManager;
|
|
74
|
+
//# sourceMappingURL=manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/session/manager.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAIvE,qBAAa,cAAc;IACzB,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,cAAc,CAAC,CAAS;IAChC,OAAO,CAAC,eAAe,CAAC,CAAS;gBAErB,iBAAiB,SAAY,EAAE,WAAW,SAAK;IAK3D;;;;;;;;;;OAUG;IACH,SAAS,CACP,iBAAiB,EAAE,MAAM,EACzB,WAAW,CAAC,EAAE,MAAM,EACpB,cAAc,CAAC,EAAE,MAAM,EACvB,eAAe,CAAC,EAAE,MAAM,GACvB,IAAI;IAeP;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACG,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,WAAW,CAAC;IA8EhE,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4ClD;;;;OAIG;IACH,YAAY,IAAI,WAAW,EAAE;IAI7B;;;;;OAKG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAIlD;;;;OAIG;IACH,iBAAiB,IAAI,MAAM,GAAG,SAAS;IAIvC;;;;OAIG;IACH,kBAAkB,IAAI,MAAM,GAAG,SAAS;IAIlC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAe/B;AAED,eAAO,MAAM,cAAc,gBAAuB,CAAC"}
|