spiceflow 1.12.2 → 1.13.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 (98) hide show
  1. package/README.md +107 -4
  2. package/dist/_node-server-unsupported.js +1 -0
  3. package/dist/_node-server-unsupported.js.map +1 -0
  4. package/dist/_node-server.d.ts.map +1 -1
  5. package/dist/_node-server.js +2 -2
  6. package/dist/_node-server.js.map +1 -0
  7. package/dist/client/errors.js +1 -0
  8. package/dist/client/errors.js.map +1 -0
  9. package/dist/client/index.d.ts +2 -2
  10. package/dist/client/index.d.ts.map +1 -1
  11. package/dist/client/index.js +7 -4
  12. package/dist/client/index.js.map +1 -0
  13. package/dist/client/types.js +1 -0
  14. package/dist/client/types.js.map +1 -0
  15. package/dist/client/utils.js +1 -0
  16. package/dist/client/utils.js.map +1 -0
  17. package/dist/client.test.js +1 -0
  18. package/dist/client.test.js.map +1 -0
  19. package/dist/context.d.ts +4 -1
  20. package/dist/context.d.ts.map +1 -1
  21. package/dist/context.js +1 -0
  22. package/dist/context.js.map +1 -0
  23. package/dist/cors.js +1 -0
  24. package/dist/cors.js.map +1 -0
  25. package/dist/cors.test.js +1 -0
  26. package/dist/cors.test.js.map +1 -0
  27. package/dist/error.js +1 -0
  28. package/dist/error.js.map +1 -0
  29. package/dist/index.d.ts +1 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/mcp-client-transport.d.ts +35 -0
  34. package/dist/mcp-client-transport.d.ts.map +1 -0
  35. package/dist/mcp-client-transport.js +147 -0
  36. package/dist/mcp-client-transport.js.map +1 -0
  37. package/dist/mcp-transport.js +1 -0
  38. package/dist/mcp-transport.js.map +1 -0
  39. package/dist/mcp.d.ts +18 -1
  40. package/dist/mcp.d.ts.map +1 -1
  41. package/dist/mcp.js +43 -224
  42. package/dist/mcp.js.map +1 -0
  43. package/dist/middleware.test.js +1 -0
  44. package/dist/middleware.test.js.map +1 -0
  45. package/dist/openapi-to-mcp.d.ts +38 -0
  46. package/dist/openapi-to-mcp.d.ts.map +1 -0
  47. package/dist/openapi-to-mcp.js +367 -0
  48. package/dist/openapi-to-mcp.js.map +1 -0
  49. package/dist/openapi.d.ts.map +1 -1
  50. package/dist/openapi.js +7 -2
  51. package/dist/openapi.js.map +1 -0
  52. package/dist/openapi.test.js +32 -31
  53. package/dist/openapi.test.js.map +1 -0
  54. package/dist/simple.benchmark.js +1 -0
  55. package/dist/simple.benchmark.js.map +1 -0
  56. package/dist/spiceflow.d.ts +5 -2
  57. package/dist/spiceflow.d.ts.map +1 -1
  58. package/dist/spiceflow.js +26 -6
  59. package/dist/spiceflow.js.map +1 -0
  60. package/dist/spiceflow.test.js +15 -3
  61. package/dist/spiceflow.test.js.map +1 -0
  62. package/dist/static-node.js +1 -0
  63. package/dist/static-node.js.map +1 -0
  64. package/dist/static.benchmark.js +1 -0
  65. package/dist/static.benchmark.js.map +1 -0
  66. package/dist/static.js +1 -0
  67. package/dist/static.js.map +1 -0
  68. package/dist/stream.test.js +1 -0
  69. package/dist/stream.test.js.map +1 -0
  70. package/dist/types.js +1 -0
  71. package/dist/types.js.map +1 -0
  72. package/dist/types.test.js +1 -0
  73. package/dist/types.test.js.map +1 -0
  74. package/dist/utils.js +1 -0
  75. package/dist/utils.js.map +1 -0
  76. package/dist/waitUntil.test.d.ts +2 -0
  77. package/dist/waitUntil.test.d.ts.map +1 -0
  78. package/dist/waitUntil.test.js +142 -0
  79. package/dist/waitUntil.test.js.map +1 -0
  80. package/dist/zod.test.js +1 -0
  81. package/dist/zod.test.js.map +1 -0
  82. package/package.json +4 -3
  83. package/src/_node-server.ts +1 -2
  84. package/src/client/index.ts +9 -7
  85. package/src/context.ts +4 -1
  86. package/src/index.ts +1 -1
  87. package/src/mcp-client-transport.ts +184 -0
  88. package/src/mcp.ts +49 -307
  89. package/src/openapi-to-mcp.ts +510 -0
  90. package/src/openapi.test.ts +31 -31
  91. package/src/openapi.ts +9 -3
  92. package/src/spiceflow.test.ts +18 -4
  93. package/src/spiceflow.ts +42 -15
  94. package/src/waitUntil.test.ts +168 -0
  95. package/dist/serialize.d.ts +0 -2
  96. package/dist/serialize.d.ts.map +0 -1
  97. package/dist/serialize.js +0 -9
  98. package/src/serialize.ts +0 -10
