@sveltium/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/README.md +146 -0
- package/package.json +50 -0
- package/scripts/postinstall.js +96 -0
- package/src/client/dom/console-capture.js +143 -0
- package/src/client/dom/element-finder.js +59 -0
- package/src/client/dom/event-dispatcher.js +180 -0
- package/src/client/dom/snapshot-builder.js +229 -0
- package/src/client/index.js +232 -0
- package/src/client/tool-executor.js +630 -0
- package/src/server/index.js +115 -0
- package/src/server/mcp-handler.js +722 -0
- package/src/server/ws-bridge.js +212 -0
- package/types/index.d.ts +58 -0
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP Protocol Handler (stdio transport)
|
|
5
|
+
*
|
|
6
|
+
* Handles JSON-RPC 2.0 messages from Claude Code via stdin/stdout
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
var PROTOCOL_VERSION = '2024-11-05'
|
|
10
|
+
var SERVER_NAME = 'nwjs-mcp'
|
|
11
|
+
var SERVER_VERSION = '0.1.0'
|
|
12
|
+
|
|
13
|
+
// Default NW.js executable path - can be overridden via setNwPath()
|
|
14
|
+
var DEFAULT_NW_PATH = null
|
|
15
|
+
|
|
16
|
+
function MCPServer(wsBridge) {
|
|
17
|
+
this.wsBridge = wsBridge
|
|
18
|
+
this.buffer = ''
|
|
19
|
+
this.running = false
|
|
20
|
+
this.nwPath = DEFAULT_NW_PATH
|
|
21
|
+
this.startedApps = [] // Track spawned app processes
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Set the default NW.js executable path
|
|
26
|
+
* @param {string} path - Path to nw.exe
|
|
27
|
+
*/
|
|
28
|
+
MCPServer.prototype.setNwPath = function(path) {
|
|
29
|
+
this.nwPath = path
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
MCPServer.prototype.start = function() {
|
|
33
|
+
var self = this
|
|
34
|
+
this.running = true
|
|
35
|
+
|
|
36
|
+
process.stdin.setEncoding('utf8')
|
|
37
|
+
|
|
38
|
+
process.stdin.on('data', function(chunk) {
|
|
39
|
+
self._handleData(chunk)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
process.stdin.on('end', function() {
|
|
43
|
+
self.running = false
|
|
44
|
+
process.exit(0)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// Log to stderr (stdout is for MCP protocol)
|
|
48
|
+
process.stderr.write('[nwjs-mcp] Server started, waiting for MCP messages...\n')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
MCPServer.prototype._handleData = function(chunk) {
|
|
52
|
+
var self = this
|
|
53
|
+
this.buffer += chunk
|
|
54
|
+
|
|
55
|
+
var lines = this.buffer.split('\n')
|
|
56
|
+
this.buffer = lines.pop()
|
|
57
|
+
|
|
58
|
+
for (var i = 0; i < lines.length; i++) {
|
|
59
|
+
var line = lines[i].trim()
|
|
60
|
+
if (line) {
|
|
61
|
+
self._processMessage(line)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Handle a message and return response via callback (for HTTP mode)
|
|
68
|
+
*/
|
|
69
|
+
MCPServer.prototype.handleMessage = function(message, callback) {
|
|
70
|
+
var self = this
|
|
71
|
+
var request = null
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
request = JSON.parse(message)
|
|
75
|
+
} catch (e) {
|
|
76
|
+
callback(JSON.stringify({
|
|
77
|
+
jsonrpc: '2.0',
|
|
78
|
+
id: null,
|
|
79
|
+
error: { code: -32700, message: 'Parse error' }
|
|
80
|
+
}))
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
var id = request.id
|
|
85
|
+
var method = request.method
|
|
86
|
+
var params = request.params || {}
|
|
87
|
+
|
|
88
|
+
this._handleMethod(id, method, params, function(result, error) {
|
|
89
|
+
if (error) {
|
|
90
|
+
callback(JSON.stringify({
|
|
91
|
+
jsonrpc: '2.0',
|
|
92
|
+
id: id,
|
|
93
|
+
error: error
|
|
94
|
+
}))
|
|
95
|
+
} else {
|
|
96
|
+
callback(JSON.stringify({
|
|
97
|
+
jsonrpc: '2.0',
|
|
98
|
+
id: id,
|
|
99
|
+
result: result
|
|
100
|
+
}))
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
MCPServer.prototype._processMessage = function(message) {
|
|
106
|
+
var self = this
|
|
107
|
+
var request = null
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
request = JSON.parse(message)
|
|
111
|
+
} catch (e) {
|
|
112
|
+
this._sendError(null, -32700, 'Parse error')
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
var id = request.id
|
|
117
|
+
var method = request.method
|
|
118
|
+
var params = request.params || {}
|
|
119
|
+
|
|
120
|
+
this._handleMethod(id, method, params, function(result, error) {
|
|
121
|
+
if (error) {
|
|
122
|
+
self._sendError(id, error.code, error.message, error.data)
|
|
123
|
+
} else {
|
|
124
|
+
self._sendResult(id, result)
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
MCPServer.prototype._handleMethod = function(id, method, params, callback) {
|
|
130
|
+
var self = this
|
|
131
|
+
|
|
132
|
+
switch (method) {
|
|
133
|
+
case 'initialize':
|
|
134
|
+
callback({
|
|
135
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
136
|
+
capabilities: { tools: {} },
|
|
137
|
+
serverInfo: { name: SERVER_NAME, version: SERVER_VERSION }
|
|
138
|
+
})
|
|
139
|
+
break
|
|
140
|
+
|
|
141
|
+
case 'initialized':
|
|
142
|
+
callback({})
|
|
143
|
+
break
|
|
144
|
+
|
|
145
|
+
case 'tools/list':
|
|
146
|
+
callback({ tools: this._getToolsList() })
|
|
147
|
+
break
|
|
148
|
+
|
|
149
|
+
case 'tools/call':
|
|
150
|
+
this._handleToolCall(params, callback)
|
|
151
|
+
break
|
|
152
|
+
|
|
153
|
+
case 'ping':
|
|
154
|
+
callback({})
|
|
155
|
+
break
|
|
156
|
+
|
|
157
|
+
default:
|
|
158
|
+
callback(null, { code: -32601, message: 'Method not found: ' + method })
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
MCPServer.prototype._getToolsList = function() {
|
|
163
|
+
return [
|
|
164
|
+
{
|
|
165
|
+
name: 'browser_snapshot',
|
|
166
|
+
description: 'Capture accessibility snapshot of the current page. Returns a tree structure with element refs for targeting interactions.',
|
|
167
|
+
inputSchema: {
|
|
168
|
+
type: 'object',
|
|
169
|
+
properties: {}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 'browser_take_screenshot',
|
|
174
|
+
description: 'Take a screenshot of the current page or a specific element.',
|
|
175
|
+
inputSchema: {
|
|
176
|
+
type: 'object',
|
|
177
|
+
properties: {
|
|
178
|
+
ref: { type: 'string', description: 'Element ref to screenshot (optional)' },
|
|
179
|
+
fullPage: { type: 'boolean', description: 'Capture full scrollable page' }
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: 'browser_click',
|
|
185
|
+
description: 'Click on an element specified by ref.',
|
|
186
|
+
inputSchema: {
|
|
187
|
+
type: 'object',
|
|
188
|
+
properties: {
|
|
189
|
+
ref: { type: 'string', description: 'Element ref from snapshot' },
|
|
190
|
+
element: { type: 'string', description: 'Human-readable element description' },
|
|
191
|
+
button: { type: 'string', enum: ['left', 'right', 'middle'], description: 'Mouse button' },
|
|
192
|
+
doubleClick: { type: 'boolean', description: 'Perform double click' }
|
|
193
|
+
},
|
|
194
|
+
required: ['ref', 'element']
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: 'browser_type',
|
|
199
|
+
description: 'Type text into an editable element.',
|
|
200
|
+
inputSchema: {
|
|
201
|
+
type: 'object',
|
|
202
|
+
properties: {
|
|
203
|
+
ref: { type: 'string', description: 'Element ref from snapshot' },
|
|
204
|
+
element: { type: 'string', description: 'Human-readable element description' },
|
|
205
|
+
text: { type: 'string', description: 'Text to type' },
|
|
206
|
+
slowly: { type: 'boolean', description: 'Type character by character' },
|
|
207
|
+
submit: { type: 'boolean', description: 'Press Enter after typing' }
|
|
208
|
+
},
|
|
209
|
+
required: ['ref', 'element', 'text']
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: 'browser_evaluate',
|
|
214
|
+
description: 'Evaluate JavaScript expression on the page.',
|
|
215
|
+
inputSchema: {
|
|
216
|
+
type: 'object',
|
|
217
|
+
properties: {
|
|
218
|
+
function: { type: 'string', description: 'JavaScript function to evaluate' },
|
|
219
|
+
ref: { type: 'string', description: 'Element ref to pass to function (optional)' },
|
|
220
|
+
element: { type: 'string', description: 'Human-readable element description' }
|
|
221
|
+
},
|
|
222
|
+
required: ['function']
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: 'browser_navigate',
|
|
227
|
+
description: 'Navigate to a URL.',
|
|
228
|
+
inputSchema: {
|
|
229
|
+
type: 'object',
|
|
230
|
+
properties: {
|
|
231
|
+
url: { type: 'string', description: 'URL to navigate to' }
|
|
232
|
+
},
|
|
233
|
+
required: ['url']
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: 'browser_console_messages',
|
|
238
|
+
description: 'Get console messages from the page.',
|
|
239
|
+
inputSchema: {
|
|
240
|
+
type: 'object',
|
|
241
|
+
properties: {
|
|
242
|
+
level: { type: 'string', enum: ['error', 'warning', 'info', 'debug'], description: 'Minimum log level' }
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: 'browser_resize',
|
|
248
|
+
description: 'Resize the browser window.',
|
|
249
|
+
inputSchema: {
|
|
250
|
+
type: 'object',
|
|
251
|
+
properties: {
|
|
252
|
+
width: { type: 'number', description: 'Window width in pixels' },
|
|
253
|
+
height: { type: 'number', description: 'Window height in pixels' }
|
|
254
|
+
},
|
|
255
|
+
required: ['width', 'height']
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: 'browser_wait_for',
|
|
260
|
+
description: 'Wait for text to appear or disappear, or wait for a specified time.',
|
|
261
|
+
inputSchema: {
|
|
262
|
+
type: 'object',
|
|
263
|
+
properties: {
|
|
264
|
+
text: { type: 'string', description: 'Text to wait for' },
|
|
265
|
+
textGone: { type: 'string', description: 'Text to wait for to disappear' },
|
|
266
|
+
time: { type: 'number', description: 'Time to wait in seconds' }
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
name: 'browser_fill_form',
|
|
272
|
+
description: 'Fill multiple form fields at once.',
|
|
273
|
+
inputSchema: {
|
|
274
|
+
type: 'object',
|
|
275
|
+
properties: {
|
|
276
|
+
fields: {
|
|
277
|
+
type: 'array',
|
|
278
|
+
description: 'Array of fields to fill',
|
|
279
|
+
items: {
|
|
280
|
+
type: 'object',
|
|
281
|
+
properties: {
|
|
282
|
+
ref: { type: 'string' },
|
|
283
|
+
name: { type: 'string' },
|
|
284
|
+
type: { type: 'string', enum: ['textbox', 'checkbox', 'radio', 'combobox', 'slider'] },
|
|
285
|
+
value: { type: 'string' }
|
|
286
|
+
},
|
|
287
|
+
required: ['ref', 'name', 'type', 'value']
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
required: ['fields']
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
name: 'browser_press_key',
|
|
296
|
+
description: 'Press a keyboard key.',
|
|
297
|
+
inputSchema: {
|
|
298
|
+
type: 'object',
|
|
299
|
+
properties: {
|
|
300
|
+
key: { type: 'string', description: 'Key to press (e.g., "Enter", "Tab", "a")' }
|
|
301
|
+
},
|
|
302
|
+
required: ['key']
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: 'nwjs_list_apps',
|
|
307
|
+
description: 'List all connected NW.js applications.',
|
|
308
|
+
inputSchema: {
|
|
309
|
+
type: 'object',
|
|
310
|
+
properties: {}
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
name: 'nwjs_select_app',
|
|
315
|
+
description: 'Select which NW.js app to target for browser commands.',
|
|
316
|
+
inputSchema: {
|
|
317
|
+
type: 'object',
|
|
318
|
+
properties: {
|
|
319
|
+
appId: { type: 'string', description: 'App ID to select' }
|
|
320
|
+
},
|
|
321
|
+
required: ['appId']
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
name: 'nwjs_reload',
|
|
326
|
+
description: 'Reload the NW.js app window.',
|
|
327
|
+
inputSchema: {
|
|
328
|
+
type: 'object',
|
|
329
|
+
properties: {
|
|
330
|
+
ignoreCache: { type: 'boolean', description: 'Ignore cache when reloading (like Ctrl+Shift+R)' },
|
|
331
|
+
relaunch: { type: 'boolean', description: 'Fully relaunch the app (restart the process) instead of just reloading the window' }
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
name: 'nwjs_show_devtools',
|
|
337
|
+
description: 'Show the developer tools for the NW.js app.',
|
|
338
|
+
inputSchema: {
|
|
339
|
+
type: 'object',
|
|
340
|
+
properties: {}
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
name: 'nwjs_close',
|
|
345
|
+
description: 'Close the NW.js app window.',
|
|
346
|
+
inputSchema: {
|
|
347
|
+
type: 'object',
|
|
348
|
+
properties: {}
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
name: 'nwjs_start_app',
|
|
353
|
+
description: 'Start an NW.js application. The app must include the MCP client library to connect.',
|
|
354
|
+
inputSchema: {
|
|
355
|
+
type: 'object',
|
|
356
|
+
properties: {
|
|
357
|
+
appPath: { type: 'string', description: 'Path to the NW.js app directory (containing package.json)' },
|
|
358
|
+
nwPath: { type: 'string', description: 'Path to nw.exe (optional, uses configured default if not specified)' },
|
|
359
|
+
args: { type: 'array', items: { type: 'string' }, description: 'Additional command line arguments' }
|
|
360
|
+
},
|
|
361
|
+
required: ['appPath']
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
name: 'nwjs_get_manifest',
|
|
366
|
+
description: 'Get the app manifest (package.json) information.',
|
|
367
|
+
inputSchema: {
|
|
368
|
+
type: 'object',
|
|
369
|
+
properties: {}
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
name: 'nwjs_get_argv',
|
|
374
|
+
description: 'Get command line arguments the app was started with.',
|
|
375
|
+
inputSchema: {
|
|
376
|
+
type: 'object',
|
|
377
|
+
properties: {}
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: 'nwjs_minimize',
|
|
382
|
+
description: 'Minimize the NW.js app window.',
|
|
383
|
+
inputSchema: {
|
|
384
|
+
type: 'object',
|
|
385
|
+
properties: {}
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
name: 'nwjs_maximize',
|
|
390
|
+
description: 'Maximize the NW.js app window.',
|
|
391
|
+
inputSchema: {
|
|
392
|
+
type: 'object',
|
|
393
|
+
properties: {}
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: 'nwjs_restore',
|
|
398
|
+
description: 'Restore the NW.js app window from minimized/maximized state.',
|
|
399
|
+
inputSchema: {
|
|
400
|
+
type: 'object',
|
|
401
|
+
properties: {}
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
name: 'nwjs_focus',
|
|
406
|
+
description: 'Bring the NW.js app window to the front.',
|
|
407
|
+
inputSchema: {
|
|
408
|
+
type: 'object',
|
|
409
|
+
properties: {}
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
name: 'nwjs_get_bounds',
|
|
414
|
+
description: 'Get the window position and size.',
|
|
415
|
+
inputSchema: {
|
|
416
|
+
type: 'object',
|
|
417
|
+
properties: {}
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
name: 'nwjs_set_bounds',
|
|
422
|
+
description: 'Set the window position and size.',
|
|
423
|
+
inputSchema: {
|
|
424
|
+
type: 'object',
|
|
425
|
+
properties: {
|
|
426
|
+
x: { type: 'number', description: 'Window X position' },
|
|
427
|
+
y: { type: 'number', description: 'Window Y position' },
|
|
428
|
+
width: { type: 'number', description: 'Window width' },
|
|
429
|
+
height: { type: 'number', description: 'Window height' }
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
name: 'nwjs_zoom',
|
|
435
|
+
description: 'Set the zoom level of the page.',
|
|
436
|
+
inputSchema: {
|
|
437
|
+
type: 'object',
|
|
438
|
+
properties: {
|
|
439
|
+
level: { type: 'number', description: 'Zoom level (1.0 = 100%, 1.5 = 150%, etc.)' }
|
|
440
|
+
},
|
|
441
|
+
required: ['level']
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
]
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
MCPServer.prototype._handleToolCall = function(params, callback) {
|
|
448
|
+
var self = this
|
|
449
|
+
var toolName = params.name
|
|
450
|
+
var toolArgs = params.arguments || {}
|
|
451
|
+
|
|
452
|
+
// Handle server-side tools
|
|
453
|
+
if (toolName === 'nwjs_list_apps') {
|
|
454
|
+
var apps = this.wsBridge.getConnectedApps()
|
|
455
|
+
callback({
|
|
456
|
+
content: [{
|
|
457
|
+
type: 'text',
|
|
458
|
+
text: apps.length === 0
|
|
459
|
+
? 'No NW.js apps connected. Start an NW.js app with the MCP client library.'
|
|
460
|
+
: 'Connected apps:\n' + apps.map(function(app) {
|
|
461
|
+
return '- ' + app.id + (app.name ? ' (' + app.name + ')' : '') + (app.active ? ' [active]' : '')
|
|
462
|
+
}).join('\n')
|
|
463
|
+
}]
|
|
464
|
+
})
|
|
465
|
+
return
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (toolName === 'nwjs_select_app') {
|
|
469
|
+
var success = this.wsBridge.selectApp(toolArgs.appId)
|
|
470
|
+
callback({
|
|
471
|
+
content: [{
|
|
472
|
+
type: 'text',
|
|
473
|
+
text: success
|
|
474
|
+
? 'Selected app: ' + toolArgs.appId
|
|
475
|
+
: 'App not found: ' + toolArgs.appId
|
|
476
|
+
}],
|
|
477
|
+
isError: !success
|
|
478
|
+
})
|
|
479
|
+
return
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (toolName === 'nwjs_start_app') {
|
|
483
|
+
this._startApp(toolArgs, callback)
|
|
484
|
+
return
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Proxy to NW.js app
|
|
488
|
+
this.wsBridge.callTool(toolName, toolArgs, function(err, result) {
|
|
489
|
+
if (err) {
|
|
490
|
+
callback({
|
|
491
|
+
content: [{ type: 'text', text: 'Error: ' + err.message }],
|
|
492
|
+
isError: true
|
|
493
|
+
})
|
|
494
|
+
} else {
|
|
495
|
+
callback(result)
|
|
496
|
+
}
|
|
497
|
+
})
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
MCPServer.prototype._sendResult = function(id, result) {
|
|
501
|
+
var response = {
|
|
502
|
+
jsonrpc: '2.0',
|
|
503
|
+
id: id,
|
|
504
|
+
result: result
|
|
505
|
+
}
|
|
506
|
+
process.stdout.write(JSON.stringify(response) + '\n')
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
MCPServer.prototype._sendError = function(id, code, message, data) {
|
|
510
|
+
var response = {
|
|
511
|
+
jsonrpc: '2.0',
|
|
512
|
+
id: id,
|
|
513
|
+
error: {
|
|
514
|
+
code: code,
|
|
515
|
+
message: message
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (data !== undefined) {
|
|
519
|
+
response.error.data = data
|
|
520
|
+
}
|
|
521
|
+
process.stdout.write(JSON.stringify(response) + '\n')
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
MCPServer.prototype._startApp = function(args, callback) {
|
|
525
|
+
var self = this
|
|
526
|
+
var spawn = require('child_process').spawn
|
|
527
|
+
var path = require('path')
|
|
528
|
+
var fs = require('fs')
|
|
529
|
+
|
|
530
|
+
var appPath = args.appPath
|
|
531
|
+
|
|
532
|
+
// Validate app path exists
|
|
533
|
+
if (!fs.existsSync(appPath)) {
|
|
534
|
+
callback({
|
|
535
|
+
content: [{ type: 'text', text: 'App path does not exist: ' + appPath }],
|
|
536
|
+
isError: true
|
|
537
|
+
})
|
|
538
|
+
return
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Check for package.json
|
|
542
|
+
var packageJsonPath = path.join(appPath, 'package.json')
|
|
543
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
544
|
+
callback({
|
|
545
|
+
content: [{ type: 'text', text: 'No package.json found in: ' + appPath }],
|
|
546
|
+
isError: true
|
|
547
|
+
})
|
|
548
|
+
return
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Find nwPath from multiple sources
|
|
552
|
+
var nwPath = this._findNwPath(args.nwPath, appPath)
|
|
553
|
+
|
|
554
|
+
if (!nwPath) {
|
|
555
|
+
callback({
|
|
556
|
+
content: [{
|
|
557
|
+
type: 'text',
|
|
558
|
+
text: 'NW.js executable not found. Options:\n' +
|
|
559
|
+
'1. Pass nwPath argument to this tool\n' +
|
|
560
|
+
'2. Set NWJS_PATH in .env file in app directory\n' +
|
|
561
|
+
'3. Set NWJS_PATH environment variable\n' +
|
|
562
|
+
'4. Install "nw" package in your project: npm install nw'
|
|
563
|
+
}],
|
|
564
|
+
isError: true
|
|
565
|
+
})
|
|
566
|
+
return
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Check nw.exe exists
|
|
570
|
+
if (!fs.existsSync(nwPath)) {
|
|
571
|
+
callback({
|
|
572
|
+
content: [{ type: 'text', text: 'NW.js executable not found at: ' + nwPath }],
|
|
573
|
+
isError: true
|
|
574
|
+
})
|
|
575
|
+
return
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Build arguments
|
|
579
|
+
var spawnArgs = [appPath]
|
|
580
|
+
if (args.args && Array.isArray(args.args)) {
|
|
581
|
+
spawnArgs = spawnArgs.concat(args.args)
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Spawn the app
|
|
585
|
+
var child = spawn(nwPath, spawnArgs, {
|
|
586
|
+
detached: true,
|
|
587
|
+
stdio: 'ignore',
|
|
588
|
+
cwd: appPath
|
|
589
|
+
})
|
|
590
|
+
|
|
591
|
+
child.unref()
|
|
592
|
+
|
|
593
|
+
// Track spawned app
|
|
594
|
+
self.startedApps.push({
|
|
595
|
+
pid: child.pid,
|
|
596
|
+
appPath: appPath,
|
|
597
|
+
startedAt: Date.now()
|
|
598
|
+
})
|
|
599
|
+
|
|
600
|
+
callback({
|
|
601
|
+
content: [{
|
|
602
|
+
type: 'text',
|
|
603
|
+
text: 'Started NW.js app: ' + appPath + '\nPID: ' + child.pid + '\nWaiting for app to connect...'
|
|
604
|
+
}]
|
|
605
|
+
})
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Parse a .env file and return key-value pairs
|
|
610
|
+
* @param {string} envFilePath - Path to .env file
|
|
611
|
+
* @returns {Object}
|
|
612
|
+
*/
|
|
613
|
+
MCPServer.prototype._parseEnvFile = function(envFilePath) {
|
|
614
|
+
var fs = require('fs')
|
|
615
|
+
var result = {}
|
|
616
|
+
|
|
617
|
+
if (!fs.existsSync(envFilePath)) {
|
|
618
|
+
return result
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
try {
|
|
622
|
+
var content = fs.readFileSync(envFilePath, 'utf8')
|
|
623
|
+
var lines = content.split('\n')
|
|
624
|
+
|
|
625
|
+
for (var i = 0; i < lines.length; i++) {
|
|
626
|
+
var line = lines[i].trim()
|
|
627
|
+
|
|
628
|
+
// Skip empty lines and comments
|
|
629
|
+
if (!line || line.charAt(0) === '#') {
|
|
630
|
+
continue
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
var eqIndex = line.indexOf('=')
|
|
634
|
+
if (eqIndex === -1) {
|
|
635
|
+
continue
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
var key = line.substring(0, eqIndex).trim()
|
|
639
|
+
var value = line.substring(eqIndex + 1).trim()
|
|
640
|
+
|
|
641
|
+
// Remove surrounding quotes if present
|
|
642
|
+
if ((value.charAt(0) === '"' && value.charAt(value.length - 1) === '"') ||
|
|
643
|
+
(value.charAt(0) === "'" && value.charAt(value.length - 1) === "'")) {
|
|
644
|
+
value = value.substring(1, value.length - 1)
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
result[key] = value
|
|
648
|
+
}
|
|
649
|
+
} catch (e) {
|
|
650
|
+
// Failed to read/parse .env file
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return result
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Find NW.js executable path from multiple sources
|
|
658
|
+
* @param {string} explicitPath - Path passed as argument
|
|
659
|
+
* @param {string} appPath - App directory to check for nw package
|
|
660
|
+
* @returns {string|null}
|
|
661
|
+
*/
|
|
662
|
+
MCPServer.prototype._findNwPath = function(explicitPath, appPath) {
|
|
663
|
+
var fs = require('fs')
|
|
664
|
+
var path = require('path')
|
|
665
|
+
|
|
666
|
+
// 1. Explicit path from argument
|
|
667
|
+
if (explicitPath && fs.existsSync(explicitPath)) {
|
|
668
|
+
return explicitPath
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// 2. Server configured path
|
|
672
|
+
if (this.nwPath && fs.existsSync(this.nwPath)) {
|
|
673
|
+
return this.nwPath
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// 3. Environment variable
|
|
677
|
+
var envPath = process.env.NWJS_PATH
|
|
678
|
+
if (envPath && fs.existsSync(envPath)) {
|
|
679
|
+
return envPath
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// 4. Check .env file in app directory
|
|
683
|
+
var envFile = this._parseEnvFile(path.join(appPath, '.env'))
|
|
684
|
+
if (envFile.NWJS_PATH && fs.existsSync(envFile.NWJS_PATH)) {
|
|
685
|
+
return envFile.NWJS_PATH
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// 5. Try to find from 'nw' package in app's node_modules
|
|
689
|
+
var nwPackagePath = path.join(appPath, 'node_modules', 'nw')
|
|
690
|
+
if (fs.existsSync(nwPackagePath)) {
|
|
691
|
+
try {
|
|
692
|
+
// The nw package has a findpath module
|
|
693
|
+
var findpath = require(path.join(nwPackagePath, 'lib', 'findpath'))
|
|
694
|
+
var foundPath = findpath()
|
|
695
|
+
if (foundPath && fs.existsSync(foundPath)) {
|
|
696
|
+
return foundPath
|
|
697
|
+
}
|
|
698
|
+
} catch (e) {
|
|
699
|
+
// findpath module not available or failed
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// 6. Check common locations on Windows
|
|
704
|
+
if (process.platform === 'win32') {
|
|
705
|
+
var commonPaths = [
|
|
706
|
+
path.join(process.env.LOCALAPPDATA || '', 'nw'),
|
|
707
|
+
path.join(process.env.PROGRAMFILES || '', 'nw'),
|
|
708
|
+
'C:\\nw'
|
|
709
|
+
]
|
|
710
|
+
|
|
711
|
+
for (var i = 0; i < commonPaths.length; i++) {
|
|
712
|
+
var nwExe = path.join(commonPaths[i], 'nw.exe')
|
|
713
|
+
if (fs.existsSync(nwExe)) {
|
|
714
|
+
return nwExe
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return null
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
module.exports = MCPServer
|