@mcp-b/transports 0.0.1 → 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/dist/.tsbuildinfo +1 -1
- package/dist/index.d.ts +174 -385
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +37 -19
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,177 @@
|
|
|
1
|
-
import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
|
|
2
1
|
import { Transport, TransportSendOptions } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
3
|
-
import {
|
|
2
|
+
import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
|
|
4
|
+
interface TabClientTransportOptions {
|
|
5
|
+
targetOrigin: string;
|
|
6
|
+
channelId?: string;
|
|
7
|
+
}
|
|
8
|
+
declare class TabClientTransport implements Transport {
|
|
9
|
+
private _started;
|
|
10
|
+
private _targetOrigin;
|
|
11
|
+
private _channelId;
|
|
12
|
+
private _messageHandler?;
|
|
13
|
+
onclose?: () => void;
|
|
14
|
+
onerror?: (error: Error) => void;
|
|
15
|
+
onmessage?: (message: JSONRPCMessage) => void;
|
|
16
|
+
constructor(options: TabClientTransportOptions);
|
|
17
|
+
start(): Promise<void>;
|
|
18
|
+
send(message: JSONRPCMessage): Promise<void>;
|
|
19
|
+
close(): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface TabServerTransportOptions {
|
|
23
|
+
allowedOrigins: string[];
|
|
24
|
+
channelId?: string;
|
|
25
|
+
}
|
|
26
|
+
declare class TabServerTransport implements Transport {
|
|
27
|
+
private _started;
|
|
28
|
+
private _allowedOrigins;
|
|
29
|
+
private _channelId;
|
|
30
|
+
private _messageHandler?;
|
|
31
|
+
private _clientOrigin?;
|
|
32
|
+
onclose?: () => void;
|
|
33
|
+
onerror?: (error: Error) => void;
|
|
34
|
+
onmessage?: (message: JSONRPCMessage) => void;
|
|
35
|
+
constructor(options: TabServerTransportOptions);
|
|
36
|
+
start(): Promise<void>;
|
|
37
|
+
send(message: JSONRPCMessage): Promise<void>;
|
|
38
|
+
close(): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Configuration options for ExtensionClientTransport
|
|
43
|
+
*/
|
|
44
|
+
interface ExtensionClientTransportOptions {
|
|
45
|
+
/**
|
|
46
|
+
* The extension ID to connect to (optional for same-extension connections)
|
|
47
|
+
*/
|
|
48
|
+
extensionId?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Port name for the connection
|
|
51
|
+
* Default: 'mcp'
|
|
52
|
+
*/
|
|
53
|
+
portName?: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Client transport for Chrome extensions using Port-based messaging.
|
|
57
|
+
* This transport can be used in content scripts, popup scripts, or sidepanel scripts
|
|
58
|
+
* to connect to a server running in the background service worker.
|
|
59
|
+
*/
|
|
60
|
+
declare class ExtensionClientTransport implements Transport {
|
|
61
|
+
private _port?;
|
|
62
|
+
private _extensionId?;
|
|
63
|
+
private _portName;
|
|
64
|
+
private _messageHandler?;
|
|
65
|
+
private _disconnectHandler?;
|
|
66
|
+
onclose?: () => void;
|
|
67
|
+
onerror?: (error: Error) => void;
|
|
68
|
+
onmessage?: (message: JSONRPCMessage) => void;
|
|
69
|
+
constructor(options?: ExtensionClientTransportOptions);
|
|
70
|
+
/**
|
|
71
|
+
* Starts the transport by connecting to the extension port
|
|
72
|
+
*/
|
|
73
|
+
start(): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Sends a message to the server
|
|
76
|
+
*/
|
|
77
|
+
send(message: JSONRPCMessage, _options?: TransportSendOptions): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Closes the transport
|
|
80
|
+
*/
|
|
81
|
+
close(): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Cleans up event listeners and references
|
|
84
|
+
*/
|
|
85
|
+
private _cleanup;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Server transport for Chrome extensions using Port-based messaging.
|
|
90
|
+
* This transport handles a single client connection through Chrome's port messaging API.
|
|
91
|
+
* It should be used in the extension's background service worker.
|
|
92
|
+
*/
|
|
93
|
+
declare class ExtensionServerTransport implements Transport {
|
|
94
|
+
private _port;
|
|
95
|
+
private _started;
|
|
96
|
+
private _messageHandler?;
|
|
97
|
+
private _disconnectHandler?;
|
|
98
|
+
onclose?: () => void;
|
|
99
|
+
onerror?: (error: Error) => void;
|
|
100
|
+
onmessage?: (message: JSONRPCMessage) => void;
|
|
101
|
+
constructor(port: chrome.runtime.Port);
|
|
102
|
+
/**
|
|
103
|
+
* Starts the transport and begins handling messages
|
|
104
|
+
*/
|
|
105
|
+
start(): Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* Sends a message to the client
|
|
108
|
+
*/
|
|
109
|
+
send(message: JSONRPCMessage, _options?: TransportSendOptions): Promise<void>;
|
|
110
|
+
/**
|
|
111
|
+
* Closes the transport
|
|
112
|
+
*/
|
|
113
|
+
close(): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* Cleans up event listeners and references
|
|
116
|
+
*/
|
|
117
|
+
private _cleanup;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Client transport for WebSocket: this will connect to a server over the WebSocket protocol.
|
|
122
|
+
* This transport is Node.js specific and requires the 'ws' package to be installed.
|
|
123
|
+
*/
|
|
124
|
+
declare class WebSocketClientTransport implements Transport {
|
|
125
|
+
private _socket?;
|
|
126
|
+
private _url;
|
|
127
|
+
private _WebSocket?;
|
|
128
|
+
onclose?: () => void;
|
|
129
|
+
onerror?: (error: Error) => void;
|
|
130
|
+
onmessage?: (message: JSONRPCMessage) => void;
|
|
131
|
+
constructor(url: URL);
|
|
132
|
+
start(): Promise<void>;
|
|
133
|
+
close(): Promise<void>;
|
|
134
|
+
send(message: JSONRPCMessage): Promise<void>;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Server transport for WebSocket: this will accept connections from MCP clients over WebSocket protocol.
|
|
139
|
+
* Designed to work in browser extension environments.
|
|
140
|
+
*/
|
|
141
|
+
declare class WebSocketServerTransport implements Transport {
|
|
142
|
+
private _options;
|
|
143
|
+
private _socket?;
|
|
144
|
+
private _server?;
|
|
145
|
+
private _started;
|
|
146
|
+
onclose?: () => void;
|
|
147
|
+
onerror?: (error: Error) => void;
|
|
148
|
+
onmessage?: (message: JSONRPCMessage) => void;
|
|
149
|
+
constructor(_options: {
|
|
150
|
+
/**
|
|
151
|
+
* For browser extensions: provide an existing WebSocket connection
|
|
152
|
+
*/
|
|
153
|
+
socket?: WebSocket;
|
|
154
|
+
/**
|
|
155
|
+
* For Node.js: provide port to create WebSocketServer
|
|
156
|
+
*/
|
|
157
|
+
port?: number;
|
|
158
|
+
});
|
|
159
|
+
/**
|
|
160
|
+
* Starts the transport. In browser extension context, this validates the socket.
|
|
161
|
+
*/
|
|
162
|
+
start(): Promise<void>;
|
|
163
|
+
private setupSocketHandlers;
|
|
164
|
+
close(): Promise<void>;
|
|
165
|
+
send(message: JSONRPCMessage): Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* Get the current WebSocket connection (if any)
|
|
168
|
+
*/
|
|
169
|
+
get socket(): WebSocket | undefined;
|
|
170
|
+
/**
|
|
171
|
+
* Check if transport has an active connection
|
|
172
|
+
*/
|
|
173
|
+
get isConnected(): boolean;
|
|
174
|
+
}
|
|
4
175
|
|
|
5
176
|
/**
|
|
6
177
|
* Unique identifier for an event in the event store
|
|
@@ -129,386 +300,4 @@ interface StoredEvent {
|
|
|
129
300
|
clientId: string;
|
|
130
301
|
}
|
|
131
302
|
|
|
132
|
-
|
|
133
|
-
* Browser-specific error class to match StreamableHTTPError
|
|
134
|
-
*/
|
|
135
|
-
declare class BrowserTransportError extends Error {
|
|
136
|
-
readonly code: string | undefined;
|
|
137
|
-
constructor(code: string | undefined, message: string | undefined);
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Configuration options for the BrowserClientTransport, matching StreamableHTTPClientTransportOptions style
|
|
141
|
-
*/
|
|
142
|
-
interface TabClientTransportOptions {
|
|
143
|
-
/**
|
|
144
|
-
* A unique identifier for this client instance. If not provided, one will be generated.
|
|
145
|
-
* This is similar to a persistent client identifier.
|
|
146
|
-
*/
|
|
147
|
-
clientInstanceId?: string;
|
|
148
|
-
/**
|
|
149
|
-
* Global namespace to look for MCP server (defaults to 'mcp')
|
|
150
|
-
*/
|
|
151
|
-
globalNamespace?: string;
|
|
152
|
-
/**
|
|
153
|
-
* Options to configure the reconnection behavior.
|
|
154
|
-
*/
|
|
155
|
-
reconnectionOptions?: BrowserReconnectionOptions;
|
|
156
|
-
/**
|
|
157
|
-
* Timeout for initial connection handshake (ms). Default is 30000 (30 seconds).
|
|
158
|
-
*/
|
|
159
|
-
connectionTimeout?: number;
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Configuration options for reconnection behavior
|
|
163
|
-
*/
|
|
164
|
-
interface BrowserReconnectionOptions {
|
|
165
|
-
/**
|
|
166
|
-
* Maximum backoff time between reconnection attempts in milliseconds.
|
|
167
|
-
* Default is 30000 (30 seconds).
|
|
168
|
-
*/
|
|
169
|
-
maxReconnectionDelay: number;
|
|
170
|
-
/**
|
|
171
|
-
* Initial backoff time between reconnection attempts in milliseconds.
|
|
172
|
-
* Default is 1000 (1 second).
|
|
173
|
-
*/
|
|
174
|
-
initialReconnectionDelay: number;
|
|
175
|
-
/**
|
|
176
|
-
* The factor by which the reconnection delay increases after each attempt.
|
|
177
|
-
* Default is 1.5.
|
|
178
|
-
*/
|
|
179
|
-
reconnectionDelayGrowFactor: number;
|
|
180
|
-
/**
|
|
181
|
-
* Maximum number of reconnection attempts before giving up.
|
|
182
|
-
* Default is 2.
|
|
183
|
-
*/
|
|
184
|
-
maxRetries: number;
|
|
185
|
-
}
|
|
186
|
-
/**
|
|
187
|
-
* Client transport for browser environments using window.mcp global.
|
|
188
|
-
* This implementation follows the same patterns as StreamableHTTPClientTransport.
|
|
189
|
-
*/
|
|
190
|
-
declare class TabClientTransport implements Transport {
|
|
191
|
-
private _globalNamespace;
|
|
192
|
-
/**
|
|
193
|
-
* The client's persistent instance ID
|
|
194
|
-
*/
|
|
195
|
-
clientInstanceId: string;
|
|
196
|
-
/**
|
|
197
|
-
* The session ID provided by the server during connection
|
|
198
|
-
*/
|
|
199
|
-
sessionId?: string;
|
|
200
|
-
private _reconnectionOptions;
|
|
201
|
-
private _connectionTimeout;
|
|
202
|
-
private _port?;
|
|
203
|
-
/**
|
|
204
|
-
* The server's instance ID, received during handshake.
|
|
205
|
-
*/
|
|
206
|
-
serverInstanceId?: string;
|
|
207
|
-
hasEventStore: boolean;
|
|
208
|
-
streamId?: string;
|
|
209
|
-
/**
|
|
210
|
-
* Indicates whether the transport is currently connected.
|
|
211
|
-
*/
|
|
212
|
-
isConnected: boolean;
|
|
213
|
-
private _abortController?;
|
|
214
|
-
private _connectionPromise?;
|
|
215
|
-
/**
|
|
216
|
-
* The last event ID received from the server.
|
|
217
|
-
*/
|
|
218
|
-
lastEventId?: EventId;
|
|
219
|
-
private _reconnectAttempt;
|
|
220
|
-
onclose?: () => void;
|
|
221
|
-
onerror?: (error: Error) => void;
|
|
222
|
-
onmessage?: (message: JSONRPCMessage) => void;
|
|
223
|
-
constructor(opts?: TabClientTransportOptions);
|
|
224
|
-
/**
|
|
225
|
-
* Start the transport connection
|
|
226
|
-
*/
|
|
227
|
-
start(): Promise<void>;
|
|
228
|
-
/**
|
|
229
|
-
* Internal method to establish connection with retry logic
|
|
230
|
-
*/
|
|
231
|
-
private _connectWithRetry;
|
|
232
|
-
/**
|
|
233
|
-
* Handle incoming messages with error handling
|
|
234
|
-
*/
|
|
235
|
-
private _handleMessage;
|
|
236
|
-
/**
|
|
237
|
-
* Handle connection errors with retry logic
|
|
238
|
-
*/
|
|
239
|
-
private _handleConnectionError;
|
|
240
|
-
/**
|
|
241
|
-
* Calculate the next reconnection delay using exponential backoff
|
|
242
|
-
*/
|
|
243
|
-
private _getNextReconnectionDelay;
|
|
244
|
-
/**
|
|
245
|
-
* Schedule a reconnection attempt with exponential backoff
|
|
246
|
-
*/
|
|
247
|
-
private _scheduleReconnection;
|
|
248
|
-
/**
|
|
249
|
-
* Send a message over the transport
|
|
250
|
-
*/
|
|
251
|
-
send(message: JSONRPCMessage, options?: {
|
|
252
|
-
resumptionToken?: string;
|
|
253
|
-
onresumptiontoken?: (token: string) => void;
|
|
254
|
-
}): Promise<void>;
|
|
255
|
-
/**
|
|
256
|
-
* Close the transport connection
|
|
257
|
-
*/
|
|
258
|
-
close(): Promise<void>;
|
|
259
|
-
/**
|
|
260
|
-
* Terminate the current session explicitly
|
|
261
|
-
* Similar to StreamableHTTP's terminateSession
|
|
262
|
-
*/
|
|
263
|
-
terminateSession(): Promise<void>;
|
|
264
|
-
/**
|
|
265
|
-
* Static helper to check if an MCP server is available
|
|
266
|
-
* Similar to checking server availability before connection
|
|
267
|
-
*/
|
|
268
|
-
static isServerAvailable(namespace?: string): boolean;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Configuration options for BrowserServerTransport
|
|
273
|
-
*/
|
|
274
|
-
interface BrowserServerTransportOptions {
|
|
275
|
-
/**
|
|
276
|
-
* Function that generates a session ID for each client connection.
|
|
277
|
-
* Return undefined to operate in stateless mode.
|
|
278
|
-
* If not provided, defaults to undefined (stateless mode).
|
|
279
|
-
*/
|
|
280
|
-
sessionIdGenerator?: (() => string) | undefined;
|
|
281
|
-
/**
|
|
282
|
-
* Callback for session initialization events
|
|
283
|
-
*/
|
|
284
|
-
onsessioninitialized?: (clientSessionId: string | undefined, clientInstanceId: string) => void;
|
|
285
|
-
/**
|
|
286
|
-
* Optional namespace to use instead of window.mcp
|
|
287
|
-
*/
|
|
288
|
-
globalNamespace?: string;
|
|
289
|
-
/**
|
|
290
|
-
* Enable event storage for resumability (only works in stateful mode)
|
|
291
|
-
* Default is true in stateful mode, false in stateless mode
|
|
292
|
-
*/
|
|
293
|
-
enableEventStore?: boolean;
|
|
294
|
-
/**
|
|
295
|
-
* Maximum number of events to store per client (default: 1000)
|
|
296
|
-
*/
|
|
297
|
-
maxEventsPerClient?: number;
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Server transport for browser environments using window.mcp global
|
|
301
|
-
* Supports multiple concurrent client connections via MessageChannel
|
|
302
|
-
*/
|
|
303
|
-
declare class TabServerTransport implements Transport {
|
|
304
|
-
private _serverInstanceId;
|
|
305
|
-
private _sessionIdGenerator;
|
|
306
|
-
private _onsessioninitialized?;
|
|
307
|
-
private _isStarted;
|
|
308
|
-
private _clients;
|
|
309
|
-
private _globalNamespace;
|
|
310
|
-
private _eventStore?;
|
|
311
|
-
private _enableEventStore;
|
|
312
|
-
onclose?: () => void;
|
|
313
|
-
onerror?: (error: Error) => void;
|
|
314
|
-
onmessage?: (message: JSONRPCMessage, extra?: {
|
|
315
|
-
authInfo?: AuthInfo | undefined;
|
|
316
|
-
}) => void;
|
|
317
|
-
constructor(options?: BrowserServerTransportOptions);
|
|
318
|
-
start(): Promise<void>;
|
|
319
|
-
private _setupClientConnection;
|
|
320
|
-
send(message: JSONRPCMessage, options?: TransportSendOptions & {
|
|
321
|
-
targetClientId?: string;
|
|
322
|
-
}): Promise<void>;
|
|
323
|
-
close(): Promise<void>;
|
|
324
|
-
get clientCount(): number;
|
|
325
|
-
get clients(): ReadonlyArray<{
|
|
326
|
-
clientId: string;
|
|
327
|
-
initialized: boolean;
|
|
328
|
-
serverSessionId?: string;
|
|
329
|
-
}>;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* In your background script:
|
|
334
|
-
* import { setupBackgroundBridge } from '@modelcontextprotocol/sdk/extension-bridge/backgroundBridge'
|
|
335
|
-
* // Dynamically inject pageBridge and contentScript into matching pages
|
|
336
|
-
* setupBackgroundBridge({
|
|
337
|
-
* matches: ['https://your-app.com/*'],
|
|
338
|
-
* pageBridgeRunAt: 'document_start',
|
|
339
|
-
* contentScriptRunAt: 'document_idle'
|
|
340
|
-
* });
|
|
341
|
-
*/
|
|
342
|
-
declare function setupBackgroundBridge(_?: {
|
|
343
|
-
/** Host-match patterns for injection, defaults to ['<all_urls>'] */
|
|
344
|
-
matches?: string[];
|
|
345
|
-
/** When to inject the pageBridge (MAIN world) */
|
|
346
|
-
pageBridgeRunAt?: 'document_start' | 'document_end' | 'document_idle';
|
|
347
|
-
/** When to inject the content script (ISOLATED world) */
|
|
348
|
-
contentScriptRunAt?: 'document_start' | 'document_end' | 'document_idle';
|
|
349
|
-
}): Promise<void>;
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* Sets up a content script bridge that relays messages between the page context
|
|
353
|
-
* and the extension background script. This enables communication between
|
|
354
|
-
* the injected page bridge and the extension's background script.
|
|
355
|
-
*
|
|
356
|
-
* @param port - Optional Chrome runtime port. If not provided, creates a default port with name 'cs'
|
|
357
|
-
* @returns The Chrome runtime port used for communication
|
|
358
|
-
*/
|
|
359
|
-
declare function mcpRelay(port?: chrome.runtime.Port): chrome.runtime.Port;
|
|
360
|
-
|
|
361
|
-
declare const isJSONRPCMessage: (value: unknown) => value is JSONRPCMessage;
|
|
362
|
-
/**
|
|
363
|
-
* Configuration options for reconnection behavior, mirroring BrowserReconnectionOptions
|
|
364
|
-
*/
|
|
365
|
-
interface ExtensionReconnectionOptions {
|
|
366
|
-
maxReconnectionDelay: number;
|
|
367
|
-
initialReconnectionDelay: number;
|
|
368
|
-
reconnectionDelayGrowFactor: number;
|
|
369
|
-
maxRetries: number;
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Configuration options for the ExtensionClientTransport
|
|
373
|
-
*/
|
|
374
|
-
interface ExtensionClientTransportOptions {
|
|
375
|
-
clientInstanceId?: string;
|
|
376
|
-
reconnectionOptions?: ExtensionReconnectionOptions;
|
|
377
|
-
connectionTimeout?: number;
|
|
378
|
-
}
|
|
379
|
-
declare class ExtensionClientTransport implements Transport {
|
|
380
|
-
onclose?: () => void;
|
|
381
|
-
onerror?: (err: Error) => void;
|
|
382
|
-
onmessage?: (message: JSONRPCMessage, extra?: {
|
|
383
|
-
authInfo?: any;
|
|
384
|
-
}) => void;
|
|
385
|
-
isConnected: boolean;
|
|
386
|
-
sessionId?: string;
|
|
387
|
-
lastEventId?: EventId;
|
|
388
|
-
serverInstanceId?: string;
|
|
389
|
-
hasEventStore: boolean;
|
|
390
|
-
streamId?: string;
|
|
391
|
-
private clientId;
|
|
392
|
-
private bridge;
|
|
393
|
-
private _reconnectionOptions;
|
|
394
|
-
private _connectionTimeout;
|
|
395
|
-
private _abortController?;
|
|
396
|
-
private _connectionPromise?;
|
|
397
|
-
private _reconnectAttempt;
|
|
398
|
-
private _startPromise?;
|
|
399
|
-
constructor(options?: ExtensionClientTransportOptions & {
|
|
400
|
-
port?: chrome.runtime.Port;
|
|
401
|
-
});
|
|
402
|
-
private _setupMessageHandler;
|
|
403
|
-
start(): Promise<void>;
|
|
404
|
-
private _connectWithRetry;
|
|
405
|
-
private _handleConnectionError;
|
|
406
|
-
private _getNextReconnectionDelay;
|
|
407
|
-
private _scheduleReconnection;
|
|
408
|
-
send(message: JSONRPCMessage, _?: TransportSendOptions): Promise<void>;
|
|
409
|
-
close(): Promise<void>;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Union type representing all possible message types that can be sent through the page bridge.
|
|
414
|
-
* This includes MCP server information, events, replay events, and JSON-RPC messages.
|
|
415
|
-
*/
|
|
416
|
-
type PageBridgeMessageType = MCPServerInfoMessage | MCPEventMessage | MCPReplayEventMessage | JSONRPCMessage | {
|
|
417
|
-
type: 'mcp-server-disconnected';
|
|
418
|
-
reason: string;
|
|
419
|
-
};
|
|
420
|
-
/**
|
|
421
|
-
* Response structure for messages received from the bridge.
|
|
422
|
-
*/
|
|
423
|
-
interface BridgeResponse {
|
|
424
|
-
/** Unique identifier for the client connection */
|
|
425
|
-
clientId: string;
|
|
426
|
-
/** The actual message payload */
|
|
427
|
-
msg: PageBridgeMessageType;
|
|
428
|
-
}
|
|
429
|
-
/**
|
|
430
|
-
* Message structure for commands sent to the bridge.
|
|
431
|
-
*/
|
|
432
|
-
interface BridgeMessage {
|
|
433
|
-
/** Command type - either 'connect' to establish a connection or 'send' to send a message */
|
|
434
|
-
cmd: 'connect' | 'send' | 'disconnect';
|
|
435
|
-
/** Unique identifier for the client connection */
|
|
436
|
-
clientId: string;
|
|
437
|
-
/** Connection options, required when cmd is 'connect' */
|
|
438
|
-
options?: MCPConnectOptions;
|
|
439
|
-
/** JSON-RPC message to send, required when cmd is 'send' */
|
|
440
|
-
message?: JSONRPCMessage;
|
|
441
|
-
}
|
|
442
|
-
/**
|
|
443
|
-
* Creates a UI bridge for communicating with MCP servers from browser extension UI components.
|
|
444
|
-
*
|
|
445
|
-
* This function establishes a connection to the extension's background script via Chrome's
|
|
446
|
-
* runtime messaging API, allowing UI components like sidebars and popups to interact with
|
|
447
|
-
* MCP servers.
|
|
448
|
-
*
|
|
449
|
-
* @param port - Optional Chrome runtime port to use for communication. If not provided,
|
|
450
|
-
* a new port will be created with the name 'extensionUI'.
|
|
451
|
-
*
|
|
452
|
-
* The port should be connected to your background script's chrome.runtime.onConnect listener.
|
|
453
|
-
*
|
|
454
|
-
* @returns An object with methods to interact with MCP servers:
|
|
455
|
-
* - connect: Establish a connection to an MCP server
|
|
456
|
-
* - send: Send JSON-RPC messages to a connected server
|
|
457
|
-
* - onMessage: Listen for responses and events from the server
|
|
458
|
-
*
|
|
459
|
-
* @example
|
|
460
|
-
* ```typescript
|
|
461
|
-
* // In your sidebar/popup code:
|
|
462
|
-
* import { createUIBridge } from '@modelcontextprotocol/sdk/extension-bridge/uiConnector';
|
|
463
|
-
*
|
|
464
|
-
* // Using default port
|
|
465
|
-
* const bridge = createUIBridge();
|
|
466
|
-
*
|
|
467
|
-
* // Or using a custom port
|
|
468
|
-
* const customPort = chrome.runtime.connect({ name: 'sidebar' });
|
|
469
|
-
* const bridge = createUIBridge(customPort);
|
|
470
|
-
*
|
|
471
|
-
* // Connect to an MCP server
|
|
472
|
-
* bridge.connect('my-client-id', {
|
|
473
|
-
* serverName: 'my-server',
|
|
474
|
-
* command: 'node',
|
|
475
|
-
* args: ['server.js']
|
|
476
|
-
* });
|
|
477
|
-
*
|
|
478
|
-
* // Listen for messages
|
|
479
|
-
* bridge.onMessage((response) => {
|
|
480
|
-
* console.log('Received:', response.msg);
|
|
481
|
-
* });
|
|
482
|
-
*
|
|
483
|
-
* // Send a message
|
|
484
|
-
* bridge.send('my-client-id', {
|
|
485
|
-
* jsonrpc: '2.0',
|
|
486
|
-
* id: 1,
|
|
487
|
-
* method: 'tools/list'
|
|
488
|
-
* });
|
|
489
|
-
* ```
|
|
490
|
-
*/
|
|
491
|
-
declare function createUIBridge(port?: chrome.runtime.Port): {
|
|
492
|
-
/**
|
|
493
|
-
* Establishes a connection to an MCP server.
|
|
494
|
-
*
|
|
495
|
-
* @param clientId - Unique identifier for this client connection
|
|
496
|
-
* @param options - Configuration options for the MCP server connection
|
|
497
|
-
*/
|
|
498
|
-
connect: (clientId: string, options?: MCPConnectOptions) => void;
|
|
499
|
-
/**
|
|
500
|
-
* Sends a JSON-RPC message to a connected MCP server.
|
|
501
|
-
*
|
|
502
|
-
* @param clientId - The client ID of the connection to send the message through
|
|
503
|
-
* @param message - The JSON-RPC message to send
|
|
504
|
-
*/
|
|
505
|
-
send: (clientId: string, message: JSONRPCMessage) => void;
|
|
506
|
-
/**
|
|
507
|
-
* Registers a handler for incoming messages from MCP servers.
|
|
508
|
-
*
|
|
509
|
-
* @param handler - Function to handle incoming bridge responses
|
|
510
|
-
*/
|
|
511
|
-
onMessage: (handler: (resp: BridgeResponse) => void) => void;
|
|
512
|
-
};
|
|
513
|
-
|
|
514
|
-
export { type BridgeMessage, type BridgeResponse, type BrowserReconnectionOptions, type BrowserServerTransportOptions, BrowserTransportError, type EventId, ExtensionClientTransport, type ExtensionClientTransportOptions, type ExtensionReconnectionOptions, type MCPBrowserInterface, type MCPConnectOptions, type MCPEventMessage, type MCPEventStore, type MCPReplayEventMessage, type MCPServerInfo, type MCPServerInfoMessage, type MCPWindow, type PageBridgeMessageType, type StoredEvent, type StreamId, TabClientTransport, type TabClientTransportOptions, TabServerTransport, createUIBridge, isJSONRPCMessage, mcpRelay, setupBackgroundBridge };
|
|
303
|
+
export { type EventId, ExtensionClientTransport, type ExtensionClientTransportOptions, ExtensionServerTransport, type MCPBrowserInterface, type MCPConnectOptions, type MCPEventMessage, type MCPEventStore, type MCPReplayEventMessage, type MCPServerInfo, type MCPServerInfoMessage, type MCPWindow, type StoredEvent, type StreamId, TabClientTransport, type TabClientTransportOptions, TabServerTransport, type TabServerTransportOptions, WebSocketClientTransport, WebSocketServerTransport };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {JSONRPCMessageSchema,isInitializeRequest,isJSONRPCResponse,isJSONRPCError}from'@modelcontextprotocol/sdk/types.js';var m=class extends Error{constructor(t,s){super(`Browser transport error: ${s}`);this.code=t;}},P={initialReconnectionDelay:1e3,maxReconnectionDelay:3e4,reconnectionDelayGrowFactor:1.5,maxRetries:2},f=class{_globalNamespace;clientInstanceId;sessionId;_reconnectionOptions;_connectionTimeout;_port;serverInstanceId;hasEventStore=false;streamId;isConnected=false;_abortController;_connectionPromise;lastEventId;_reconnectAttempt=0;onclose;onerror;onmessage;constructor(e){this._globalNamespace=e?.globalNamespace??"mcp",this._reconnectionOptions=e?.reconnectionOptions??P,this._connectionTimeout=e?.connectionTimeout??3e4,this.clientInstanceId=e?.clientInstanceId||(typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():`client-${Date.now()}-${Math.random().toString(36).substr(2,9)}`);}async start(){if(this._abortController)throw new Error("TabClientTransport already started! If using Client class, note that connect() calls start() automatically.");this._abortController=new AbortController,await this._connectWithRetry();}async _connectWithRetry(e){return new Promise((t,s)=>{this._connectionPromise={resolve:t,reject:s};let n=window,o=this._globalNamespace==="mcp"?n.mcp:n[this._globalNamespace];if(!o?.isServerAvailable?.()){let l=new m("NO_SERVER",`No MCP server found at window.${this._globalNamespace}`);this._handleConnectionError(l,e);return}let r=o.getServerInfo();this.serverInstanceId=r.instanceId;let i=e?{resumeFrom:e}:void 0,c=o.connect(this.clientInstanceId,i);if(!c){let l=new m("CONNECTION_FAILED","Failed to connect to MCP server");this._handleConnectionError(l,e);return}this._port=c;let p=false,g;this._port.onmessage=l=>{let h=l.data;if(!p&&h.type==="mcp-server-info"){let d=h;clearTimeout(g),p=true,this.sessionId=d.serverSessionId,this.hasEventStore=d.hasEventStore||false,this.streamId=d.streamId,this.isConnected=true,this._reconnectAttempt=0,this._connectionPromise&&(this._connectionPromise.resolve(),this._connectionPromise=void 0);return}if(h.type==="mcp-replay-event"){let d=h;this.lastEventId=d.eventId,this._handleMessage(d.message);return}if(h.type==="mcp-event"){let d=h;this.lastEventId=d.eventId,this._handleMessage(d.message);return}p&&this._handleMessage(h);},this._port.onmessageerror=()=>{let l=new m("MESSAGE_ERROR","MessagePort error");this.onerror?.(l),this._connectionPromise&&(this._connectionPromise.reject(l),this._connectionPromise=void 0);},this._port.start(),g=setTimeout(()=>{if(!p){let l=new m("HANDSHAKE_TIMEOUT","Server handshake timeout");this._handleConnectionError(l,e);}},this._connectionTimeout);})}_handleMessage(e){try{let t=JSONRPCMessageSchema.parse(e);this.onmessage?.(t);}catch(t){this.onerror?.(new Error(`Failed to parse message: ${t}`));}}_handleConnectionError(e,t){this.onerror?.(e),this._connectionPromise&&(this._connectionPromise.reject(e),this._connectionPromise=void 0),this._abortController&&!this._abortController.signal.aborted&&t&&this._scheduleReconnection(t);}_getNextReconnectionDelay(e){let t=this._reconnectionOptions.initialReconnectionDelay,s=this._reconnectionOptions.reconnectionDelayGrowFactor,n=this._reconnectionOptions.maxReconnectionDelay;return Math.min(t*Math.pow(s,e),n)}_scheduleReconnection(e){let t=this._reconnectionOptions.maxRetries;if(t>0&&this._reconnectAttempt>=t){this.onerror?.(new Error(`Maximum reconnection attempts (${t}) exceeded.`));return}let s=this._getNextReconnectionDelay(this._reconnectAttempt);setTimeout(()=>{this._reconnectAttempt++,this._connectWithRetry(e).catch(n=>{this.onerror?.(new Error(`Failed to reconnect: ${n instanceof Error?n.message:String(n)}`));});},s);}async send(e,t){if(!this.isConnected||!this._port)if(t?.resumptionToken){if(await this._connectWithRetry(t.resumptionToken).catch(s=>{throw this.onerror?.(new Error(`Failed to reconnect with token: ${s instanceof Error?s.message:String(s)}`)),s}),!this.isConnected||!this._port)throw new Error("Not connected after attempting reconnection with token.")}else if(this._reconnectionOptions.maxRetries>0&&this.lastEventId&&this._abortController&&!this._abortController.signal.aborted){if(await this._connectWithRetry(this.lastEventId).catch(s=>{let n=new Error(`Failed to auto-reconnect: ${s instanceof Error?s.message:String(s)}`);throw this.onerror?.(n),n}),!this.isConnected||!this._port)throw new Error("Not connected after attempting auto-reconnection.")}else throw new Error("Not connected");this._port.postMessage(e);}async close(){this._abortController?.abort(),this._abortController=void 0,this._connectionPromise&&(this._connectionPromise.reject(new Error("Transport closed")),this._connectionPromise=void 0),this._port&&(this._port.close(),this._port=void 0);let e=window,t=this._globalNamespace==="mcp"?e.mcp:e[this._globalNamespace];t?.disconnect&&t.disconnect(this.clientInstanceId),this.isConnected=false,this.onclose?.();}async terminateSession(){if(this.sessionId)try{let e=window,t=this._globalNamespace==="mcp"?e.mcp:e[this._globalNamespace];t?.terminateSession?t.terminateSession(this.clientInstanceId):t?.events?.clearEvents&&t.events.clearEvents(this.clientInstanceId),this.sessionId=void 0,this.lastEventId=void 0;}catch(e){throw this.onerror?.(e),e}}static isServerAvailable(e="mcp"){let t=window;return !!(e==="mcp"?t.mcp:t[e])?.isServerAvailable?.()}};var v=class{_events=[];_eventCounter=0;_maxEventsPerClient;constructor(e=1e3){this._maxEventsPerClient=e;}async storeEvent(e,t,s){let n=`evt_${++this._eventCounter}_${Date.now()}`,o={eventId:n,streamId:e,message:s,timestamp:Date.now(),clientId:t};this._events.push(o);let r=this._events.filter(i=>i.clientId===t);if(r.length>this._maxEventsPerClient){let i=r.slice(0,r.length-this._maxEventsPerClient);this._events=this._events.filter(c=>!i.includes(c));}return n}async replayEventsAfter(e,t,s){let n=this._events.filter(i=>i.clientId===e),o=0;if(t){let i=n.findIndex(c=>c.eventId===t);i>=0&&(o=i+1);}let r=n.slice(o);for(let i of r)await s(i.eventId,i.message);return r.length>0?r[r.length-1].streamId:`stream_${Date.now()}`}getEvents(e,t,s=100){let n=e?this._events.filter(o=>o.clientId===e):this._events;if(t){let o=n.findIndex(r=>r.eventId===t);o>=0&&(n=n.slice(o+1));}return n.slice(0,s)}getLastEventId(e){let t=e?this._events.filter(s=>s.clientId===e):this._events;return t.length>0?t[t.length-1].eventId:null}clearEvents(e){e?this._events=this._events.filter(t=>t.clientId!==e):this._events=[];}removeClientEvents(e){this._events=this._events.filter(t=>t.clientId!==e);}},u=class{_serverInstanceId;_sessionIdGenerator;_onsessioninitialized;_isStarted=false;_clients=new Map;_globalNamespace;_eventStore;_enableEventStore;onclose;onerror;onmessage;constructor(e){this._sessionIdGenerator=e?.sessionIdGenerator,this._onsessioninitialized=e?.onsessioninitialized,this._globalNamespace=e?.globalNamespace||"mcp",this._enableEventStore=e?.enableEventStore??this._sessionIdGenerator!==void 0,this._enableEventStore&&this._sessionIdGenerator&&(this._eventStore=new v(e?.maxEventsPerClient)),this._serverInstanceId=typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():`server-${Date.now()}-${Math.random().toString(36).substr(2,9)}`;}async start(){if(this._isStarted)throw new Error("BrowserServerTransport already started");let e=window;if((this._globalNamespace==="mcp"?e.mcp:e[this._globalNamespace])?.isServerAvailable?.())throw new Error(`Another MCP server is already registered at window.${this._globalNamespace}`);let s={connect:(n,o)=>{if(!this._isStarted)return console.error("MCP server not started"),null;if(this._clients.has(n))return console.error(`Client ${n} already connected`),null;let r=new MessageChannel,i=r.port1,c=r.port2,p=this._sessionIdGenerator?.(),g=`stream_${n}_${Date.now()}`,l={port:i,clientInstanceId:n,serverSessionId:p,streamId:g,initialized:false,requestIds:new Set};return this._setupClientConnection(l,o?.resumeFrom),this._clients.set(n,l),c},disconnect:n=>{let o=this._clients.get(n);o&&(o.port.close(),this._clients.delete(n));},isServerAvailable:()=>this._isStarted,getServerInfo:()=>({instanceId:this._serverInstanceId,stateful:this._sessionIdGenerator!==void 0,hasEventStore:this._eventStore!==void 0}),terminateSession:n=>{let o=this._clients.get(n);o&&(o.port.close(),this._clients.delete(n)),this._eventStore?.removeClientEvents(n);}};this._eventStore&&(s.events={getEvents:(n,o,r)=>this._eventStore.getEvents(n,o,r),getLastEventId:n=>this._eventStore.getLastEventId(n),clearEvents:n=>{this._eventStore.clearEvents(n);}}),this._globalNamespace==="mcp"?window.mcp=s:window[this._globalNamespace]=s,this._isStarted=true;}async _setupClientConnection(e,t){let{port:s,clientInstanceId:n}=e;s.onmessage=r=>{try{let i=JSONRPCMessageSchema.parse(r.data);if("id"in i&&i.id!==void 0&&"method"in i&&e.requestIds.add(i.id),isInitializeRequest(i)){if(e.initialized){this.onerror?.(new Error(`Client ${n} attempted to re-initialize`));return}e.initialized=!0,this._onsessioninitialized?.(e.serverSessionId,n);}else if(!e.initialized){let p=`Client ${n} sent message before initialization`;this.onerror?.(new Error(p));return}this.onmessage?.(i,{authInfo:{clientId:n,token:"N/A",scopes:["browser"]}});}catch(i){this.onerror?.(i instanceof Error?i:new Error(String(i)));}},s.onmessageerror=()=>{this.onerror?.(new Error(`MessagePort error for client ${n}`));},s.start();let o={type:"mcp-server-info",serverInstanceId:this._serverInstanceId,serverSessionId:e.serverSessionId,hasEventStore:this._eventStore!==void 0,streamId:e.streamId};s.postMessage(o),t&&this._eventStore&&await this._eventStore.replayEventsAfter(n,t,async(r,i)=>{let c={type:"mcp-replay-event",eventId:r,message:i};s.postMessage(c);});}async send(e,t){if(!this._isStarted)throw new Error("BrowserServerTransport not started");let s=[];if(t?.relatedRequestId){for(let[n,o]of this._clients)if(o.requestIds.has(t.relatedRequestId)){s=[o],(isJSONRPCResponse(e)||isJSONRPCError(e))&&o.requestIds.delete(t.relatedRequestId);break}}else if(t?.targetClientId){let n=this._clients.get(t.targetClientId);n&&(s=[n]);}else s=Array.from(this._clients.values()).filter(n=>n.initialized);if(s.length===0){this.onerror?.(new Error("No suitable clients found for message"));return}for(let n of s){let o;if(this._eventStore&&(o=await this._eventStore.storeEvent(n.streamId,n.clientInstanceId,e)),o){let r={type:"mcp-event",eventId:o,message:e};n.port.postMessage(r);}else n.port.postMessage(e);}}async close(){if(this._isStarted){for(let e of this._clients.values())e.port.close();this._clients.clear(),this._eventStore?.clearEvents(),this._globalNamespace==="mcp"?delete window.mcp:delete window[this._globalNamespace],this._isStarted=false,this.onclose?.();}}get clientCount(){return this._clients.size}get clients(){return Array.from(this._clients.entries()).map(([e,t])=>({clientId:e,initialized:t.initialized,serverSessionId:t.serverSessionId}))}};async function G(a){let e=null,t=new Map,s=new Map;console.log("BSGW: Background bridge initialized"),chrome.runtime.onConnect.addListener(n=>{if(n.name==="extensionUI")console.log("BSGW: Extension UI connected"),e=n,n.onMessage.addListener(o=>{if(console.log("BSGW: Message from extension",o),o.cmd==="connect"){let r=Array.from(t.keys())[0];if(r!==void 0){s.set(o.clientId,r);let i=t.get(r);i&&(console.log(`BSGW: Routing connect for client ${o.clientId} to tab ${r}`),i.postMessage(o));}else console.error("BSGW: No content script tabs available for connection");}else if(o.cmd==="send"){let r=s.get(o.clientId);if(r!==void 0){let i=t.get(r);i?(console.log(`BSGW: Routing send for client ${o.clientId} to tab ${r}`),i.postMessage(o)):(console.error(`BSGW: Tab ${r} no longer available for client ${o.clientId}`),s.delete(o.clientId));}else console.error(`BSGW: No tab mapping found for client ${o.clientId}`);}}),n.onDisconnect.addListener(()=>{console.log("BSGW: Extension UI disconnected");let o=new Set;for(let[r,i]of s.entries())o.add(r);o.forEach(r=>{let i=s.get(r);if(i!==void 0){let c=t.get(i);c&&(console.log(`BSGW: Relaying disconnect for client ${r} to tab ${i}`),c.postMessage({cmd:"disconnect",clientId:r}));}s.delete(r);}),e=null;});else if(n.name==="cs"){let o=n.sender?.tab?.id;typeof o=="number"&&(console.log(`BSGW: Content script connected from tab ${o}`),t.set(o,n),n.onMessage.addListener(r=>{console.log(`BSGW: Message from tab ${o}`,r);let i=s.get(r.clientId);i===o?e?.postMessage(r):console.warn(`BSGW: Ignoring response from tab ${o} for client ${r.clientId} (expected tab ${i})`);}),n.onDisconnect.addListener(()=>{console.log(`BSGW: Content script disconnected from tab ${o}`),t.delete(o);for(let[r,i]of s.entries())i===o&&(console.log(`BSGW: Removing client ${r} mapping to disconnected tab ${o}`),e&&(console.log(`BSGW: Notifying UI about disconnected client ${r}`),e.postMessage({clientId:r,msg:{type:"mcp-server-disconnected",reason:"The tab hosting the server was refreshed or closed."}})),s.delete(r));}));}});}function U(a){let e=a??chrome.runtime.connect({name:"cs"});return window.addEventListener("message",t=>{if(t.source===window&&t.data?.source==="EXT-PAGE"){console.log("MCP relay: received from tab",t.data.cmd,t.data.clientId);let{clientId:s,msg:n}=t.data;e.postMessage({clientId:s,msg:n});}}),e.onMessage.addListener(t=>{console.log("MCP relay: received from extension",t.cmd,t.clientId),window.postMessage({source:"EXT-CS",...t},"*");}),e}function _(a=chrome.runtime.connect({name:"extensionUI"})){return {connect:(e,t)=>{console.log({clientId:e,options:t}),a.postMessage({cmd:"connect",clientId:e,options:t});},send:(e,t)=>{console.log({clientId:e,message:t}),a.postMessage({cmd:"send",clientId:e,message:t});},onMessage:e=>{a.onMessage.addListener(e);}}}var I=a=>JSONRPCMessageSchema.safeParse(a).success,R={initialReconnectionDelay:1e3,maxReconnectionDelay:3e4,reconnectionDelayGrowFactor:1.5,maxRetries:2},C=class{onclose;onerror;onmessage;isConnected=false;sessionId;lastEventId;serverInstanceId;hasEventStore=false;streamId;clientId;bridge;_reconnectionOptions;_connectionTimeout;_abortController;_connectionPromise;_reconnectAttempt=0;_startPromise;constructor(e){this.clientId=e?.clientInstanceId||(crypto.randomUUID?.()??`ext-${Date.now()}-${Math.random().toString(36).slice(2,8)}`),this._reconnectionOptions=e?.reconnectionOptions??R,this._connectionTimeout=e?.connectionTimeout??3e4,this.bridge=_(e?.port),this._setupMessageHandler();}_setupMessageHandler(){this.bridge.onMessage(e=>{if(console.log(`ExtensionClientTransport: Received message for client ${e.clientId}`,"Expected client:",this.clientId,"Match:",e.clientId===this.clientId,"Message type:",e.msg&&"type"in e.msg?e.msg.type:"unknown"),e.clientId!==this.clientId){console.log(`ExtensionClientTransport: Ignoring message for different client. Expected ${this.clientId}, got ${e.clientId}`);return}let t=e.msg;try{if("type"in t&&t.type==="mcp-server-disconnected"){if(console.warn(`ExtensionClientTransport: Server for client ${this.clientId} disconnected unexpectedly.`),this.isConnected){this.isConnected=!1;let s=new Error("The server-side transport has disconnected.");this._handleConnectionError(s,this.lastEventId);}return}if("type"in t&&t.type==="mcp-server-info"){let s=t;if(console.log(`ExtensionClientTransport: Received server info for client ${this.clientId}`,s),this._connectionPromise){this.sessionId=s.serverSessionId,this.serverInstanceId=s.serverInstanceId,this.hasEventStore=s.hasEventStore||!1,this.streamId=s.streamId,this.isConnected=!0,this._reconnectAttempt=0;let n=this._connectionPromise;this._connectionPromise=void 0,n.resolve();}else console.warn("ExtensionClientTransport: Received server info but no connection promise pending");return}if(!this.isConnected){console.warn("ExtensionClientTransport: Received message while not connected",t);return}if("type"in t&&(t.type==="mcp-replay-event"||t.type==="mcp-event")){let s=t;if(this.lastEventId=s.eventId,I(s.message))this.onmessage?.(s.message);else {let n=JSONRPCMessageSchema.parse(s.message);this.onmessage?.(n);}}else I(t)?this.onmessage?.(t):console.warn("ExtensionClientTransport received unknown message type:",t);}catch(s){let n=s;console.error("Error processing message in ExtensionClientTransport:",n,"Data:",t),this.onerror?.(n);}});}async start(){return this._startPromise?(console.warn("ExtensionClientTransport already started, returning existing connection promise"),this._startPromise):this._abortController&&(console.warn("ExtensionClientTransport already started!"),this.isConnected)?Promise.resolve():(this._abortController=new AbortController,this._startPromise=this._connectWithRetry().finally(()=>{this._startPromise=void 0;}),this._startPromise)}async _connectWithRetry(e){return new Promise((t,s)=>{this._connectionPromise={resolve:t,reject:s};let n,o=this._abortController?.signal;if(o?.aborted)return s(new Error("Connection aborted"));console.log(`ExtensionClientTransport: Starting connection for client ${this.clientId}`,e?`with resumption token ${e}`:"without resumption token");let r=e?{resumeFrom:e}:void 0;console.log(`ExtensionClientTransport: Sending connect command for client ${this.clientId}`,r),this.bridge.connect(this.clientId,r),n=setTimeout(()=>{if(!this.isConnected&&this._connectionPromise){let c=new Error(`ExtensionClientTransport: Server handshake timeout for client ${this.clientId}`);this._handleConnectionError(c,e);}},this._connectionTimeout);let i=this._connectionPromise.resolve;this._connectionPromise.resolve=()=>{n&&clearTimeout(n),i();},o?.addEventListener("abort",()=>{n&&clearTimeout(n),this._connectionPromise&&(this._connectionPromise.reject(new Error("Connection aborted during handshake")),this._connectionPromise=void 0);});})}_handleConnectionError(e,t){this.onerror?.(e),this.isConnected=false,this._connectionPromise&&(this._connectionPromise.reject(e),this._connectionPromise=void 0),this._abortController&&!this._abortController.signal.aborted&&(t||this.lastEventId)&&this._reconnectionOptions.maxRetries>0&&this._reconnectAttempt<this._reconnectionOptions.maxRetries?this._scheduleReconnection(t||this.lastEventId):this._abortController&&(this.onclose?.(),this._abortController=void 0);}_getNextReconnectionDelay(e){let{initialReconnectionDelay:t,maxReconnectionDelay:s,reconnectionDelayGrowFactor:n}=this._reconnectionOptions;return Math.min(t*Math.pow(n,e),s)}_scheduleReconnection(e){let t=this._getNextReconnectionDelay(this._reconnectAttempt);this._reconnectAttempt++,console.log(`ExtensionClientTransport: Scheduling reconnection attempt ${this._reconnectAttempt} in ${t}ms`),setTimeout(()=>{if(this._abortController?.signal.aborted){console.log("ExtensionClientTransport: Reconnection aborted.");return}console.log(`ExtensionClientTransport: Attempting reconnection (attempt ${this._reconnectAttempt})`),this._connectWithRetry(e).catch(s=>{console.error("ExtensionClientTransport: Scheduled reconnection attempt failed:",s);});},t);}async send(e,t){if(this._startPromise){console.log("ExtensionClientTransport: Waiting for initial connection before send.");try{await this._startPromise;}catch(s){let n=new Error(`ExtensionClientTransport: Failed to establish initial connection: ${s instanceof Error?s.message:String(s)}`);throw this.onerror?.(n),n}}if(!this.isConnected)if(this._reconnectionOptions.maxRetries>0&&this.lastEventId&&this._abortController&&!this._abortController.signal.aborted){console.log("ExtensionClientTransport: Not connected. Attempting auto-reconnect before send.");try{await this._connectWithRetry(this.lastEventId);}catch(n){let o=new Error(`ExtensionClientTransport: Failed to auto-reconnect before send: ${n instanceof Error?n.message:String(n)}`);throw this.onerror?.(o),o}if(!this.isConnected)throw new Error("ExtensionClientTransport: Not connected after attempting auto-reconnection.")}else throw new Error("ExtensionClientTransport: Not connected and cannot auto-reconnect.");this.bridge.send(this.clientId,e);}async close(){this._abortController&&(this._abortController.abort(),this._abortController=void 0),this._connectionPromise&&(this._connectionPromise.reject(new Error("ExtensionClientTransport: Transport closed by client.")),this._connectionPromise=void 0),this._startPromise=void 0,this.isConnected=false,this.onclose?.();}};export{m as BrowserTransportError,C as ExtensionClientTransport,f as TabClientTransport,u as TabServerTransport,_ as createUIBridge,I as isJSONRPCMessage,U as mcpRelay,G as setupBackgroundBridge};//# sourceMappingURL=index.js.map
|
|
1
|
+
import {JSONRPCMessageSchema}from'@modelcontextprotocol/sdk/types.js';var n=class{_started=false;_targetOrigin;_channelId;_messageHandler;onclose;onerror;onmessage;constructor(e){if(!e.targetOrigin)throw new Error("targetOrigin must be explicitly set for security");this._targetOrigin=e.targetOrigin,this._channelId=e.channelId||"mcp-default";}async start(){if(this._started)throw new Error("Transport already started");this._messageHandler=e=>{if(e.origin===this._targetOrigin&&!(e.data?.channel!==this._channelId||e.data?.type!=="mcp")&&e.data?.direction==="server-to-client")try{let r=JSONRPCMessageSchema.parse(e.data.payload);this.onmessage?.(r);}catch(r){this.onerror?.(new Error(`Invalid message: ${r}`));}},window.addEventListener("message",this._messageHandler),this._started=true;}async send(e){if(!this._started)throw new Error("Transport not started");window.postMessage({channel:this._channelId,type:"mcp",direction:"client-to-server",payload:e},this._targetOrigin);}async close(){this._messageHandler&&window.removeEventListener("message",this._messageHandler),this._started=false,this.onclose?.();}};var a=class{_started=false;_allowedOrigins;_channelId;_messageHandler;_clientOrigin;onclose;onerror;onmessage;constructor(e){if(!e.allowedOrigins||e.allowedOrigins.length===0)throw new Error("At least one allowed origin must be specified");this._allowedOrigins=e.allowedOrigins,this._channelId=e.channelId||"mcp-default";}async start(){if(this._started)throw new Error("Transport already started");this._messageHandler=e=>{if(console.log({event:e}),!(!this._allowedOrigins.includes(e.origin)&&!this._allowedOrigins.includes("*"))&&!(e.data?.channel!==this._channelId||e.data?.type!=="mcp")&&e.data?.direction==="client-to-server"){this._clientOrigin=e.origin;try{let r=JSONRPCMessageSchema.parse(e.data.payload);this.onmessage?.(r);}catch(r){this.onerror?.(new Error(`Invalid message: ${r}`));}}},window.addEventListener("message",this._messageHandler),this._started=true;}async send(e){if(!this._started)throw new Error("Transport not started");if(!this._clientOrigin)throw new Error("No client connected");window.postMessage({channel:this._channelId,type:"mcp",direction:"server-to-client",payload:e},this._clientOrigin);}async close(){this._messageHandler&&window.removeEventListener("message",this._messageHandler),this._started=false,this.onclose?.();}};var c=class{_port;_extensionId;_portName;_messageHandler;_disconnectHandler;onclose;onerror;onmessage;constructor(e={}){this._extensionId=e.extensionId,this._portName=e.portName||"mcp";}async start(){if(this._port)throw new Error("ExtensionClientTransport already started! If using Client class, note that connect() calls start() automatically.");return new Promise((e,r)=>{if(!chrome?.runtime?.connect){r(new Error("Chrome runtime API not available. This transport must be used in a Chrome extension context."));return}try{this._extensionId?this._port=chrome.runtime.connect(this._extensionId,{name:this._portName}):this._port=chrome.runtime.connect({name:this._portName}),this._messageHandler=s=>{try{let o=JSONRPCMessageSchema.parse(s);this.onmessage?.(o);}catch(o){this.onerror?.(new Error(`Failed to parse message: ${o}`));}},this._disconnectHandler=()=>{this._cleanup(),this.onclose?.();},this._port.onMessage.addListener(this._messageHandler),this._port.onDisconnect.addListener(this._disconnectHandler);let t=chrome.runtime.lastError;if(t){this._cleanup(),r(new Error(`Connection failed: ${t.message}`));return}e();}catch(t){r(t);}})}async send(e,r){if(!this._port)throw new Error("Not connected");try{this._port.postMessage(e);}catch(t){throw new Error(`Failed to send message: ${t}`)}}async close(){if(this._port)try{this._port.disconnect();}catch{}this._cleanup(),this.onclose?.();}_cleanup(){this._port&&(this._messageHandler&&this._port.onMessage.removeListener(this._messageHandler),this._disconnectHandler&&this._port.onDisconnect.removeListener(this._disconnectHandler)),this._port=void 0;}};var d=class{_port;_started=false;_messageHandler;_disconnectHandler;onclose;onerror;onmessage;constructor(e){this._port=e;}async start(){if(this._started)throw new Error("ExtensionServerTransport already started! If using Server class, note that connect() calls start() automatically.");if(!this._port)throw new Error("Port not available");this._started=true,this._messageHandler=e=>{try{let r=JSONRPCMessageSchema.parse(e);this.onmessage?.(r);}catch(r){this.onerror?.(new Error(`Failed to parse message: ${r}`));}},this._disconnectHandler=()=>{this._cleanup(),this.onclose?.();},this._port.onMessage.addListener(this._messageHandler),this._port.onDisconnect.addListener(this._disconnectHandler);}async send(e,r){if(!this._started)throw new Error("Transport not started");if(!this._port)throw new Error("Not connected to client");try{this._port.postMessage(e);}catch(t){throw new Error(`Failed to send message: ${t}`)}}async close(){if(this._started=false,this._port)try{this._port.disconnect();}catch{}this._cleanup(),this.onclose?.();}_cleanup(){this._port&&(this._messageHandler&&this._port.onMessage.removeListener(this._messageHandler),this._disconnectHandler&&this._port.onDisconnect.removeListener(this._disconnectHandler));}};var h="mcp",l=class{_socket;_url;_WebSocket;onclose;onerror;onmessage;constructor(e){this._url=e;}async start(){if(this._socket)throw new Error("WebSocketClientTransport already started! If using Client class, note that connect() calls start() automatically.");if(!this._WebSocket)try{let e=await import('ws');this._WebSocket=e.WebSocket;}catch{throw new Error("Failed to import 'ws' package. Please install it with: npm install ws")}return new Promise((e,r)=>{this._socket=new this._WebSocket(this._url.toString(),{perMessageDeflate:false,...{protocol:h}}),this._socket.on("error",t=>{r(t),this.onerror?.(t);}),this._socket.on("open",()=>{e();}),this._socket.on("close",()=>{this.onclose?.();}),this._socket.on("message",t=>{let s;try{let o=t instanceof Buffer?t.toString("utf-8"):t.toString();s=JSONRPCMessageSchema.parse(JSON.parse(o));}catch(o){this.onerror?.(o);return}this.onmessage?.(s);});})}async close(){this._socket&&this._WebSocket&&this._socket.readyState===this._WebSocket.OPEN&&this._socket.close();}async send(e){return new Promise((r,t)=>{if(!this._socket||!this._WebSocket){t(new Error("Not connected"));return}if(this._socket.readyState!==this._WebSocket.OPEN){t(new Error("WebSocket is not open"));return}this._socket.send(JSON.stringify(e),s=>{s?t(s):r();});})}};var p=class{constructor(e){this._options=e;}_socket;_server;_started=false;onclose;onerror;onmessage;async start(){if(this._started)throw new Error("WebSocketServerTransport already started! If using Server class, note that connect() calls start() automatically.");if(this._started=true,this._options.socket)this._socket=this._options.socket,this.setupSocketHandlers(this._socket);else throw new Error("WebSocketServerTransport requires either a socket ")}setupSocketHandlers(e){e.onmessage=r=>{let t;try{let s=typeof r.data=="string"?r.data:r.data.toString();t=JSONRPCMessageSchema.parse(JSON.parse(s));}catch(s){this.onerror?.(s);return}this.onmessage?.(t);},e.onerror=r=>{let t=new Error(`WebSocket error: ${JSON.stringify(r)}`);this.onerror?.(t);},e.onclose=()=>{this._socket=void 0,this.onclose?.();};}async close(){if(this._socket&&(this._socket.close(),this._socket=void 0),this._server)return new Promise(e=>{this._server.close(()=>{this._server=void 0,e();});})}send(e){return new Promise((r,t)=>{if(!this._socket||this._socket.readyState!==WebSocket.OPEN){t(new Error("No active WebSocket connection"));return}try{this._socket.send(JSON.stringify(e)),r();}catch(s){t(s);}})}get socket(){return this._socket}get isConnected(){return this._socket?.readyState===WebSocket.OPEN}};export{c as ExtensionClientTransport,d as ExtensionServerTransport,n as TabClientTransport,a as TabServerTransport,l as WebSocketClientTransport,p as WebSocketServerTransport};//# sourceMappingURL=index.js.map
|
|
2
2
|
//# sourceMappingURL=index.js.map
|