@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,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime abstraction layer for MCP Web Bridge.
|
|
3
|
+
*
|
|
4
|
+
* This module provides interfaces and utilities that allow the bridge
|
|
5
|
+
* to work across different JavaScript runtimes (Node.js, Deno, Bun, PartyKit).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type {
|
|
9
|
+
BridgeAdapterConfig,
|
|
10
|
+
BridgeHandlers,
|
|
11
|
+
HttpRequest,
|
|
12
|
+
HttpResponse,
|
|
13
|
+
WebSocketConnection,
|
|
14
|
+
} from './types.js';
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
createHttpResponse,
|
|
18
|
+
jsonResponse,
|
|
19
|
+
readyStateToString,
|
|
20
|
+
WebSocketReadyState,
|
|
21
|
+
} from './types.js';
|
|
22
|
+
|
|
23
|
+
export type { Scheduler } from './scheduler.js';
|
|
24
|
+
export { NoopScheduler, TimerScheduler } from './scheduler.js';
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduler abstraction for timing operations.
|
|
3
|
+
*
|
|
4
|
+
* Different runtimes have different timing APIs:
|
|
5
|
+
* - Node.js/Deno/Bun: setTimeout/setInterval
|
|
6
|
+
* - PartyKit/Cloudflare: Alarms via Party.storage.setAlarm()
|
|
7
|
+
*
|
|
8
|
+
* This abstraction allows the bridge core to schedule tasks
|
|
9
|
+
* without knowing which runtime it's running on.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Abstract scheduler interface.
|
|
14
|
+
* Implementations handle the runtime-specific timing mechanism.
|
|
15
|
+
*/
|
|
16
|
+
export interface Scheduler {
|
|
17
|
+
/**
|
|
18
|
+
* Schedule a one-time callback after a delay.
|
|
19
|
+
* @param callback - Function to execute
|
|
20
|
+
* @param delayMs - Delay in milliseconds
|
|
21
|
+
* @returns An ID that can be used to cancel the scheduled callback
|
|
22
|
+
*/
|
|
23
|
+
schedule(callback: () => void, delayMs: number): string;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Cancel a scheduled one-time callback.
|
|
27
|
+
* @param id - The ID returned by schedule()
|
|
28
|
+
*/
|
|
29
|
+
cancel(id: string): void;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Schedule a repeating callback.
|
|
33
|
+
* @param callback - Function to execute repeatedly
|
|
34
|
+
* @param intervalMs - Interval in milliseconds
|
|
35
|
+
* @returns An ID that can be used to cancel the interval
|
|
36
|
+
*/
|
|
37
|
+
scheduleInterval(callback: () => void, intervalMs: number): string;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Cancel a repeating callback.
|
|
41
|
+
* @param id - The ID returned by scheduleInterval()
|
|
42
|
+
*/
|
|
43
|
+
cancelInterval(id: string): void;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Clean up all scheduled tasks.
|
|
47
|
+
* Called when the bridge is shutting down.
|
|
48
|
+
*/
|
|
49
|
+
dispose(): void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Timer-based scheduler using setTimeout/setInterval.
|
|
54
|
+
* Works with Node.js, Deno, and Bun.
|
|
55
|
+
*/
|
|
56
|
+
export class TimerScheduler implements Scheduler {
|
|
57
|
+
#timeouts = new Map<string, ReturnType<typeof setTimeout>>();
|
|
58
|
+
#intervals = new Map<string, ReturnType<typeof setInterval>>();
|
|
59
|
+
|
|
60
|
+
schedule(callback: () => void, delayMs: number): string {
|
|
61
|
+
const id = crypto.randomUUID();
|
|
62
|
+
const timeout = setTimeout(() => {
|
|
63
|
+
this.#timeouts.delete(id);
|
|
64
|
+
callback();
|
|
65
|
+
}, delayMs);
|
|
66
|
+
this.#timeouts.set(id, timeout);
|
|
67
|
+
return id;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
cancel(id: string): void {
|
|
71
|
+
const timeout = this.#timeouts.get(id);
|
|
72
|
+
if (timeout) {
|
|
73
|
+
clearTimeout(timeout);
|
|
74
|
+
this.#timeouts.delete(id);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
scheduleInterval(callback: () => void, intervalMs: number): string {
|
|
79
|
+
const id = crypto.randomUUID();
|
|
80
|
+
const interval = setInterval(callback, intervalMs);
|
|
81
|
+
this.#intervals.set(id, interval);
|
|
82
|
+
return id;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
cancelInterval(id: string): void {
|
|
86
|
+
const interval = this.#intervals.get(id);
|
|
87
|
+
if (interval) {
|
|
88
|
+
clearInterval(interval);
|
|
89
|
+
this.#intervals.delete(id);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
dispose(): void {
|
|
94
|
+
for (const timeout of this.#timeouts.values()) {
|
|
95
|
+
clearTimeout(timeout);
|
|
96
|
+
}
|
|
97
|
+
this.#timeouts.clear();
|
|
98
|
+
|
|
99
|
+
for (const interval of this.#intervals.values()) {
|
|
100
|
+
clearInterval(interval);
|
|
101
|
+
}
|
|
102
|
+
this.#intervals.clear();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* No-op scheduler for environments where timing isn't needed or supported.
|
|
108
|
+
* Callbacks are simply not executed.
|
|
109
|
+
*/
|
|
110
|
+
export class NoopScheduler implements Scheduler {
|
|
111
|
+
schedule(_callback: () => void, _delayMs: number): string {
|
|
112
|
+
return crypto.randomUUID();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
cancel(_id: string): void {
|
|
116
|
+
// No-op
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
scheduleInterval(_callback: () => void, _intervalMs: number): string {
|
|
120
|
+
return crypto.randomUUID();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
cancelInterval(_id: string): void {
|
|
124
|
+
// No-op
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
dispose(): void {
|
|
128
|
+
// No-op
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime-agnostic types for the MCP Web Bridge.
|
|
3
|
+
* These interfaces allow the bridge core to work with any JavaScript runtime
|
|
4
|
+
* (Node.js, Deno, Bun, Cloudflare Workers/PartyKit).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Runtime-agnostic WebSocket connection.
|
|
9
|
+
* Wraps the native WebSocket implementation of each runtime.
|
|
10
|
+
*/
|
|
11
|
+
export interface WebSocketConnection {
|
|
12
|
+
/** Send a string message to the client */
|
|
13
|
+
send(data: string): void;
|
|
14
|
+
|
|
15
|
+
/** Close the connection */
|
|
16
|
+
close(code?: number, reason?: string): void;
|
|
17
|
+
|
|
18
|
+
/** Current connection state */
|
|
19
|
+
readonly readyState: 'CONNECTING' | 'OPEN' | 'CLOSING' | 'CLOSED';
|
|
20
|
+
|
|
21
|
+
/** Add a message handler */
|
|
22
|
+
onMessage(handler: (data: string) => void): void;
|
|
23
|
+
|
|
24
|
+
/** Remove a message handler */
|
|
25
|
+
offMessage(handler: (data: string) => void): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Runtime-agnostic HTTP request.
|
|
30
|
+
* Abstracts differences between Node.js IncomingMessage, Deno Request, etc.
|
|
31
|
+
*/
|
|
32
|
+
export interface HttpRequest {
|
|
33
|
+
/** HTTP method (GET, POST, PUT, etc.) */
|
|
34
|
+
readonly method: string;
|
|
35
|
+
|
|
36
|
+
/** Full URL string */
|
|
37
|
+
readonly url: string;
|
|
38
|
+
|
|
39
|
+
/** Request headers */
|
|
40
|
+
readonly headers: {
|
|
41
|
+
get(name: string): string | null;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** Get the request body as text */
|
|
45
|
+
text(): Promise<string>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Runtime-agnostic HTTP response.
|
|
50
|
+
* Used to construct responses that adapters convert to native format.
|
|
51
|
+
*/
|
|
52
|
+
export interface HttpResponse {
|
|
53
|
+
/** HTTP status code */
|
|
54
|
+
status: number;
|
|
55
|
+
|
|
56
|
+
/** Response headers */
|
|
57
|
+
headers: Record<string, string>;
|
|
58
|
+
|
|
59
|
+
/** Response body (for non-streaming responses) */
|
|
60
|
+
body: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Callback type for sending SSE events.
|
|
65
|
+
* @param data - The data to send (will be prefixed with "data: " and suffixed with "\n\n")
|
|
66
|
+
*/
|
|
67
|
+
export type SSEWriter = (data: string) => void;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* SSE stream response for server-initiated messages.
|
|
71
|
+
* Adapters handle the actual streaming; core provides the setup callback.
|
|
72
|
+
*/
|
|
73
|
+
export interface SSEResponse {
|
|
74
|
+
/** HTTP status code (always 200 for SSE) */
|
|
75
|
+
status: 200;
|
|
76
|
+
|
|
77
|
+
/** Response headers (Content-Type: text/event-stream, etc.) */
|
|
78
|
+
headers: Record<string, string>;
|
|
79
|
+
|
|
80
|
+
/** Marks this as an SSE response for adapter detection */
|
|
81
|
+
isSSE: true;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Called by the adapter to set up the SSE stream.
|
|
85
|
+
* @param writer - Function to send SSE data to the client
|
|
86
|
+
* @param onClose - Called when the client disconnects
|
|
87
|
+
*/
|
|
88
|
+
setup(writer: SSEWriter, onClose: () => void): void;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Handlers that the bridge core provides to adapters.
|
|
93
|
+
* Adapters wire these up to their runtime's native APIs.
|
|
94
|
+
*/
|
|
95
|
+
export interface BridgeHandlers {
|
|
96
|
+
/**
|
|
97
|
+
* Called when a WebSocket connection is established.
|
|
98
|
+
* @param sessionId - The session ID from the URL query parameter
|
|
99
|
+
* @param ws - The wrapped WebSocket connection
|
|
100
|
+
* @param url - The parsed connection URL
|
|
101
|
+
* @returns true if the connection should be accepted
|
|
102
|
+
*/
|
|
103
|
+
onWebSocketConnect(sessionId: string, ws: WebSocketConnection, url: URL): boolean;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Called when a WebSocket message is received.
|
|
107
|
+
* @param sessionId - The session ID
|
|
108
|
+
* @param ws - The wrapped WebSocket connection
|
|
109
|
+
* @param data - The message data as a string
|
|
110
|
+
*/
|
|
111
|
+
onWebSocketMessage(sessionId: string, ws: WebSocketConnection, data: string): void;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Called when a WebSocket connection closes.
|
|
115
|
+
* @param sessionId - The session ID
|
|
116
|
+
*/
|
|
117
|
+
onWebSocketClose(sessionId: string): void;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Called for HTTP requests (MCP JSON-RPC, query endpoints, etc.)
|
|
121
|
+
* @param req - The wrapped HTTP request
|
|
122
|
+
* @returns The response to send (can be SSE for streaming)
|
|
123
|
+
*/
|
|
124
|
+
onHttpRequest(req: HttpRequest): Promise<HttpResponse | SSEResponse>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Configuration for bridge adapters.
|
|
129
|
+
* Extends the base MCPWebConfig with adapter-specific options.
|
|
130
|
+
*/
|
|
131
|
+
export interface BridgeAdapterConfig {
|
|
132
|
+
/** Port to listen on (single port for both HTTP and WebSocket) */
|
|
133
|
+
port?: number;
|
|
134
|
+
|
|
135
|
+
/** Host to bind to */
|
|
136
|
+
host?: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Helper to create an HttpResponse
|
|
141
|
+
*/
|
|
142
|
+
export function createHttpResponse(
|
|
143
|
+
status: number,
|
|
144
|
+
body: unknown,
|
|
145
|
+
headers: Record<string, string> = {}
|
|
146
|
+
): HttpResponse {
|
|
147
|
+
const isJson = typeof body === 'object';
|
|
148
|
+
return {
|
|
149
|
+
status,
|
|
150
|
+
headers: {
|
|
151
|
+
'Content-Type': isJson ? 'application/json' : 'text/plain',
|
|
152
|
+
...headers,
|
|
153
|
+
},
|
|
154
|
+
body: isJson ? JSON.stringify(body) : String(body),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Helper to create a JSON response
|
|
160
|
+
*/
|
|
161
|
+
export function jsonResponse(status: number, data: unknown): HttpResponse {
|
|
162
|
+
return createHttpResponse(status, data, {
|
|
163
|
+
'Access-Control-Allow-Origin': '*',
|
|
164
|
+
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
|
165
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization, Mcp-Session-Id',
|
|
166
|
+
'Access-Control-Expose-Headers': 'Mcp-Session-Id',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Type guard to check if a response is an SSE response
|
|
172
|
+
*/
|
|
173
|
+
export function isSSEResponse(
|
|
174
|
+
response: HttpResponse | SSEResponse
|
|
175
|
+
): response is SSEResponse {
|
|
176
|
+
return 'isSSE' in response && response.isSSE === true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Helper to create an SSE response
|
|
181
|
+
*/
|
|
182
|
+
export function sseResponse(
|
|
183
|
+
setup: (writer: SSEWriter, onClose: () => void) => void
|
|
184
|
+
): SSEResponse {
|
|
185
|
+
return {
|
|
186
|
+
status: 200,
|
|
187
|
+
headers: {
|
|
188
|
+
'Content-Type': 'text/event-stream',
|
|
189
|
+
'Cache-Control': 'no-cache',
|
|
190
|
+
'Connection': 'keep-alive',
|
|
191
|
+
'Access-Control-Allow-Origin': '*',
|
|
192
|
+
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
|
193
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization, Mcp-Session-Id',
|
|
194
|
+
'Access-Control-Expose-Headers': 'Mcp-Session-Id',
|
|
195
|
+
},
|
|
196
|
+
isSSE: true,
|
|
197
|
+
setup,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* WebSocket ready state constants (matching the WebSocket API)
|
|
203
|
+
*/
|
|
204
|
+
export const WebSocketReadyState = {
|
|
205
|
+
CONNECTING: 0,
|
|
206
|
+
OPEN: 1,
|
|
207
|
+
CLOSING: 2,
|
|
208
|
+
CLOSED: 3,
|
|
209
|
+
} as const;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Convert numeric ready state to string
|
|
213
|
+
*/
|
|
214
|
+
export function readyStateToString(
|
|
215
|
+
state: number
|
|
216
|
+
): 'CONNECTING' | 'OPEN' | 'CLOSING' | 'CLOSED' {
|
|
217
|
+
switch (state) {
|
|
218
|
+
case 0:
|
|
219
|
+
return 'CONNECTING';
|
|
220
|
+
case 1:
|
|
221
|
+
return 'OPEN';
|
|
222
|
+
case 2:
|
|
223
|
+
return 'CLOSING';
|
|
224
|
+
case 3:
|
|
225
|
+
return 'CLOSED';
|
|
226
|
+
default:
|
|
227
|
+
return 'CLOSED';
|
|
228
|
+
}
|
|
229
|
+
}
|
package/src/schemas.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const MissingAuthenticationErrorMessage = 'Missing authentication';
|
|
4
|
+
export const MissingAuthenticationErrorMessageSchema = z.literal(MissingAuthenticationErrorMessage);
|
|
5
|
+
export const InvalidAuthenticationErrorMessage = 'Invalid authentication';
|
|
6
|
+
export const InvalidAuthenticationErrorMessageSchema = z.literal(InvalidAuthenticationErrorMessage);
|