@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.
@@ -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
@@ -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
+ }