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.
- package/README.md +107 -4
- package/dist/_node-server-unsupported.js +1 -0
- package/dist/_node-server-unsupported.js.map +1 -0
- package/dist/_node-server.d.ts.map +1 -1
- package/dist/_node-server.js +2 -2
- package/dist/_node-server.js.map +1 -0
- package/dist/client/errors.js +1 -0
- package/dist/client/errors.js.map +1 -0
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +7 -4
- package/dist/client/index.js.map +1 -0
- package/dist/client/types.js +1 -0
- package/dist/client/types.js.map +1 -0
- package/dist/client/utils.js +1 -0
- package/dist/client/utils.js.map +1 -0
- package/dist/client.test.js +1 -0
- package/dist/client.test.js.map +1 -0
- package/dist/context.d.ts +4 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +1 -0
- package/dist/context.js.map +1 -0
- package/dist/cors.js +1 -0
- package/dist/cors.js.map +1 -0
- package/dist/cors.test.js +1 -0
- package/dist/cors.test.js.map +1 -0
- package/dist/error.js +1 -0
- package/dist/error.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-client-transport.d.ts +35 -0
- package/dist/mcp-client-transport.d.ts.map +1 -0
- package/dist/mcp-client-transport.js +147 -0
- package/dist/mcp-client-transport.js.map +1 -0
- package/dist/mcp-transport.js +1 -0
- package/dist/mcp-transport.js.map +1 -0
- package/dist/mcp.d.ts +18 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +43 -224
- package/dist/mcp.js.map +1 -0
- package/dist/middleware.test.js +1 -0
- package/dist/middleware.test.js.map +1 -0
- package/dist/openapi-to-mcp.d.ts +38 -0
- package/dist/openapi-to-mcp.d.ts.map +1 -0
- package/dist/openapi-to-mcp.js +367 -0
- package/dist/openapi-to-mcp.js.map +1 -0
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +7 -2
- package/dist/openapi.js.map +1 -0
- package/dist/openapi.test.js +32 -31
- package/dist/openapi.test.js.map +1 -0
- package/dist/simple.benchmark.js +1 -0
- package/dist/simple.benchmark.js.map +1 -0
- package/dist/spiceflow.d.ts +5 -2
- package/dist/spiceflow.d.ts.map +1 -1
- package/dist/spiceflow.js +26 -6
- package/dist/spiceflow.js.map +1 -0
- package/dist/spiceflow.test.js +15 -3
- package/dist/spiceflow.test.js.map +1 -0
- package/dist/static-node.js +1 -0
- package/dist/static-node.js.map +1 -0
- package/dist/static.benchmark.js +1 -0
- package/dist/static.benchmark.js.map +1 -0
- package/dist/static.js +1 -0
- package/dist/static.js.map +1 -0
- package/dist/stream.test.js +1 -0
- package/dist/stream.test.js.map +1 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/dist/types.test.js +1 -0
- package/dist/types.test.js.map +1 -0
- package/dist/utils.js +1 -0
- package/dist/utils.js.map +1 -0
- package/dist/waitUntil.test.d.ts +2 -0
- package/dist/waitUntil.test.d.ts.map +1 -0
- package/dist/waitUntil.test.js +142 -0
- package/dist/waitUntil.test.js.map +1 -0
- package/dist/zod.test.js +1 -0
- package/dist/zod.test.js.map +1 -0
- package/package.json +4 -3
- package/src/_node-server.ts +1 -2
- package/src/client/index.ts +9 -7
- package/src/context.ts +4 -1
- package/src/index.ts +1 -1
- package/src/mcp-client-transport.ts +184 -0
- package/src/mcp.ts +49 -307
- package/src/openapi-to-mcp.ts +510 -0
- package/src/openapi.test.ts +31 -31
- package/src/openapi.ts +9 -3
- package/src/spiceflow.test.ts +18 -4
- package/src/spiceflow.ts +42 -15
- package/src/waitUntil.test.ts +168 -0
- package/dist/serialize.d.ts +0 -2
- package/dist/serialize.d.ts.map +0 -1
- package/dist/serialize.js +0 -9
- 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
|
@@ -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.
|
|
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.
|
|
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",
|
package/src/_node-server.ts
CHANGED
|
@@ -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(
|
|
112
|
+
res.end(JSON.stringify({ message: 'Internal Server Error' }))
|
|
114
113
|
}
|
|
115
114
|
}
|
package/src/client/index.ts
CHANGED
|
@@ -113,7 +113,7 @@ const processHeaders = (
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
interface SSEEvent {
|
|
116
|
-
event
|
|
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
|
|
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
|
|
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
|
@@ -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
|
+
}
|