@@ -0,0 +1,142 @@
1
+ import { describe, test, expect, vi } from 'vitest';
2
+ import { Spiceflow } from "./spiceflow.js";
3
+ describe('waitUntil', () => {
4
+ test('waitUntil is available in handler context', async () => {
5
+ let waitUntilCalled = false;
6
+ let waitUntilPromise = null;
7
+ const mockWaitUntil = vi.fn((promise) => {
8
+ waitUntilCalled = true;
9
+ waitUntilPromise = promise;
10
+ });
11
+ const app = new Spiceflow({
12
+ waitUntil: mockWaitUntil
13
+ }).route({
14
+ method: 'GET',
15
+ path: '/test',
16
+ handler({ waitUntil }) {
17
+ expect(typeof waitUntil).toBe('function');
18
+ const backgroundTask = Promise.resolve('background work done');
19
+ waitUntil(backgroundTask);
20
+ return { success: true };
21
+ }
22
+ });
23
+ const response = await app.handle(new Request('http://localhost/test'));
24
+ const data = await response.json();
25
+ expect(data).toEqual({ success: true });
26
+ expect(waitUntilCalled).toBe(true);
27
+ expect(mockWaitUntil).toHaveBeenCalledTimes(1);
28
+ expect(waitUntilPromise).toBeInstanceOf(Promise);
29
+ });
30
+ test('waitUntil defaults to noop when not provided', async () => {
31
+ const app = new Spiceflow().route({
32
+ method: 'GET',
33
+ path: '/test',
34
+ handler({ waitUntil }) {
35
+ expect(typeof waitUntil).toBe('function');
36
+ // Should not throw when called
37
+ waitUntil(Promise.resolve('test'));
38
+ return { success: true };
39
+ }
40
+ });
41
+ const response = await app.handle(new Request('http://localhost/test'));
42
+ const data = await response.json();
43
+ expect(data).toEqual({ success: true });
44
+ });
45
+ test('waitUntil can be called multiple times', async () => {
46
+ const mockWaitUntil = vi.fn();
47
+ const app = new Spiceflow({
48
+ waitUntil: mockWaitUntil
49
+ }).route({
50
+ method: 'POST',
51
+ path: '/multi',
52
+ handler({ waitUntil }) {
53
+ waitUntil(Promise.resolve('task 1'));
54
+ waitUntil(Promise.resolve('task 2'));
55
+ waitUntil(Promise.resolve('task 3'));
56
+ return { taskCount: 3 };
57
+ }
58
+ });
59
+ const response = await app.handle(new Request('http://localhost/multi', {
60
+ method: 'POST'
61
+ }));
62
+ const data = await response.json();
63
+ expect(data).toEqual({ taskCount: 3 });
64
+ expect(mockWaitUntil).toHaveBeenCalledTimes(3);
65
+ });
66
+ test('waitUntil works with middleware context', async () => {
67
+ const mockWaitUntil = vi.fn();
68
+ const app = new Spiceflow({
69
+ waitUntil: mockWaitUntil
70
+ })
71
+ .use(({ waitUntil }, next) => {
72
+ expect(typeof waitUntil).toBe('function');
73
+ waitUntil(Promise.resolve('middleware task'));
74
+ return next();
75
+ })
76
+ .route({
77
+ method: 'GET',
78
+ path: '/middleware',
79
+ handler({ waitUntil }) {
80
+ waitUntil(Promise.resolve('handler task'));
81
+ return { success: true };
82
+ }
83
+ });
84
+ const response = await app.handle(new Request('http://localhost/middleware'));
85
+ const data = await response.json();
86
+ expect(data).toEqual({ success: true });
87
+ expect(mockWaitUntil).toHaveBeenCalledTimes(2);
88
+ });
89
+ test('waitUntil uses global waitUntil when available', async () => {
90
+ // Mock global waitUntil
91
+ const originalGlobal = globalThis;
92
+ const mockGlobalWaitUntil = vi.fn();
93
+ originalGlobal.waitUntil = mockGlobalWaitUntil;
94
+ try {
95
+ const app = new Spiceflow().route({
96
+ method: 'GET',
97
+ path: '/global',
98
+ handler({ waitUntil }) {
99
+ waitUntil(Promise.resolve('using global'));
100
+ return { usingGlobal: true };
101
+ }
102
+ });
103
+ const response = await app.handle(new Request('http://localhost/global'));
104
+ const data = await response.json();
105
+ expect(data).toEqual({ usingGlobal: true });
106
+ expect(mockGlobalWaitUntil).toHaveBeenCalledTimes(1);
107
+ }
108
+ finally {
109
+ // Clean up
110
+ delete originalGlobal.waitUntil;
111
+ }
112
+ });
113
+ test('custom waitUntil overrides global waitUntil', async () => {
114
+ // Mock global waitUntil
115
+ const originalGlobal = globalThis;
116
+ const mockGlobalWaitUntil = vi.fn();
117
+ const mockCustomWaitUntil = vi.fn();
118
+ originalGlobal.waitUntil = mockGlobalWaitUntil;
119
+ try {
120
+ const app = new Spiceflow({
121
+ waitUntil: mockCustomWaitUntil
122
+ }).route({
123
+ method: 'GET',
124
+ path: '/custom',
125
+ handler({ waitUntil }) {
126
+ waitUntil(Promise.resolve('using custom'));
127
+ return { usingCustom: true };
128
+ }
129
+ });
130
+ const response = await app.handle(new Request('http://localhost/custom'));
131
+ const data = await response.json();
132
+ expect(data).toEqual({ usingCustom: true });
133
+ expect(mockCustomWaitUntil).toHaveBeenCalledTimes(1);
134
+ expect(mockGlobalWaitUntil).not.toHaveBeenCalled();
135
+ }
136
+ finally {
137
+ // Clean up
138
+ delete originalGlobal.waitUntil;
139
+ }
140
+ });
141
+ });
142
+ //# sourceMappingURL=waitUntil.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"waitUntil.test.js","sourceRoot":"","sources":["../src/waitUntil.test.ts"],"names":[],"mappings":"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"}
package/dist/zod.test.js CHANGED
@@ -58,3 +58,4 @@ test('body is parsed as json', async () => {
58
58
  expect(res.status).toBe(200);
59
59
  expect(await res.json()).toEqual({ name: 'John', nameEcho: 'John' });
60
60
  });
