@mcp-web/bridge 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.
Files changed (72) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +311 -0
  3. package/dist/adapters/bun.d.ts +95 -0
  4. package/dist/adapters/bun.d.ts.map +1 -0
  5. package/dist/adapters/bun.js +286 -0
  6. package/dist/adapters/bun.js.map +1 -0
  7. package/dist/adapters/deno.d.ts +89 -0
  8. package/dist/adapters/deno.d.ts.map +1 -0
  9. package/dist/adapters/deno.js +249 -0
  10. package/dist/adapters/deno.js.map +1 -0
  11. package/dist/adapters/index.d.ts +21 -0
  12. package/dist/adapters/index.d.ts.map +1 -0
  13. package/dist/adapters/index.js +21 -0
  14. package/dist/adapters/index.js.map +1 -0
  15. package/dist/adapters/node.d.ts +112 -0
  16. package/dist/adapters/node.d.ts.map +1 -0
  17. package/dist/adapters/node.js +309 -0
  18. package/dist/adapters/node.js.map +1 -0
  19. package/dist/adapters/partykit.d.ts +153 -0
  20. package/dist/adapters/partykit.d.ts.map +1 -0
  21. package/dist/adapters/partykit.js +372 -0
  22. package/dist/adapters/partykit.js.map +1 -0
  23. package/dist/bridge.d.ts +38 -0
  24. package/dist/bridge.d.ts.map +1 -0
  25. package/dist/bridge.js +1004 -0
  26. package/dist/bridge.js.map +1 -0
  27. package/dist/core.d.ts +75 -0
  28. package/dist/core.d.ts.map +1 -0
  29. package/dist/core.js +1508 -0
  30. package/dist/core.js.map +1 -0
  31. package/dist/index.d.ts +38 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +42 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/runtime/index.d.ts +11 -0
  36. package/dist/runtime/index.d.ts.map +1 -0
  37. package/dist/runtime/index.js +9 -0
  38. package/dist/runtime/index.js.map +1 -0
  39. package/dist/runtime/scheduler.d.ts +69 -0
  40. package/dist/runtime/scheduler.d.ts.map +1 -0
  41. package/dist/runtime/scheduler.js +88 -0
  42. package/dist/runtime/scheduler.js.map +1 -0
  43. package/dist/runtime/types.d.ts +144 -0
  44. package/dist/runtime/types.d.ts.map +1 -0
  45. package/dist/runtime/types.js +82 -0
  46. package/dist/runtime/types.js.map +1 -0
  47. package/dist/schemas.d.ts +6 -0
  48. package/dist/schemas.d.ts.map +1 -0
  49. package/dist/schemas.js +6 -0
  50. package/dist/schemas.js.map +1 -0
  51. package/dist/types.d.ts +130 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +2 -0
  54. package/dist/types.js.map +1 -0
  55. package/package.json +28 -0
  56. package/src/adapters/bun.ts +354 -0
  57. package/src/adapters/deno.ts +282 -0
  58. package/src/adapters/index.ts +28 -0
  59. package/src/adapters/node.ts +385 -0
  60. package/src/adapters/partykit.ts +482 -0
  61. package/src/bridge.test.ts +64 -0
  62. package/src/core.ts +2176 -0
  63. package/src/index.ts +90 -0
  64. package/src/limits.test.ts +436 -0
  65. package/src/remote-mcp.test.ts +770 -0
  66. package/src/runtime/index.ts +24 -0
  67. package/src/runtime/scheduler.ts +130 -0
  68. package/src/runtime/types.ts +229 -0
  69. package/src/schemas.ts +6 -0
  70. package/src/session-naming.test.ts +443 -0
  71. package/src/types.ts +180 -0
  72. package/tsconfig.json +12 -0
