@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,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);