61
+ //# sourceMappingURL=zod.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zod.test.js","sourceRoot":"","sources":["../src/zod.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAA;AAEhC,IAAI,CAAC,wBAAwB,EAAE,KAAK,IAAI,EAAE;IACxC,IAAI,IAAI,GAAG,EAAE,CAAA;IACb,MAAM,GAAG,GAAG,MAAM,IAAI,SAAS,EAAE;SAC9B,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;SAEf,IAAI,CACH,OAAO,EACP,KAAK,EAAE,CAAC,EAAE,EAAE;QACV,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAA;QACnC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAA;QAChB,mBAAmB;QACnB,IAAI,CAAC,gBAAgB,CAAA;QACrB,OAAO;YACL,IAAI;YACJ,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,UAAU;SACX,CAAA;IACH,CAAC,EACD;QACE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;YACb,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;SACjB,CAAC;QACF,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC;YACjB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;YAChB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;SACrB,CAAC;KACH,CACF;SACA,IAAI,CACH,QAAQ,EACR,KAAK,EAAE,CAAC,EAAE,EAAE;QACV,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAA;QACnC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAA;QAChB,mBAAmB;QACnB,IAAI,CAAC,gBAAgB,CAAA;QACrB,OAAO;YACL,IAAI;YACJ,QAAQ,EAAE,IAAI,CAAC,IAAI;SACpB,CAAA;IACH,CAAC,EACD;QACE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;YACb,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;SACjB,CAAC;QACF,QAAQ,EAAE;YACR,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC;gBACZ,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;gBAChB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;aACrB,CAAC;YACF,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC;gBACZ,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;aACzB,CAAC;SACH;KACF,CACF;SACA,MAAM,CACL,GAAG,CAAC,OAAO,EAAE;QACX,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;SACnC;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;KACvC,CAAC,CACH,CAAA;IACH,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC5B,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAA;AACtE,CAAC,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spiceflow",
3
- "version": "1.12.2",
3
+ "version": "1.13.0",
4
4
  "description": "Simple API framework with RPC and type safety",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -70,14 +70,15 @@