@@ -0,0 +1,6 @@
1
+ import { z } from 'zod';
2
+ export declare const MissingAuthenticationErrorMessage = "Missing authentication";
3
+ export declare const MissingAuthenticationErrorMessageSchema: z.ZodLiteral<"Missing authentication">;
4
+ export declare const InvalidAuthenticationErrorMessage = "Invalid authentication";
5
+ export declare const InvalidAuthenticationErrorMessageSchema: z.ZodLiteral<"Invalid authentication">;
6
+ //# sourceMappingURL=schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,iCAAiC,2BAA2B,CAAC;AAC1E,eAAO,MAAM,uCAAuC,wCAA+C,CAAC;AACpG,eAAO,MAAM,iCAAiC,2BAA2B,CAAC;AAC1E,eAAO,MAAM,uCAAuC,wCAA+C,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { z } from 'zod';
2
+ export const MissingAuthenticationErrorMessage = 'Missing authentication';
3
+ export const MissingAuthenticationErrorMessageSchema = z.literal(MissingAuthenticationErrorMessage);
4
+ export const InvalidAuthenticationErrorMessage = 'Invalid authentication';
5
+ export const InvalidAuthenticationErrorMessageSchema = z.literal(InvalidAuthenticationErrorMessage);
6
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,iCAAiC,GAAG,wBAAwB,CAAC;AAC1E,MAAM,CAAC,MAAM,uCAAuC,GAAG,CAAC,CAAC,OAAO,CAAC,iCAAiC,CAAC,CAAC;AACpG,MAAM,CAAC,MAAM,iCAAiC,GAAG,wBAAwB,CAAC;AAC1E,MAAM,CAAC,MAAM,uCAAuC,GAAG,CAAC,CAAC,OAAO,CAAC,iCAAiC,CAAC,CAAC"}
@@ -0,0 +1,130 @@
1
+ import type { McpRequestMetaParams, QueryAcceptedMessage, QueryCancelMessage, QueryCompleteBridgeMessage, QueryCompleteClientMessage, QueryFailureMessage, QueryMessage, QueryProgressMessage, ResourceMetadata, ToolMetadata } from '@mcp-web/types';
2
+ import type * as WS from 'ws';
3
+ import type { z } from 'zod';
4
+ export interface AuthenticateMessage {
5
+ type: 'authenticate';
6
+ sessionId: string;
7
+ authToken: string;
8
+ origin: string;
9
+ pageTitle?: string;
10
+ sessionName?: string;
11
+ userAgent?: string;
12
+ timestamp: number;
13
+ }
14
+ export interface AuthenticatedMessage {
15
+ type: 'authenticated';
16
+ mcpPort?: number;
17
+ sessionId: string;
18
+ success: boolean;
19
+ }
20
+ export interface AuthenticationFailedMessage {
21
+ type: 'authentication-failed';
22
+ error: string;
23
+ code: string;
24
+ }
25
+ export interface RegisterToolMessage {
26
+ type: 'register-tool';
27
+ tool: {
28
+ name: string;
29
+ description: string;
30
+ inputSchema?: z.core.JSONSchema.JSONSchema;
31
+ outputSchema?: z.core.JSONSchema.JSONSchema;
32
+ _meta?: Record<string, unknown>;
33
+ };
34
+ }
35
+ export interface RegisterResourceMessage {
36
+ type: 'register-resource';
37
+ resource: ResourceMetadata;
38
+ }
39
+ export interface ResourceReadMessage {
40
+ type: 'resource-read';
41
+ requestId: string;
42
+ uri: string;
43
+ }
44
+ export interface ResourceResponseMessage {
45
+ type: 'resource-response';
46
+ requestId: string;
47
+ content?: string;
48
+ blob?: string;
49
+ mimeType: string;
50
+ error?: string;
51
+ }
52
+ export interface ActivityMessage {
53
+ type: 'activity';
54
+ timestamp: number;
55
+ }
56
+ export interface ToolCallMessage {
57
+ type: 'tool-call';
58
+ requestId: string;
59
+ toolName: string;
60
+ toolInput?: Record<string, unknown>;
61
+ queryId?: string;
62
+ }
63
+ export interface ToolRegistrationErrorMessage {
64
+ type: 'tool-registration-error';
65
+ toolName: string;
66
+ error: string;
67
+ message: string;
68
+ }
69
+ export interface ToolResponseMessage {
70
+ type: 'tool-response';
71
+ requestId: string;
72
+ result: unknown;
73
+ }
74
+ export type FrontendMessage = AuthenticateMessage | RegisterToolMessage | RegisterResourceMessage | ActivityMessage | ToolResponseMessage | ResourceResponseMessage | QueryMessage | QueryCompleteClientMessage | QueryProgressMessage | QueryCancelMessage;
75
+ export type BridgeMessage = AuthenticatedMessage | AuthenticationFailedMessage | ToolCallMessage | ToolRegistrationErrorMessage | ResourceReadMessage | QueryAcceptedMessage | QueryProgressMessage | QueryCompleteBridgeMessage | QueryFailureMessage | QueryCancelMessage;
76
+ export interface TrackedToolCall {
77
+ tool: string;
78
+ arguments: unknown;
79
+ result: unknown;
80
+ }
81
+ export type QueryState = 'active' | 'completed' | 'failed' | 'cancelled';
82
+ export interface QueryTracking {
83
+ sessionId: string;
84
+ responseTool?: string;
85
+ toolCalls: TrackedToolCall[];
86
+ ws: WS.WebSocket;
87
+ state: QueryState;
88
+ tools?: ToolMetadata[];
89
+ restrictTools?: boolean;
90
+ }
91
+ export interface McpRequest {
92
+ jsonrpc: string;
93
+ id: string | number;
94
+ method: string;
95
+ params?: {
96
+ name?: string;
97
+ arguments?: Record<string, unknown>;
98
+ _meta?: McpRequestMetaParams;
99
+ };
100
+ }
101
+ export interface McpResponse {
102
+ jsonrpc: string;
103
+ id: string | number;
104
+ result?: unknown;
105
+ error?: {
106
+ code: number;
107
+ message: string;
108
+ data?: unknown;
109
+ };
110
+ }
111
+ export interface ToolDefinition {
112
+ name: string;
113
+ description: string;
114
+ inputSchema?: z.core.JSONSchema.JSONSchema;
115
+ outputSchema?: z.core.JSONSchema.JSONSchema;
116
+ handler?: string;
117
+ }
118
+ export interface SessionData {
119
+ ws: WS.WebSocket;
120
+ authToken: string;
121
+ origin: string;
122
+ pageTitle?: string;
123
+ sessionName?: string;
124
+ userAgent?: string;
125
+ connectedAt: number;
126
+ lastActivity: number;
127
+ tools: Map<string, ToolDefinition>;
128
+ resources: Map<string, ResourceMetadata>;
129
+ }
130
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@mcp-web/bridge",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "exports": {
7
+ ".": "./dist/index.js",
8
+ "./bridge": "./dist/bridge.js"
9
+ },
10
+ "dependencies": {
11
+ "@modelcontextprotocol/sdk": "^1.24.0",
12
+ "ws": "^8.18.0",
13
+ "zod": "~4.1.12",
14
+ "@mcp-web/types": "0.1.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/node": "^25.0.9",
18
+ "@types/ws": "^8.5.13",
19
+ "typescript": "~5.9.3"
20
+ },
21
+ "scripts": {
22
+ "build": "tsc",
23
+ "dev": "tsc --watch",
24
+ "clean": "rm -rf dist",
25
+ "start": "node dist/index.js",
26
+ "test": "bun test"
27
+ }
28
+ }
@@ -0,0 +1,354 @@
1
+ /**
2
+ * MCPWebBridgeBun - Bun adapter for the MCP Web Bridge.
3
+ *
4
+ * Uses Bun.serve() with built-in WebSocket support for a single-port server.
5
+ * Bun's server natively handles both HTTP and WebSocket on the same port.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // server.ts
10
+ * import { MCPWebBridgeBun } from '@mcp-web/bridge';
11
+ *
12
+ * const bridge = new MCPWebBridgeBun({
13
+ * name: 'My App',
14
+ * description: 'My awesome app',
15
+ * port: 3001,
16
+ * });
17
+ *
18
+ * // Bridge is now listening on ws://localhost:3001 and http://localhost:3001
19
+ * ```
20
+ *
21
+ * @example Production deployment
22
+ * ```typescript
23
+ * import { MCPWebBridgeBun } from '@mcp-web/bridge';
24
+ *
25
+ * const bridge = new MCPWebBridgeBun({
26
+ * name: 'Production Bridge',
27
+ * description: 'Production MCP Web bridge server',
28
+ * port: Number(process.env.PORT) || 3001,
29
+ * hostname: '0.0.0.0',
30
+ * });
31
+ *
32
+ * // Graceful shutdown
33
+ * process.on('SIGTERM', async () => {
34
+ * await bridge.close();
35
+ * process.exit(0);
36
+ * });
37
+ * ```
38
+ *
39
+ * @remarks
40
+ * This adapter requires Bun runtime (https://bun.sh).
41
+ * Bun's `Bun.serve()` provides excellent performance with native WebSocket support.
42
+ *
43
+ * Key features:
44
+ * - Single port for HTTP and WebSocket
45
+ * - Native TypeScript support
46
+ * - High performance HTTP and WebSocket handling
47
+ *
48
+ * @see https://bun.sh/docs/api/http
49
+ * @see https://bun.sh/docs/api/websockets
50
+ */
51
+
52
+ import type { MCPWebConfig } from '@mcp-web/types';
53
+ import { MCPWebBridge } from '../core.js';
54
+ import { TimerScheduler } from '../runtime/scheduler.js';
55
+ import type { HttpRequest, WebSocketConnection } from '../runtime/types.js';
56
+ import { isSSEResponse } from '../runtime/types.js';
57
+
58
+ /**
59
+ * Configuration for the Bun bridge adapter.
60
+ */
61
+ export interface MCPWebBridgeBunConfig extends Omit<MCPWebConfig, 'bridgeUrl'> {
62
+ /** Port to listen on (default: 3001) */
63
+ port?: number;
64
+
65
+ /** Hostname to bind to (default: '0.0.0.0') */
66
+ hostname?: string;
67
+ }
68
+
69
+ /**
70
+ * WebSocket data attached to each connection for session tracking.
71
+ */
72
+ interface WebSocketData {
73
+ sessionId: string;
74
+ wrapped: WebSocketConnection;
75
+ messageHandlers: Set<(data: string) => void>;
76
+ // biome-ignore lint/suspicious/noExplicitAny: Bun's ServerWebSocket type varies
77
+ setSocket: (socket: any) => void;
78
+ handlers: ReturnType<MCPWebBridge['getHandlers']>;
79
+ url: URL;
80
+ }
81
+
82
+ /**
83
+ * Wraps a Bun ServerWebSocket in our runtime-agnostic interface.
84
+ */
85
+ function createBunWebSocketWrapper(): {
86
+ wrapped: WebSocketConnection;
87
+ messageHandlers: Set<(data: string) => void>;
88
+ // biome-ignore lint/suspicious/noExplicitAny: Bun's ServerWebSocket type varies
89
+ setSocket: (socket: any) => void;
90
+ } {
91
+ const messageHandlers = new Set<(data: string) => void>();
92
+ // biome-ignore lint/suspicious/noExplicitAny: Bun's ServerWebSocket type varies
93
+ let wsSocket: any = null;
94
+
95
+ const wrapped: WebSocketConnection = {
96
+ send(data: string): void {
97
+ if (wsSocket?.readyState === 1) {
98
+ // OPEN
99
+ wsSocket.send(data);
100
+ }
101
+ },
102
+
103
+ close(code?: number, reason?: string): void {
104
+ wsSocket?.close(code, reason);
105
+ },
106
+
107
+ get readyState(): 'CONNECTING' | 'OPEN' | 'CLOSING' | 'CLOSED' {
108
+ if (!wsSocket) return 'CLOSED';
109
+ switch (wsSocket.readyState) {
110
+ case 0:
111
+ return 'CONNECTING';
112
+ case 1:
113
+ return 'OPEN';
114
+ case 2:
115
+ return 'CLOSING';
116
+ case 3:
117
+ default:
118
+ return 'CLOSED';
119
+ }
120
+ },
121
+
122
+ onMessage(handler: (data: string) => void): void {
123
+ messageHandlers.add(handler);
124
+ },
125
+
126
+ offMessage(handler: (data: string) => void): void {
127
+ messageHandlers.delete(handler);
128
+ },
129
+ };
130
+
131
+ return {
132
+ wrapped,
133
+ messageHandlers,
134
+ // biome-ignore lint/suspicious/noExplicitAny: Bun's ServerWebSocket type varies
135
+ setSocket: (socket: any) => {
136
+ wsSocket = socket;
137
+ },
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Wraps a Bun Request in our runtime-agnostic HttpRequest interface.
143
+ */
144
+ function wrapBunRequest(req: Request): HttpRequest {
145
+ return {
146
+ method: req.method,
147
+ url: req.url,
148
+ headers: {
149
+ get(name: string): string | null {
150
+ return req.headers.get(name);
151
+ },
152
+ },
153
+ text(): Promise<string> {
154
+ return req.text();
155
+ },
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Bun adapter for MCPWebBridge.
161
+ * Provides a single-port server using Bun.serve() with native WebSocket support.
162
+ *
163
+ * @example
164
+ * ```typescript
165
+ * const bridge = new MCPWebBridgeBun({
166
+ * name: 'My App',
167
+ * description: 'My app',
168
+ * port: 3001,
169
+ * });
170
+ * ```
171
+ */
172
+ export class MCPWebBridgeBun {
173
+ #core: MCPWebBridge;
174
+ // biome-ignore lint/suspicious/noExplicitAny: Bun.Server type varies
175
+ #server: any;
176
+ #port: number;
177
+ #hostname: string;
178
+
179
+ constructor(config: MCPWebBridgeBunConfig) {
180
+ this.#port = config.port ?? 3001;
181
+ this.#hostname = config.hostname ?? '0.0.0.0';
182
+
183
+ // Create the core with a timer-based scheduler
184
+ const scheduler = new TimerScheduler();
185
+ this.#core = new MCPWebBridge(config, scheduler);
186
+ const handlers = this.#core.getHandlers();
187
+
188
+ // Start Bun server with WebSocket support
189
+ // biome-ignore lint/suspicious/noExplicitAny: Bun global is runtime-specific
190
+ this.#server = (globalThis as any).Bun.serve({
191
+ port: this.#port,
192
+ hostname: this.#hostname,
193
+
194
+ // Handle HTTP requests
195
+ fetch: async (req: Request, server: { upgrade: (req: Request, options: { data: WebSocketData }) => boolean }) => {
196
+ const url = new URL(req.url);
197
+
198
+ // Handle WebSocket upgrade
199
+ if (req.headers.get('upgrade')?.toLowerCase() === 'websocket') {
200
+ const sessionId = url.searchParams.get('session');
201
+
202
+ if (!sessionId) {
203
+ return new Response('Missing session parameter', { status: 400 });
204
+ }
205
+
206
+ const { wrapped, messageHandlers, setSocket } = createBunWebSocketWrapper();
207
+
208
+ const success = server.upgrade(req, {
209
+ data: {
210
+ sessionId,
211
+ wrapped,
212
+ // Store these for the websocket handlers
213
+ messageHandlers,
214
+ setSocket,
215
+ handlers,
216
+ url,
217
+ } as WebSocketData,
218
+ });
219
+
220
+ if (success) {
221
+ return undefined as unknown as Response; // Bun handles the upgrade
222
+ }
223
+
224
+ return new Response('WebSocket upgrade failed', { status: 500 });
225
+ }
226
+
227
+ // Handle regular HTTP requests
228
+ const wrappedReq = wrapBunRequest(req);
229
+ const httpResponse = await handlers.onHttpRequest(wrappedReq);
230
+
231
+ // Check if this is an SSE response
232
+ if (isSSEResponse(httpResponse)) {
233
+ // Create a ReadableStream for SSE
234
+ const stream = new ReadableStream({
235
+ start(controller) {
236
+ // Create writer function that sends SSE-formatted data
237
+ const writer = (data: string): void => {
238
+ controller.enqueue(new TextEncoder().encode(`data: ${data}\n\n`));
239
+ };
240
+
241
+ // Set up the SSE stream
242
+ httpResponse.setup(writer, () => {
243
+ controller.close();
244
+ });
245
+
246
+ // Keep connection alive with periodic comments
247
+ const keepAlive = setInterval(() => {
248
+ try {
249
+ controller.enqueue(new TextEncoder().encode(': keepalive\n\n'));
250
+ } catch {
251
+ clearInterval(keepAlive);
252
+ }
253
+ }, 30000);
254
+ },
255
+ });
256
+
257
+ return new Response(stream, {
258
+ status: httpResponse.status,
259
+ headers: httpResponse.headers,
260
+ });
261
+ }
262
+
263
+ return new Response(httpResponse.body, {
264
+ status: httpResponse.status,
265
+ headers: httpResponse.headers,
266
+ });
267
+ },
268
+
269
+ // WebSocket handlers
270
+ websocket: {
271
+ // biome-ignore lint/suspicious/noExplicitAny: Bun's ServerWebSocket type varies
272
+ open(ws: any) {
273
+ const data = ws.data as WebSocketData & {
274
+ messageHandlers: Set<(data: string) => void>;
275
+ // biome-ignore lint/suspicious/noExplicitAny: Bun's ServerWebSocket type varies
276
+ setSocket: (socket: any) => void;
277
+ handlers: ReturnType<MCPWebBridge['getHandlers']>;
278
+ url: URL;
279
+ };
280
+
281
+ // Connect the socket to the wrapper
282
+ data.setSocket(ws);
283
+
284
+ // Notify the bridge
285
+ data.handlers.onWebSocketConnect(data.sessionId, data.wrapped, data.url);
286
+ },
287
+
288
+ // biome-ignore lint/suspicious/noExplicitAny: Bun's ServerWebSocket type varies
289
+ message(ws: any, message: string | Buffer) {
290
+ const data = ws.data as WebSocketData & {
291
+ messageHandlers: Set<(data: string) => void>;
292
+ handlers: ReturnType<MCPWebBridge['getHandlers']>;
293
+ };
294
+
295
+ const str = typeof message === 'string' ? message : message.toString();
296
+
297
+ // Notify message handlers
298
+ for (const handler of data.messageHandlers) {
299
+ handler(str);
300
+ }
301
+
302
+ // Notify the bridge
303
+ data.handlers.onWebSocketMessage(data.sessionId, data.wrapped, str);
304
+ },
305
+
306
+ // biome-ignore lint/suspicious/noExplicitAny: Bun's ServerWebSocket type varies
307
+ close(ws: any) {
308
+ const data = ws.data as WebSocketData & {
309
+ handlers: ReturnType<MCPWebBridge['getHandlers']>;
310
+ };
311
+
312
+ data.handlers.onWebSocketClose(data.sessionId);
313
+ },
314
+
315
+ },
316
+ });
317
+
318
+ console.log(`🌉 MCP Web Bridge (Bun) listening on ${this.#hostname}:${this.#port}`);
319
+ console.log(` WebSocket: ws://${this.#hostname === '0.0.0.0' ? 'localhost' : this.#hostname}:${this.#port}`);
320
+ console.log(` HTTP/MCP: http://${this.#hostname === '0.0.0.0' ? 'localhost' : this.#hostname}:${this.#port}`);
321
+ }
322
+
323
+ /**
324
+ * Get the underlying MCPWebBridge core instance.
325
+ */
326
+ get core(): MCPWebBridge {
327
+ return this.#core;
328
+ }
329
+
330
+ /**
331
+ * Get the bridge handlers for custom integrations.
332
+ */
333
+ getHandlers() {
334
+ return this.#core.getHandlers();
335
+ }
336
+
337
+ /**
338
+ * Get the port the server is listening on.
339
+ */
340
+ get port(): number {
341
+ return this.#port;
342
+ }
343
+
344
+ /**
345
+ * Gracefully shut down the bridge.
346
+ */
347
+ async close(): Promise<void> {
348
+ // Stop the server
349
+ this.#server?.stop();
350
+
351
+ // Close the core (cleans up sessions, timers)
352
+ await this.#core.close();
353
+ }
354
+ }