@blaxel/core 0.2.2-preview2 → 0.2.2
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/dist/common/node.js +2 -7
- package/dist/sandbox/action.d.ts +1 -0
- package/dist/sandbox/action.js +40 -0
- package/dist/sandbox/filesystem.d.ts +20 -0
- package/dist/sandbox/filesystem.js +124 -0
- package/dist/sandbox/process.d.ts +15 -0
- package/dist/sandbox/process.js +113 -0
- package/package.json +1 -1
- package/dist/mcp/server/http.d.ts +0 -8
- package/dist/mcp/server/http.js +0 -16
- package/dist/mcp/server/index.d.ts +0 -4
- package/dist/mcp/server/index.js +0 -21
- package/dist/mcp/server/websocket.d.ts +0 -24
- package/dist/mcp/server/websocket.js +0 -213
package/dist/common/node.js
CHANGED
|
@@ -10,11 +10,6 @@ exports.fs = fs;
|
|
|
10
10
|
let os = null;
|
|
11
11
|
exports.os = os;
|
|
12
12
|
if (isNode) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
exports.os = os = eval("require")("os");
|
|
16
|
-
}
|
|
17
|
-
catch (e) {
|
|
18
|
-
console.warn("fs and os are not available in this environment");
|
|
19
|
-
}
|
|
13
|
+
exports.fs = fs = eval("require")("fs");
|
|
14
|
+
exports.os = os = eval("require")("os");
|
|
20
15
|
}
|
package/dist/sandbox/action.d.ts
CHANGED
package/dist/sandbox/action.js
CHANGED
|
@@ -84,5 +84,45 @@ class SandboxAction {
|
|
|
84
84
|
throw new ResponseError(response, data, error);
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
+
websocket(path) {
|
|
88
|
+
let ws = null;
|
|
89
|
+
// Build ws:// or wss:// URL from baseUrl
|
|
90
|
+
let baseUrl = this.url.replace(/^http/, 'ws');
|
|
91
|
+
if (baseUrl.endsWith('/'))
|
|
92
|
+
baseUrl = baseUrl.slice(0, -1);
|
|
93
|
+
let params = `token=${settings_js_1.settings.token}`;
|
|
94
|
+
if (this.sandbox.params) {
|
|
95
|
+
params = "";
|
|
96
|
+
for (const [key, value] of Object.entries(this.sandbox.params)) {
|
|
97
|
+
params += `${key}=${value}&`;
|
|
98
|
+
}
|
|
99
|
+
params = params.slice(0, -1);
|
|
100
|
+
}
|
|
101
|
+
const wsUrl = `${baseUrl}/ws/${path}?${params}`;
|
|
102
|
+
// Use isomorphic WebSocket: browser or Node.js
|
|
103
|
+
let WS = undefined;
|
|
104
|
+
if (typeof globalThis.WebSocket !== 'undefined') {
|
|
105
|
+
WS = globalThis.WebSocket;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
try {
|
|
109
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
110
|
+
WS = require('ws');
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
WS = undefined;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (!WS)
|
|
117
|
+
throw new Error('WebSocket is not available in this environment');
|
|
118
|
+
try {
|
|
119
|
+
ws = typeof WS === 'function' ? new WS(wsUrl) : new WS(wsUrl);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
console.error('WebSocket connection error:', err);
|
|
123
|
+
throw err;
|
|
124
|
+
}
|
|
125
|
+
return ws;
|
|
126
|
+
}
|
|
87
127
|
}
|
|
88
128
|
exports.SandboxAction = SandboxAction;
|
|
@@ -14,7 +14,27 @@ export declare class SandboxFileSystem extends SandboxAction {
|
|
|
14
14
|
rm(path: string, recursive?: boolean): Promise<SuccessResponse>;
|
|
15
15
|
ls(path: string): Promise<Directory>;
|
|
16
16
|
cp(source: string, destination: string): Promise<CopyResponse>;
|
|
17
|
+
/**
|
|
18
|
+
* Watch for changes in a directory. Calls the callback with the changed file path (and optionally its content).
|
|
19
|
+
* Returns a handle with a close() method to stop watching.
|
|
20
|
+
* @param path Directory to watch
|
|
21
|
+
* @param callback Function called on each change: (filePath, content?)
|
|
22
|
+
* @param withContent If true, also fetches and passes the file content (default: false)
|
|
23
|
+
*/
|
|
17
24
|
watch(path: string, callback: (filePath: string, content?: string) => void | Promise<void>, options?: {
|
|
25
|
+
ws?: boolean;
|
|
26
|
+
onError?: (error: Error) => void;
|
|
27
|
+
withContent: boolean;
|
|
28
|
+
}): {
|
|
29
|
+
close: () => void;
|
|
30
|
+
};
|
|
31
|
+
wsWatch(path: string, callback: (filePath: string, content?: string) => void | Promise<void>, options?: {
|
|
32
|
+
onError?: (error: Error) => void;
|
|
33
|
+
withContent: boolean;
|
|
34
|
+
}): {
|
|
35
|
+
close: () => void;
|
|
36
|
+
};
|
|
37
|
+
sseWatch(path: string, callback: (filePath: string, content?: string) => void | Promise<void>, options?: {
|
|
18
38
|
onError?: (error: Error) => void;
|
|
19
39
|
withContent: boolean;
|
|
20
40
|
}): {
|
|
@@ -117,7 +117,131 @@ class SandboxFileSystem extends action_js_1.SandboxAction {
|
|
|
117
117
|
}
|
|
118
118
|
throw new Error("Unsupported file type");
|
|
119
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Watch for changes in a directory. Calls the callback with the changed file path (and optionally its content).
|
|
122
|
+
* Returns a handle with a close() method to stop watching.
|
|
123
|
+
* @param path Directory to watch
|
|
124
|
+
* @param callback Function called on each change: (filePath, content?)
|
|
125
|
+
* @param withContent If true, also fetches and passes the file content (default: false)
|
|
126
|
+
*/
|
|
120
127
|
watch(path, callback, options) {
|
|
128
|
+
if (options?.ws) {
|
|
129
|
+
return this.wsWatch(path, callback, options);
|
|
130
|
+
}
|
|
131
|
+
return this.sseWatch(path, callback, options);
|
|
132
|
+
}
|
|
133
|
+
wsWatch(path, callback, options) {
|
|
134
|
+
path = this.formatPath(path);
|
|
135
|
+
let closed = false;
|
|
136
|
+
let ws = this.websocket(`watch/filesystem${path.startsWith('/') ? path : '/' + path}`);
|
|
137
|
+
let pingInterval = null;
|
|
138
|
+
let pongTimeout = null;
|
|
139
|
+
const PING_INTERVAL_MS = 30000;
|
|
140
|
+
const PONG_TIMEOUT_MS = 10000;
|
|
141
|
+
function sendPing() {
|
|
142
|
+
if (ws && ws.readyState === ws.OPEN) {
|
|
143
|
+
try {
|
|
144
|
+
ws.send(JSON.stringify({ type: 'ping' }));
|
|
145
|
+
}
|
|
146
|
+
catch { }
|
|
147
|
+
// Set pong timeout
|
|
148
|
+
if (pongTimeout)
|
|
149
|
+
clearTimeout(pongTimeout);
|
|
150
|
+
pongTimeout = setTimeout(() => {
|
|
151
|
+
// No pong received in time, close connection
|
|
152
|
+
if (ws && typeof ws.close === 'function')
|
|
153
|
+
ws.close();
|
|
154
|
+
}, PONG_TIMEOUT_MS);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (ws) {
|
|
158
|
+
ws.onmessage = async (event) => {
|
|
159
|
+
if (closed)
|
|
160
|
+
return;
|
|
161
|
+
let data;
|
|
162
|
+
try {
|
|
163
|
+
data = typeof event.data === 'string' ? event.data : event.data;
|
|
164
|
+
if (!data)
|
|
165
|
+
return;
|
|
166
|
+
// Accept both JSON and plain string (file path)
|
|
167
|
+
let payload;
|
|
168
|
+
try {
|
|
169
|
+
payload = JSON.parse(data);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
payload = { name: data, event: undefined };
|
|
173
|
+
}
|
|
174
|
+
// Handle ping/pong
|
|
175
|
+
if (payload.type === 'ping') {
|
|
176
|
+
// Respond to ping with pong
|
|
177
|
+
if (ws && ws.readyState === ws.OPEN) {
|
|
178
|
+
try {
|
|
179
|
+
ws.send(JSON.stringify({ type: 'pong' }));
|
|
180
|
+
}
|
|
181
|
+
catch { }
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (payload.type === 'pong') {
|
|
186
|
+
// Pong received, clear pong timeout
|
|
187
|
+
if (pongTimeout)
|
|
188
|
+
clearTimeout(pongTimeout);
|
|
189
|
+
pongTimeout = null;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const filePath = payload.name || payload.path || data;
|
|
193
|
+
if (!filePath)
|
|
194
|
+
return;
|
|
195
|
+
if (options?.withContent) {
|
|
196
|
+
try {
|
|
197
|
+
const content = await this.read(filePath);
|
|
198
|
+
await callback(filePath, content);
|
|
199
|
+
}
|
|
200
|
+
catch (e) {
|
|
201
|
+
await callback(filePath, undefined);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
await callback(filePath);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
if (options?.onError)
|
|
210
|
+
options.onError(err);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
ws.onerror = (err) => {
|
|
214
|
+
if (options?.onError)
|
|
215
|
+
options.onError(err instanceof Error ? err : new Error(String(err)));
|
|
216
|
+
closed = true;
|
|
217
|
+
if (ws && typeof ws.close === 'function')
|
|
218
|
+
ws.close();
|
|
219
|
+
};
|
|
220
|
+
ws.onclose = () => {
|
|
221
|
+
closed = true;
|
|
222
|
+
ws = null;
|
|
223
|
+
if (pingInterval)
|
|
224
|
+
clearInterval(pingInterval);
|
|
225
|
+
if (pongTimeout)
|
|
226
|
+
clearTimeout(pongTimeout);
|
|
227
|
+
};
|
|
228
|
+
// Start ping interval
|
|
229
|
+
pingInterval = setInterval(sendPing, PING_INTERVAL_MS);
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
close: () => {
|
|
233
|
+
closed = true;
|
|
234
|
+
if (ws && typeof ws.close === 'function')
|
|
235
|
+
ws.close();
|
|
236
|
+
ws = null;
|
|
237
|
+
if (pingInterval)
|
|
238
|
+
clearInterval(pingInterval);
|
|
239
|
+
if (pongTimeout)
|
|
240
|
+
clearTimeout(pongTimeout);
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
sseWatch(path, callback, options) {
|
|
121
245
|
path = this.formatPath(path);
|
|
122
246
|
let closed = false;
|
|
123
247
|
let controller = new AbortController();
|
|
@@ -4,6 +4,21 @@ import { DeleteProcessByIdentifierKillResponse, DeleteProcessByIdentifierRespons
|
|
|
4
4
|
export declare class SandboxProcess extends SandboxAction {
|
|
5
5
|
constructor(sandbox: Sandbox);
|
|
6
6
|
streamLogs(identifier: string, options: {
|
|
7
|
+
ws?: boolean;
|
|
8
|
+
onLog?: (log: string) => void;
|
|
9
|
+
onStdout?: (stdout: string) => void;
|
|
10
|
+
onStderr?: (stderr: string) => void;
|
|
11
|
+
}): {
|
|
12
|
+
close: () => void;
|
|
13
|
+
};
|
|
14
|
+
wsStreamLogs(identifier: string, options: {
|
|
15
|
+
onLog?: (log: string) => void;
|
|
16
|
+
onStdout?: (stdout: string) => void;
|
|
17
|
+
onStderr?: (stderr: string) => void;
|
|
18
|
+
}): {
|
|
19
|
+
close: () => void;
|
|
20
|
+
};
|
|
21
|
+
sseStreamLogs(identifier: string, options: {
|
|
7
22
|
onLog?: (log: string) => void;
|
|
8
23
|
onStdout?: (stdout: string) => void;
|
|
9
24
|
onStderr?: (stderr: string) => void;
|
package/dist/sandbox/process.js
CHANGED
|
@@ -9,6 +9,119 @@ class SandboxProcess extends action_js_1.SandboxAction {
|
|
|
9
9
|
super(sandbox);
|
|
10
10
|
}
|
|
11
11
|
streamLogs(identifier, options) {
|
|
12
|
+
if (options.ws) {
|
|
13
|
+
return this.wsStreamLogs(identifier, options);
|
|
14
|
+
}
|
|
15
|
+
return this.sseStreamLogs(identifier, options);
|
|
16
|
+
}
|
|
17
|
+
wsStreamLogs(identifier, options) {
|
|
18
|
+
let closed = false;
|
|
19
|
+
let ws = this.websocket(`process/${identifier}/logs/stream`);
|
|
20
|
+
let pingInterval = null;
|
|
21
|
+
let pongTimeout = null;
|
|
22
|
+
const PING_INTERVAL_MS = 30000;
|
|
23
|
+
const PONG_TIMEOUT_MS = 10000;
|
|
24
|
+
function sendPing() {
|
|
25
|
+
if (ws && ws.readyState === ws.OPEN) {
|
|
26
|
+
try {
|
|
27
|
+
ws.send(JSON.stringify({ type: 'ping' }));
|
|
28
|
+
}
|
|
29
|
+
catch { }
|
|
30
|
+
// Set pong timeout
|
|
31
|
+
if (pongTimeout)
|
|
32
|
+
clearTimeout(pongTimeout);
|
|
33
|
+
pongTimeout = setTimeout(() => {
|
|
34
|
+
// No pong received in time, close connection
|
|
35
|
+
if (ws && typeof ws.close === 'function')
|
|
36
|
+
ws.close();
|
|
37
|
+
}, PONG_TIMEOUT_MS);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (ws) {
|
|
41
|
+
ws.onmessage = (event) => {
|
|
42
|
+
if (closed)
|
|
43
|
+
return;
|
|
44
|
+
let data;
|
|
45
|
+
try {
|
|
46
|
+
data = typeof event.data === 'string' ? event.data : event.data;
|
|
47
|
+
if (!data)
|
|
48
|
+
return;
|
|
49
|
+
let payload;
|
|
50
|
+
try {
|
|
51
|
+
payload = JSON.parse(data);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
payload = { log: data };
|
|
55
|
+
}
|
|
56
|
+
// Handle ping/pong
|
|
57
|
+
if (payload.type === 'ping') {
|
|
58
|
+
// Respond to ping with pong
|
|
59
|
+
if (ws && ws.readyState === ws.OPEN) {
|
|
60
|
+
try {
|
|
61
|
+
ws.send(JSON.stringify({ type: 'pong' }));
|
|
62
|
+
}
|
|
63
|
+
catch { }
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (payload.type === 'pong') {
|
|
68
|
+
// Pong received, clear pong timeout
|
|
69
|
+
if (pongTimeout)
|
|
70
|
+
clearTimeout(pongTimeout);
|
|
71
|
+
pongTimeout = null;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (payload.type === 'log') {
|
|
75
|
+
const logLine = payload.log || "";
|
|
76
|
+
if (typeof logLine === 'string') {
|
|
77
|
+
if (logLine.startsWith('stdout:')) {
|
|
78
|
+
options.onStdout?.(logLine.slice(7));
|
|
79
|
+
options.onLog?.(logLine.slice(7));
|
|
80
|
+
}
|
|
81
|
+
else if (logLine.startsWith('stderr:')) {
|
|
82
|
+
options.onStderr?.(logLine.slice(7));
|
|
83
|
+
options.onLog?.(logLine.slice(7));
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
options.onLog?.(logLine);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
console.error('WebSocket log stream error:', err);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
ws.onerror = (err) => {
|
|
96
|
+
closed = true;
|
|
97
|
+
if (ws && typeof ws.close === 'function')
|
|
98
|
+
ws.close();
|
|
99
|
+
};
|
|
100
|
+
ws.onclose = () => {
|
|
101
|
+
closed = true;
|
|
102
|
+
ws = null;
|
|
103
|
+
if (pingInterval)
|
|
104
|
+
clearInterval(pingInterval);
|
|
105
|
+
if (pongTimeout)
|
|
106
|
+
clearTimeout(pongTimeout);
|
|
107
|
+
};
|
|
108
|
+
// Start ping interval
|
|
109
|
+
pingInterval = setInterval(sendPing, PING_INTERVAL_MS);
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
close: () => {
|
|
113
|
+
closed = true;
|
|
114
|
+
if (ws && typeof ws.close === 'function')
|
|
115
|
+
ws.close();
|
|
116
|
+
ws = null;
|
|
117
|
+
if (pingInterval)
|
|
118
|
+
clearInterval(pingInterval);
|
|
119
|
+
if (pongTimeout)
|
|
120
|
+
clearTimeout(pongTimeout);
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
sseStreamLogs(identifier, options) {
|
|
12
125
|
const controller = new AbortController();
|
|
13
126
|
(async () => {
|
|
14
127
|
try {
|
package/package.json
CHANGED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
2
|
-
import { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
-
export declare class BlaxelHttpMcpServerTransport implements Transport {
|
|
4
|
-
private server;
|
|
5
|
-
constructor(port: number);
|
|
6
|
-
start(): Promise<void>;
|
|
7
|
-
send(msg: JSONRPCMessage): Promise<void>;
|
|
8
|
-
}
|
package/dist/mcp/server/http.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.BlaxelHttpMcpServerTransport = void 0;
|
|
4
|
-
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
5
|
-
class BlaxelHttpMcpServerTransport {
|
|
6
|
-
server;
|
|
7
|
-
constructor(port) {
|
|
8
|
-
this.server = new streamableHttp_js_1.StreamableHTTPServerTransport({ port });
|
|
9
|
-
}
|
|
10
|
-
async start() {
|
|
11
|
-
await this.server.start();
|
|
12
|
-
}
|
|
13
|
-
async send(msg) {
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
exports.BlaxelHttpMcpServerTransport = BlaxelHttpMcpServerTransport;
|
package/dist/mcp/server/index.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.BlaxelMcpServerTransport = void 0;
|
|
18
|
-
__exportStar(require("./http.js"), exports);
|
|
19
|
-
__exportStar(require("./websocket.js"), exports);
|
|
20
|
-
const websocket_js_1 = require("./websocket.js");
|
|
21
|
-
exports.BlaxelMcpServerTransport = websocket_js_1.BlaxelWebsocketMcpServerTransport;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
2
|
-
interface JSONRPCMessage {
|
|
3
|
-
jsonrpc: "2.0";
|
|
4
|
-
id?: string | number;
|
|
5
|
-
method?: string;
|
|
6
|
-
params?: Record<string, unknown>;
|
|
7
|
-
}
|
|
8
|
-
export declare class BlaxelWebsocketMcpServerTransport implements Transport {
|
|
9
|
-
private port;
|
|
10
|
-
private wss;
|
|
11
|
-
private clients;
|
|
12
|
-
onclose?: () => void;
|
|
13
|
-
onerror?: (err: Error) => void;
|
|
14
|
-
private messageHandler?;
|
|
15
|
-
onconnection?: (clientId: string) => void;
|
|
16
|
-
ondisconnection?: (clientId: string) => void;
|
|
17
|
-
set onmessage(handler: ((message: JSONRPCMessage) => void) | undefined);
|
|
18
|
-
constructor(port?: number);
|
|
19
|
-
start(): Promise<void>;
|
|
20
|
-
send(msg: JSONRPCMessage): Promise<void>;
|
|
21
|
-
broadcast(msg: JSONRPCMessage): Promise<void>;
|
|
22
|
-
close(): Promise<void>;
|
|
23
|
-
}
|
|
24
|
-
export {};
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.BlaxelWebsocketMcpServerTransport = void 0;
|
|
37
|
-
const uuid_1 = require("uuid");
|
|
38
|
-
const ws_1 = __importStar(require("ws"));
|
|
39
|
-
const env_js_1 = require("../../common/env.js");
|
|
40
|
-
const logger_js_1 = require("../../common/logger.js");
|
|
41
|
-
const telemetry_js_1 = require("../../telemetry/telemetry.js");
|
|
42
|
-
const spans = new Map();
|
|
43
|
-
class BlaxelWebsocketMcpServerTransport {
|
|
44
|
-
port;
|
|
45
|
-
wss;
|
|
46
|
-
clients = new Map();
|
|
47
|
-
onclose;
|
|
48
|
-
onerror;
|
|
49
|
-
messageHandler;
|
|
50
|
-
onconnection;
|
|
51
|
-
ondisconnection;
|
|
52
|
-
set onmessage(handler) {
|
|
53
|
-
this.messageHandler = handler
|
|
54
|
-
? (msg, clientId) => {
|
|
55
|
-
if (!("id" in msg)) {
|
|
56
|
-
return handler(msg);
|
|
57
|
-
}
|
|
58
|
-
return handler({
|
|
59
|
-
...msg,
|
|
60
|
-
id: clientId + ":" + msg.id,
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
: undefined;
|
|
64
|
-
}
|
|
65
|
-
constructor(port) {
|
|
66
|
-
this.port = port ?? parseInt(env_js_1.env.BL_SERVER_PORT ?? "8080", 10);
|
|
67
|
-
this.wss = new ws_1.WebSocketServer({ port: this.port });
|
|
68
|
-
}
|
|
69
|
-
async start() {
|
|
70
|
-
logger_js_1.logger.info("Starting WebSocket Server on port " + this.port);
|
|
71
|
-
this.wss.on("connection", (ws) => {
|
|
72
|
-
const clientId = (0, uuid_1.v4)();
|
|
73
|
-
this.clients.set(clientId, {
|
|
74
|
-
ws,
|
|
75
|
-
});
|
|
76
|
-
this.onconnection?.(clientId);
|
|
77
|
-
ws.on("message", (data) => {
|
|
78
|
-
const span = (0, telemetry_js_1.startSpan)("message", {
|
|
79
|
-
attributes: {
|
|
80
|
-
"mcp.client.id": clientId,
|
|
81
|
-
"span.type": "mcp.message",
|
|
82
|
-
},
|
|
83
|
-
isRoot: false,
|
|
84
|
-
});
|
|
85
|
-
try {
|
|
86
|
-
const msg = JSON.parse(data.toString());
|
|
87
|
-
this.messageHandler?.(msg, clientId);
|
|
88
|
-
if ("method" in msg && "id" in msg && "params" in msg) {
|
|
89
|
-
span.setAttributes({
|
|
90
|
-
"mcp.message.parsed": true,
|
|
91
|
-
"mcp.method": msg.method,
|
|
92
|
-
"mcp.messageId": msg.id,
|
|
93
|
-
"mcp.toolName": msg.params?.name,
|
|
94
|
-
});
|
|
95
|
-
spans.set(clientId + ":" + msg.id, span);
|
|
96
|
-
}
|
|
97
|
-
// Handle msg.id safely
|
|
98
|
-
const msgId = msg.id ? String(msg.id) : "";
|
|
99
|
-
const [cId, parsedMsgId] = msgId.split(":");
|
|
100
|
-
msg.id = parsedMsgId ? parseInt(parsedMsgId) : undefined;
|
|
101
|
-
// Use optional chaining for safe access
|
|
102
|
-
const client = this.clients.get(cId ?? "");
|
|
103
|
-
if (client?.ws?.readyState === ws_1.default.OPEN) {
|
|
104
|
-
const msgSpan = spans.get(cId + ":" + (msg.id ?? ""));
|
|
105
|
-
try {
|
|
106
|
-
client.ws.send(JSON.stringify(msg));
|
|
107
|
-
if (msgSpan) {
|
|
108
|
-
msgSpan.setAttributes({
|
|
109
|
-
"mcp.message.response_sent": true,
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
catch (err) {
|
|
114
|
-
if (msgSpan) {
|
|
115
|
-
msgSpan.setStatus("error"); // Error status
|
|
116
|
-
msgSpan.recordException(err);
|
|
117
|
-
}
|
|
118
|
-
throw err;
|
|
119
|
-
}
|
|
120
|
-
finally {
|
|
121
|
-
if (msgSpan) {
|
|
122
|
-
msgSpan.end();
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
this.clients.delete(cId);
|
|
128
|
-
this.ondisconnection?.(cId);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
catch (err) {
|
|
132
|
-
if (err instanceof Error) {
|
|
133
|
-
span.setStatus("error"); // Error status
|
|
134
|
-
span.recordException(err);
|
|
135
|
-
this.onerror?.(err);
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
this.onerror?.(new Error(`Failed to parse message: ${String(err)}`));
|
|
139
|
-
}
|
|
140
|
-
span.end();
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
ws.on("close", () => {
|
|
144
|
-
this.clients.delete(clientId);
|
|
145
|
-
this.ondisconnection?.(clientId);
|
|
146
|
-
});
|
|
147
|
-
ws.on("error", (err) => {
|
|
148
|
-
this.onerror?.(err);
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
return Promise.resolve();
|
|
152
|
-
}
|
|
153
|
-
async send(msg) {
|
|
154
|
-
const [cId, msgId] = msg.id ? String(msg.id).split(":") : [];
|
|
155
|
-
msg.id = parseInt(msgId);
|
|
156
|
-
const data = JSON.stringify(msg);
|
|
157
|
-
const deadClients = [];
|
|
158
|
-
if (cId) {
|
|
159
|
-
// Send to specific client
|
|
160
|
-
const client = this.clients.get(cId);
|
|
161
|
-
if (client?.ws?.readyState === ws_1.default.OPEN) {
|
|
162
|
-
const msgSpan = spans.get(cId + ":" + msg.id);
|
|
163
|
-
try {
|
|
164
|
-
client.ws.send(data);
|
|
165
|
-
if (msgSpan) {
|
|
166
|
-
msgSpan.setAttributes({
|
|
167
|
-
"mcp.message.response_sent": true,
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
catch (err) {
|
|
172
|
-
if (msgSpan) {
|
|
173
|
-
msgSpan.setStatus("error"); // Error status
|
|
174
|
-
msgSpan.recordException(err);
|
|
175
|
-
}
|
|
176
|
-
throw err;
|
|
177
|
-
}
|
|
178
|
-
finally {
|
|
179
|
-
if (msgSpan) {
|
|
180
|
-
msgSpan.end();
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
this.clients.delete(cId);
|
|
186
|
-
this.ondisconnection?.(cId);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
for (const [id, client] of this.clients.entries()) {
|
|
190
|
-
if (client.ws.readyState !== ws_1.default.OPEN) {
|
|
191
|
-
deadClients.push(id);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
// Cleanup dead clients
|
|
195
|
-
deadClients.forEach((id) => {
|
|
196
|
-
this.clients.delete(id);
|
|
197
|
-
this.ondisconnection?.(id);
|
|
198
|
-
});
|
|
199
|
-
return Promise.resolve();
|
|
200
|
-
}
|
|
201
|
-
async broadcast(msg) {
|
|
202
|
-
return this.send(msg);
|
|
203
|
-
}
|
|
204
|
-
async close() {
|
|
205
|
-
return new Promise((resolve) => {
|
|
206
|
-
this.wss.close(() => {
|
|
207
|
-
this.clients.clear();
|
|
208
|
-
resolve();
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
exports.BlaxelWebsocketMcpServerTransport = BlaxelWebsocketMcpServerTransport;
|