@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.
- package/LICENSE +201 -0
- package/README.md +311 -0
- package/dist/adapters/bun.d.ts +95 -0
- package/dist/adapters/bun.d.ts.map +1 -0
- package/dist/adapters/bun.js +286 -0
- package/dist/adapters/bun.js.map +1 -0
- package/dist/adapters/deno.d.ts +89 -0
- package/dist/adapters/deno.d.ts.map +1 -0
- package/dist/adapters/deno.js +249 -0
- package/dist/adapters/deno.js.map +1 -0
- package/dist/adapters/index.d.ts +21 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +21 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/node.d.ts +112 -0
- package/dist/adapters/node.d.ts.map +1 -0
- package/dist/adapters/node.js +309 -0
- package/dist/adapters/node.js.map +1 -0
- package/dist/adapters/partykit.d.ts +153 -0
- package/dist/adapters/partykit.d.ts.map +1 -0
- package/dist/adapters/partykit.js +372 -0
- package/dist/adapters/partykit.js.map +1 -0
- package/dist/bridge.d.ts +38 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.js +1004 -0
- package/dist/bridge.js.map +1 -0
- package/dist/core.d.ts +75 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +1508 -0
- package/dist/core.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/index.d.ts +11 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +9 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/scheduler.d.ts +69 -0
- package/dist/runtime/scheduler.d.ts.map +1 -0
- package/dist/runtime/scheduler.js +88 -0
- package/dist/runtime/scheduler.js.map +1 -0
- package/dist/runtime/types.d.ts +144 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +82 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/schemas.d.ts +6 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +6 -0
- package/dist/schemas.js.map +1 -0
- package/dist/types.d.ts +130 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +28 -0
- package/src/adapters/bun.ts +354 -0
- package/src/adapters/deno.ts +282 -0
- package/src/adapters/index.ts +28 -0
- package/src/adapters/node.ts +385 -0
- package/src/adapters/partykit.ts +482 -0
- package/src/bridge.test.ts +64 -0
- package/src/core.ts +2176 -0
- package/src/index.ts +90 -0
- package/src/limits.test.ts +436 -0
- package/src/remote-mcp.test.ts +770 -0
- package/src/runtime/index.ts +24 -0
- package/src/runtime/scheduler.ts +130 -0
- package/src/runtime/types.ts +229 -0
- package/src/schemas.ts +6 -0
- package/src/session-naming.test.ts +443 -0
- package/src/types.ts +180 -0
- 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"}
|
package/dist/schemas.js
ADDED
|
@@ -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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
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
|
+
}
|