70
70
  }
71
71
  },
72
72
  "devDependencies": {
73
- "@modelcontextprotocol/sdk": "^1.12.1",
73
+ "@modelcontextprotocol/sdk": "^1.12.3",
74
+ "ai": "^4.3.16",
74
75
  "eventsource": "^3.0.5",
75
76
  "formdata-node": "^6.0.3",
76
77
  "js-base64": "^3.7.7",
77
78
  "js-yaml": "^4.1.0"
78
79
  },
79
80
  "scripts": {
80
- "build": "cp ../README.md ./README.md && rm -rf dist && tsc",
81
+ "build": "cp ../README.md ./README.md && rm -rf dist && pnpm tsc",
81
82
  "gen-openapi": "pnpm tsx scripts/openapi-fern.ts",
82
83
  "docs:dev": "pnpm run gen-openapi && fern docs dev",
83
84
  "docs:prod": "pnpm run gen-openapi && fern generate --docs --force",
@@ -6,7 +6,6 @@ import {
6
6
  } from 'node:http'
7
7
  import { AddressInfo } from 'node:net'
8
8
  import { AnySpiceflow, type Spiceflow, SpiceflowRequest } from './spiceflow.ts'
9
- import { superjsonSerialize } from './serialize.ts'
10
9
 
11
10
  export async function listenForNode(
12
11
  app: AnySpiceflow,
@@ -110,6 +109,6 @@ export async function handleForNode(
110
109
  } catch (error) {
111
110
  console.error('Error handling request:', error)
112
111
  res.statusCode = 500
113
- res.end(superjsonSerialize({ message: 'Internal Server Error' }))
112
+ res.end(JSON.stringify({ message: 'Internal Server Error' }))
114
113
  }
115
114
  }
@@ -113,7 +113,7 @@ const processHeaders = (
113
113
  }
114
114
 
115
115
  interface SSEEvent {
116
- event: string
116
+ event?: string
117
117
  data: any
118
118
  id?: string
119
119
  }
@@ -146,6 +146,7 @@ export class TextDecoderStream extends TransformStream<Uint8Array, string> {
146
146
 
147
147
  export async function* streamSSEResponse(
148
148
  response: Response,
149
+ map: (x: SSEEvent) => any,
149
150
  ): AsyncGenerator<SSEEvent> {
150
151
  const body = response.body
151
152
  if (!body) return
@@ -162,7 +163,7 @@ export async function* streamSSEResponse(
162
163
  throw new SpiceflowFetchError(500, superjsonDeserialize(event.data))
163
164
  }
164
165
  if (event) {
165
- yield tryParsingSSEJson(event.data)
166
+ yield map({ ...event, data: event.data })
166
167
  }
167
168
  }
168
169
  }
@@ -171,7 +172,7 @@ function tryParsingSSEJson(data: string): any {
171
172
  try {
172
173
  return superjsonDeserialize(JSON.parse(data))
173
174
  } catch (error) {
174
- return null
175
+ return data
175
176
  }
176
177
  }
177
178
 
@@ -405,7 +406,10 @@ const createProxy = (
405
406
 
406
407
  switch (response.headers.get('Content-Type')?.split(';')[0]) {
407
408
  case 'text/event-stream':
408
- data = streamSSEResponse(response)
409
+ data = streamSSEResponse(response, (x) => {
410
+ return tryParsingSSEJson(x.data)
411
+ })
412
+
409
413
  break
410
414
 
411
415
  case 'application/json':
@@ -463,9 +467,7 @@ const createProxy = (
463
467
  }) as any
464
468
  }
465
469
 
466
- export const createSpiceflowClient = <
467
- const App extends AnySpiceflow,
468
- >(
470
+ export const createSpiceflowClient = <const App extends AnySpiceflow>(
469
471
  domain: App | string,
470
472
  config?: SpiceflowClient.Config &
471
473
  (App extends Spiceflow<any, any, infer Singleton, any, any, any, any>
package/src/context.ts CHANGED
@@ -13,7 +13,7 @@ import type {
13
13
  GetRequestSchema,
14
14
  } from './types.ts'
15
15
 
16
- import { SpiceflowRequest } from './spiceflow.ts'
16
+ import { SpiceflowRequest, WaitUntil } from './spiceflow.ts'
17
17
 
18
18
  export type ErrorContext<
19
19
  in out Route extends RouteSchema = {},
@@ -55,6 +55,7 @@ export type ErrorContext<
55
55
  // route: string
56
56
  request: SpiceflowRequest<GetRequestSchema<Route>>
57
57
  state: Singleton['state']
58
+ waitUntil: WaitUntil
58
59
  // response: Route['response']
59
60
  }>
60
61
 
@@ -83,6 +84,7 @@ export type Context<
83
84
 
84
85
  request: SpiceflowRequest<GetRequestSchema<Route>>
85
86
  state: Singleton['state']
87
+ waitUntil: WaitUntil
86
88
  // response?: Route['response']
87
89
  }>
88
90
 
@@ -97,6 +99,7 @@ export type MiddlewareContext<
97
99
  path: string
98
100
  query?: Record<string, string | undefined>
99
101
  params?: Record<string, string | undefined>
102
+ waitUntil: WaitUntil
100
103
 
101
104
  redirect: Redirect
102
105
  // server: Server | null
package/src/index.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { Spiceflow } from './spiceflow.ts'
2
- export type { AnySpiceflow } from './spiceflow.ts'
2
+ export type { AnySpiceflow, WaitUntil } from './spiceflow.ts'
3
3
  export { ValidationError } from './error.ts'
@@ -0,0 +1,184 @@
1
+ // fetch version of https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/client/sse.ts
2
+ import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
3
+ import {
4
+ JSONRPCMessage,
5
+ JSONRPCMessageSchema,
6
+ } from '@modelcontextprotocol/sdk/types.js'
7
+ import { streamSSEResponse } from './client/index.ts'
8
+
9
+ export type SpiceflowClientTransportOptions = {
10
+ fetch?: FetchType
11
+ url: string
12
+ }
13
+
14
+ export type FetchType = (input: Request) => Promise<Response>
15
+
16
+ export class FetchMCPCLientTransport implements Transport {
17
+ private _endpoint?: URL
18
+ private sseUrl: URL
19
+ private _abortController: AbortController
20
+ fetch: FetchType
21
+ onmessage?: (message: JSONRPCMessage) => void
22
+ onerror?: (error: Error) => void = (e) => {
23
+ throw e
24
+ }
25
+ onclose?: () => void
26
+
27
+ constructor(opts: SpiceflowClientTransportOptions) {
28
+ this.fetch = opts.fetch || fetch
29
+ this.sseUrl = new URL(opts.url)
30
+ this._abortController = new AbortController()
31
+ }
32
+
33
+ onEndpointMessage: (endpoint: URL) => void = () => {}
34
+
35
+ async start(): Promise<void> {
36
+ const { promise, resolve, reject } = withResolvers<URL>()
37
+ this.consumeEvents().catch((e) => {
38
+ reject(e)
39
+ })
40
+
41
+ this.onEndpointMessage = (endpoint) => {
42
+ this._endpoint = endpoint
43
+ resolve(endpoint)
44
+ }
45
+
46
+ await promise
47
+ this.log(`finished start`)
48
+ }
49
+
50
+ log(...x: any[]) {
51
+ // console.log(...x)
52
+ //
53
+ }
54
+ async consumeEvents() {
55
+ const sseRes = await this.fetch(
56
+ new Request(this.sseUrl!.toString(), {
57
+ method: 'GET',
58
+ signal: this._abortController.signal,
59
+ headers: {
60
+ ...(await this._commonHeaders()),
61
+ accept: 'text/event-stream',
62
+ },
63
+ }),
64
+ )
65
+ if (!sseRes.ok || !sseRes.body) {
66
+ const text = sseRes.body ? await sseRes.text().catch(() => '') : ''
67
+ throw new Error(
68
+ `SSE connection failed (HTTP ${sseRes.status})\nURL: ${this.sseUrl}\nText: ${text}`,
69
+ )
70
+ }
71
+ for await (const evt of streamSSEResponse(sseRes, (x) => {
72
+ return x
73
+ }) as AsyncGenerator<{
74
+ event: string
75
+ data: any
76
+ }>) {
77
+ if (evt.event === 'endpoint') {
78
+ const url = new URL(evt.data, this.sseUrl)
79
+ if (url.origin !== this.sseUrl.origin) {
80
+ throw new Error(`Endpoint origin mismatch: ${url.origin}`)
81
+ }
82
+ this.onEndpointMessage(url)
83
+ this._endpoint = url
84
+ } else if (evt.event === 'message') {
85
+ // JSON-RPC payload
86
+ try {
87
+ const msg = JSONRPCMessageSchema.parse(JSON.parse(evt.data))
88
+ this.log(msg)
89
+ this.onmessage?.(msg)
90
+ } catch (err) {
91
+ this.onerror?.(err as Error)
92
+ }
93
+ } else {
94
+ this.log('Unknown MCP event:', evt)
95
+ }
96
+ }
97
+ this.close?.()
98
+ }
99
+ catch(err) {
100
+ this.onerror?.(err as Error)
101
+ }
102
+
103
+ private async _commonHeaders() {
104
+ const headers = {} as Record<string, string>
105
+ // if (this._authProvider) {
106
+ // const tokens = await this._authProvider.tokens()
107
+ // if (tokens) {
108
+ // headers['Authorization'] = `Bearer ${tokens.access_token}`
109
+ // }
110
+ // }
111
+ if (this._protocolVersion) {
112
+ headers['mcp-protocol-version'] = this._protocolVersion
113
+ }
114
+
115
+ return headers
116
+ }
117
+
118
+ /**
119
+ * Sends a JSON-RPC message by POSTing to the negotiated endpoint.
120
+ * Must call start() first so that endpoint is set.
121
+ */
122
+ async send(message: JSONRPCMessage): Promise<void> {
123
+ if (!this._endpoint) {
124
+ throw new Error('Not connected')
125
+ }
126
+ this.log(`sending`, message)
127
+
128
+ const res = await this.fetch(
129
+ new Request(this._endpoint.toString(), {
130
+ method: 'POST',
131
+ headers: {
132
+ ...(await this._commonHeaders()),
133
+ 'content-type': 'application/json',
134
+ },
135
+ body: JSON.stringify(message),
136
+ signal: this._abortController?.signal,
137
+ }),
138
+ )
139
+ if (!res.ok) {
140
+ const text = await res.text().catch(() => '')
141
+ const err = new Error(
142
+ `Error POSTing to endpoint ${this._endpoint || '.'} (HTTP ${res.status}): ${text}`,
143
+ )
144
+ this.onerror?.(err)
145
+ throw err
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Aborts the SSE stream and notifies onclose.
151
+ */
152
+ async close(): Promise<void> {
153
+ this._abortController?.abort()
154
+ this.onclose?.()
155
+ }
156
+
157
+ _protocolVersion?: any
158
+ setProtocolVersion(version: string): void {
159
+ this._protocolVersion = version
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Polyfill for Promise.withResolvers
165
+ * Returns an object { promise, resolve, reject }
166
+ * If native Promise.withResolvers exists, uses it.
167
+ */
168
+ function withResolvers<T = unknown>(): {
169
+ promise: Promise<T>
170
+ resolve: (value: T | PromiseLike<T>) => void
171
+ reject: (reason?: any) => void
172
+ } {
173
+ if (typeof Promise.withResolvers === 'function') {
174
+ return Promise.withResolvers()
175
+ }
176
+ let resolve: (value: T | PromiseLike<T>) => void
177
+ let reject: (reason?: any) => void
178
+ const promise = new Promise<T>((res, rej) => {
179
+ resolve = res!
180
+ reject = rej!
181
+ })
182
+ // @ts-ignore checked by closure above
183
+ return { promise, resolve, reject }
184
+ }