@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,212 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WebSocket Bridge for NW.js Apps
|
|
5
|
+
*
|
|
6
|
+
* Manages WebSocket connections from NW.js apps and routes tool calls to them.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
var WebSocket = require('ws')
|
|
10
|
+
|
|
11
|
+
function WebSocketBridge(port) {
|
|
12
|
+
this.port = port
|
|
13
|
+
this.server = null
|
|
14
|
+
this.apps = {} // appId -> { ws, name, active }
|
|
15
|
+
this.activeAppId = null
|
|
16
|
+
this.pendingCalls = {} // callId -> { callback, timeout }
|
|
17
|
+
this.callIdCounter = 0
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
WebSocketBridge.prototype.start = function() {
|
|
21
|
+
var self = this
|
|
22
|
+
|
|
23
|
+
this.server = new WebSocket.Server({ port: this.port })
|
|
24
|
+
|
|
25
|
+
this.server.on('listening', function() {
|
|
26
|
+
process.stderr.write('[nwjs-mcp] WebSocket server listening on port ' + self.port + '\n')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
this.server.on('connection', function(ws) {
|
|
30
|
+
self._handleConnection(ws)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
this.server.on('error', function(err) {
|
|
34
|
+
process.stderr.write('[nwjs-mcp] WebSocket server error: ' + err.message + '\n')
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
WebSocketBridge.prototype.stop = function() {
|
|
39
|
+
if (this.server) {
|
|
40
|
+
this.server.close()
|
|
41
|
+
this.server = null
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
WebSocketBridge.prototype._handleConnection = function(ws) {
|
|
46
|
+
var self = this
|
|
47
|
+
var appId = null
|
|
48
|
+
|
|
49
|
+
ws.on('message', function(data) {
|
|
50
|
+
var message = null
|
|
51
|
+
try {
|
|
52
|
+
message = JSON.parse(data)
|
|
53
|
+
} catch (e) {
|
|
54
|
+
process.stderr.write('[nwjs-mcp] Invalid message from app: ' + e.message + '\n')
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
self._handleMessage(ws, message, function(assignedId) {
|
|
59
|
+
if (assignedId) {
|
|
60
|
+
appId = assignedId
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
ws.on('close', function() {
|
|
66
|
+
if (appId && self.apps[appId]) {
|
|
67
|
+
process.stderr.write('[nwjs-mcp] App disconnected: ' + appId + '\n')
|
|
68
|
+
delete self.apps[appId]
|
|
69
|
+
|
|
70
|
+
// If this was the active app, clear selection
|
|
71
|
+
if (self.activeAppId === appId) {
|
|
72
|
+
self.activeAppId = null
|
|
73
|
+
// Auto-select another app if available
|
|
74
|
+
var appIds = Object.keys(self.apps)
|
|
75
|
+
if (appIds.length > 0) {
|
|
76
|
+
self.activeAppId = appIds[0]
|
|
77
|
+
self.apps[self.activeAppId].active = true
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
ws.on('error', function(err) {
|
|
84
|
+
process.stderr.write('[nwjs-mcp] WebSocket error: ' + err.message + '\n')
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
WebSocketBridge.prototype._handleMessage = function(ws, message, onRegister) {
|
|
89
|
+
var self = this
|
|
90
|
+
|
|
91
|
+
// Handle registration
|
|
92
|
+
if (message.type === 'register') {
|
|
93
|
+
var appId = message.appId || 'app-' + Date.now()
|
|
94
|
+
var appName = message.name || ''
|
|
95
|
+
|
|
96
|
+
// If app already exists (reconnect), update the WebSocket
|
|
97
|
+
if (this.apps[appId]) {
|
|
98
|
+
this.apps[appId].ws = ws
|
|
99
|
+
process.stderr.write('[nwjs-mcp] App reconnected: ' + appId + '\n')
|
|
100
|
+
} else {
|
|
101
|
+
this.apps[appId] = {
|
|
102
|
+
ws: ws,
|
|
103
|
+
name: appName,
|
|
104
|
+
active: false
|
|
105
|
+
}
|
|
106
|
+
process.stderr.write('[nwjs-mcp] App registered: ' + appId + (appName ? ' (' + appName + ')' : '') + '\n')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Auto-select first app
|
|
110
|
+
if (!this.activeAppId) {
|
|
111
|
+
this.activeAppId = appId
|
|
112
|
+
this.apps[appId].active = true
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Send confirmation
|
|
116
|
+
ws.send(JSON.stringify({
|
|
117
|
+
type: 'registered',
|
|
118
|
+
appId: appId
|
|
119
|
+
}))
|
|
120
|
+
|
|
121
|
+
onRegister(appId)
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Handle tool response
|
|
126
|
+
if (message.type === 'toolResult') {
|
|
127
|
+
var callId = message.callId
|
|
128
|
+
var pending = this.pendingCalls[callId]
|
|
129
|
+
|
|
130
|
+
if (pending) {
|
|
131
|
+
clearTimeout(pending.timeout)
|
|
132
|
+
delete this.pendingCalls[callId]
|
|
133
|
+
|
|
134
|
+
if (message.error) {
|
|
135
|
+
pending.callback(new Error(message.error))
|
|
136
|
+
} else {
|
|
137
|
+
pending.callback(null, message.result)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
WebSocketBridge.prototype.getConnectedApps = function() {
|
|
145
|
+
var self = this
|
|
146
|
+
return Object.keys(this.apps).map(function(appId) {
|
|
147
|
+
return {
|
|
148
|
+
id: appId,
|
|
149
|
+
name: self.apps[appId].name,
|
|
150
|
+
active: appId === self.activeAppId
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
WebSocketBridge.prototype.selectApp = function(appId) {
|
|
156
|
+
if (!this.apps[appId]) {
|
|
157
|
+
return false
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Deactivate current
|
|
161
|
+
if (this.activeAppId && this.apps[this.activeAppId]) {
|
|
162
|
+
this.apps[this.activeAppId].active = false
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Activate new
|
|
166
|
+
this.activeAppId = appId
|
|
167
|
+
this.apps[appId].active = true
|
|
168
|
+
return true
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
WebSocketBridge.prototype.callTool = function(toolName, toolArgs, callback) {
|
|
172
|
+
var self = this
|
|
173
|
+
|
|
174
|
+
// Get active app
|
|
175
|
+
if (!this.activeAppId || !this.apps[this.activeAppId]) {
|
|
176
|
+
callback(new Error('No NW.js app connected. Start an NW.js app with the MCP client library.'))
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
var app = this.apps[this.activeAppId]
|
|
181
|
+
var ws = app.ws
|
|
182
|
+
|
|
183
|
+
if (ws.readyState !== WebSocket.OPEN) {
|
|
184
|
+
callback(new Error('App connection not ready'))
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Generate call ID
|
|
189
|
+
var callId = 'call-' + (++this.callIdCounter)
|
|
190
|
+
|
|
191
|
+
// Set timeout
|
|
192
|
+
var timeout = setTimeout(function() {
|
|
193
|
+
delete self.pendingCalls[callId]
|
|
194
|
+
callback(new Error('Tool call timed out'))
|
|
195
|
+
}, 30000)
|
|
196
|
+
|
|
197
|
+
// Store pending call
|
|
198
|
+
this.pendingCalls[callId] = {
|
|
199
|
+
callback: callback,
|
|
200
|
+
timeout: timeout
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Send to app
|
|
204
|
+
ws.send(JSON.stringify({
|
|
205
|
+
type: 'toolCall',
|
|
206
|
+
callId: callId,
|
|
207
|
+
tool: toolName,
|
|
208
|
+
args: toolArgs
|
|
209
|
+
}))
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
module.exports = WebSocketBridge
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NW.js MCP Server Type Definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface MCPServerOptions {
|
|
6
|
+
/** Server mode: stdio for command-line integration, http for network access */
|
|
7
|
+
mode?: 'stdio' | 'http'
|
|
8
|
+
/** HTTP port (only used in http mode) */
|
|
9
|
+
port?: number
|
|
10
|
+
/** Whether to try loading native addon for enhanced features */
|
|
11
|
+
native?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface MCPServer {
|
|
15
|
+
/** Start the server */
|
|
16
|
+
start(): void
|
|
17
|
+
/** Stop the server */
|
|
18
|
+
stop(): void
|
|
19
|
+
/** Whether the server is running */
|
|
20
|
+
running: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Start the MCP server
|
|
25
|
+
* @param options Server configuration options
|
|
26
|
+
* @returns The server instance
|
|
27
|
+
*/
|
|
28
|
+
export function startServer(options?: MCPServerOptions): MCPServer
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create MCP server instance without starting
|
|
32
|
+
* @param options Server configuration options
|
|
33
|
+
* @returns The server instance
|
|
34
|
+
*/
|
|
35
|
+
export function createServer(options?: MCPServerOptions): MCPServer
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* MCP Server class
|
|
39
|
+
*/
|
|
40
|
+
export { MCPServer }
|
|
41
|
+
|
|
42
|
+
// Tool types for reference
|
|
43
|
+
export interface MCPToolResult {
|
|
44
|
+
content: Array<{
|
|
45
|
+
type: 'text' | 'image'
|
|
46
|
+
text?: string
|
|
47
|
+
data?: string
|
|
48
|
+
mimeType?: string
|
|
49
|
+
}>
|
|
50
|
+
isError?: boolean
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface MCPTool {
|
|
54
|
+
name: string
|
|
55
|
+
description: string
|
|
56
|
+
inputSchema: object
|
|
57
|
+
execute(args: object, callback: (err: Error | null, result?: MCPToolResult) => void): void
|
|
58
|
+
}
|