spiceflow 1.9.0 → 1.10.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 (96) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -2
  3. package/dist/_node-server-unsupported.d.ts +5 -0
  4. package/dist/_node-server-unsupported.d.ts.map +1 -0
  5. package/dist/_node-server-unsupported.js +6 -0
  6. package/dist/_node-server.d.ts +7 -0
  7. package/dist/_node-server.d.ts.map +1 -0
  8. package/dist/_node-server.js +77 -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 +2 -2
  12. package/dist/client/types.d.ts +2 -2
  13. package/dist/client/types.d.ts.map +1 -1
  14. package/dist/client.test.js +2 -2
  15. package/dist/context.d.ts +3 -3
  16. package/dist/cors.d.ts +2 -2
  17. package/dist/cors.d.ts.map +1 -1
  18. package/dist/cors.js +5 -2
  19. package/dist/cors.test.js +2 -2
  20. package/dist/error.d.ts +0 -1
  21. package/dist/error.d.ts.map +1 -1
  22. package/dist/error.js +0 -10
  23. package/dist/index.d.ts +3 -3
  24. package/dist/index.js +2 -2
  25. package/dist/mcp-transport.d.ts +2 -2
  26. package/dist/mcp-transport.d.ts.map +1 -1
  27. package/dist/mcp-transport.js +1 -6
  28. package/dist/mcp.d.ts +3 -3
  29. package/dist/mcp.d.ts.map +1 -1
  30. package/dist/mcp.js +3 -3
  31. package/dist/middleware.test.js +8 -3
  32. package/dist/openapi.d.ts +3 -3
  33. package/dist/openapi.d.ts.map +1 -1
  34. package/dist/openapi.js +15 -2
  35. package/dist/openapi.test.js +8 -8
  36. package/dist/serialize.d.ts +2 -0
  37. package/dist/serialize.d.ts.map +1 -0
  38. package/dist/serialize.js +9 -0
  39. package/dist/simple.benchmark.js +1 -1
  40. package/dist/spiceflow.d.ts +15 -8
  41. package/dist/spiceflow.d.ts.map +1 -1
  42. package/dist/spiceflow.js +30 -86
  43. package/dist/spiceflow.test.js +6 -4
  44. package/dist/static-node.d.ts +2 -2
  45. package/dist/static-node.d.ts.map +1 -1
  46. package/dist/static-node.js +1 -1
  47. package/dist/static.benchmark.js +2 -2
  48. package/dist/static.d.ts +1 -1
  49. package/dist/static.d.ts.map +1 -1
  50. package/dist/static.js +1 -1
  51. package/dist/stream.test.js +2 -2
  52. package/dist/types.d.ts +6 -6
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/types.js +1 -1
  55. package/dist/types.test.js +2 -2
  56. package/dist/utils.d.ts.map +1 -1
  57. package/dist/zod.test.js +2 -2
  58. package/package.json +13 -12
  59. package/src/_node-server-unsupported.ts +20 -0
  60. package/src/_node-server.ts +115 -0
  61. package/src/client/index.ts +4 -4
  62. package/src/client/types.ts +47 -49
  63. package/src/client.test.ts +2 -3
  64. package/src/context.ts +3 -3
  65. package/src/cors.test.ts +11 -9
  66. package/src/cors.ts +7 -5
  67. package/src/error.ts +0 -12
  68. package/src/index.ts +3 -3
  69. package/src/mcp-transport.ts +2 -11
  70. package/src/mcp.ts +3 -4
  71. package/src/middleware.test.ts +19 -12
  72. package/src/openapi.test.ts +8 -8
  73. package/src/openapi.ts +20 -5
  74. package/src/serialize.ts +10 -0
  75. package/src/simple.benchmark.ts +1 -1
  76. package/src/spiceflow.test.ts +12 -10
  77. package/src/spiceflow.ts +70 -137
  78. package/src/static-node.ts +2 -2
  79. package/src/static.benchmark.ts +2 -2
  80. package/src/static.ts +2 -2
  81. package/src/stream.test.ts +2 -3
  82. package/src/types.test.ts +3 -3
  83. package/src/types.ts +18 -18
  84. package/src/zod.test.ts +2 -2
  85. package/dist/_node_utils.d.ts +0 -3
  86. package/dist/_node_utils.d.ts.map +0 -1
  87. package/dist/_node_utils.js +0 -2
  88. package/dist/_node_utils_browser.d.ts +0 -2
  89. package/dist/_node_utils_browser.d.ts.map +0 -1
  90. package/dist/_node_utils_browser.js +0 -3
  91. package/dist/mcp.test.d.ts +0 -2
  92. package/dist/mcp.test.d.ts.map +0 -1
  93. package/dist/mcp.test.js +0 -217
  94. package/src/_node_utils.ts +0 -2
  95. package/src/_node_utils_browser.ts +0 -3
  96. package/src/mcp.test.ts +0 -267
package/dist/mcp.test.js DELETED
@@ -1,217 +0,0 @@
1
- import { describe, it, expect, beforeAll } from 'vitest';
2
- import { EventSource } from 'eventsource';
3
- import { mcp } from './mcp.js';
4
- import { Client } from '@modelcontextprotocol/sdk/client/index.js';
5
- import { ListResourcesResultSchema, ListToolsResultSchema, ReadResourceResultSchema, CallToolResultSchema, } from '@modelcontextprotocol/sdk/types.js';
6
- import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
7
- import { z } from 'zod';
8
- import { Spiceflow } from './spiceflow.js';
9
- describe('MCP Plugin', () => {
10
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
- ;
12
- global.EventSource = EventSource;
13
- let app;
14
- let port;
15
- let client;
16
- let transport;
17
- beforeAll(async () => {
18
- port = await getAvailablePort();
19
- app = new Spiceflow({ basePath: '/api' })
20
- .use(mcp({ path: '/mcp' }))
21
- .get('/goSomething', () => 'hi')
22
- .get('/users', () => ({ users: [{ id: 1, name: 'John' }] }))
23
- .get('/somethingElse/:id', ({ params: { id } }) => {
24
- return 'hello ' + id;
25
- }, {
26
- params: z.object({ id: z.string() }),
27
- })
28
- .get('/search', ({ query }) => {
29
- return { results: [`Found results for: ${query.q}`] };
30
- }, {
31
- query: z
32
- .object({
33
- q: z.string().describe('Search query'),
34
- limit: z.number().optional().describe('Max number of results'),
35
- })
36
- .required(),
37
- });
38
- await app.listen(port);
39
- transport = new SSEClientTransport(new URL(`http://localhost:${port}/api/mcp`));
40
- client = new Client({
41
- name: 'example-client',
42
- version: '1.0.0',
43
- }, {
44
- capabilities: {},
45
- });
46
- await client.connect(transport);
47
- });
48
- it('should list and call available tools', async () => {
49
- const resources = await client.request({ method: 'tools/list' }, ListToolsResultSchema);
50
- expect(resources).toBeDefined();
51
- expect(resources).toHaveProperty('tools');
52
- expect(resources).toMatchInlineSnapshot(`
53
- {
54
- "tools": [
55
- {
56
- "description": "GET /api/goSomething",
57
- "inputSchema": {
58
- "properties": {},
59
- "type": "object",
60
- },
61
- "name": "GET /api/goSomething",
62
- },
63
- {
64
- "description": "GET /api/users",
65
- "inputSchema": {
66
- "properties": {},
67
- "type": "object",
68
- },
69
- "name": "GET /api/users",
70
- },
71
- {
72
- "description": "GET /api/somethingElse/{id}",
73
- "inputSchema": {
74
- "properties": {
75
- "params": {
76
- "properties": {
77
- "id": {
78
- "type": "string",
79
- },
80
- },
81
- "required": [
82
- "id",
83
- ],
84
- "type": "object",
85
- },
86
- },
87
- "type": "object",
88
- },
89
- "name": "GET /api/somethingElse/{id}",
90
- },
91
- {
92
- "description": "GET /api/search",
93
- "inputSchema": {
94
- "properties": {
95
- "query": {
96
- "properties": {
97
- "limit": {
98
- "type": "number",
99
- },
100
- "q": {
101
- "type": "string",
102
- },
103
- },
104
- "required": [
105
- "q",
106
- "limit",
107
- ],
108
- "type": "object",
109
- },
110
- },
111
- "type": "object",
112
- },
113
- "name": "GET /api/search",
114
- },
115
- ],
116
- }
117
- `);
118
- const resourceContent = await client.request({
119
- method: 'tools/call',
120
- params: {
121
- name: 'POST /somethingElse/:id',
122
- arguments: {
123
- params: { id: 'xxx' },
124
- },
125
- },
126
- }, CallToolResultSchema);
127
- expect(resourceContent).toBeDefined();
128
- expect(resourceContent).toHaveProperty('content');
129
- expect(resourceContent).toMatchInlineSnapshot(`
130
- {
131
- "content": [
132
- {
133
- "text": "Tool POST /somethingElse/:id not found",
134
- "type": "text",
135
- },
136
- ],
137
- "isError": true,
138
- }
139
- `);
140
- });
141
- it('should list and read available resources', async () => {
142
- const resources = await client.request({ method: 'resources/list' }, ListResourcesResultSchema);
143
- expect(resources).toBeDefined();
144
- expect(resources.resources).toMatchInlineSnapshot(`
145
- [
146
- {
147
- "mimeType": "application/json",
148
- "name": "GET /api/goSomething",
149
- "uri": "http://localhost/api/goSomething",
150
- },
151
- {
152
- "mimeType": "application/json",
153
- "name": "GET /api/users",
154
- "uri": "http://localhost/api/users",
155
- },
156
- {
157
- "mimeType": "application/json",
158
- "name": "GET /api/mcp",
159
- "uri": "http://localhost/api/mcp",
160
- },
161
- {
162
- "mimeType": "application/json",
163
- "name": "GET /api/mcp-openapi",
164
- "uri": "http://localhost/api/mcp-openapi",
165
- },
166
- ]
167
- `);
168
- const resourceContent = await client.request({
169
- method: 'resources/read',
170
- params: {
171
- uri: `http://localhost:${port}/api/users`,
172
- },
173
- }, ReadResourceResultSchema);
174
- expect(resourceContent).toBeDefined();
175
- expect(resourceContent.contents).toMatchInlineSnapshot(`
176
- [
177
- {
178
- "mimeType": "application/json",
179
- "text": "{"users":[{"id":1,"name":"John"}]}",
180
- "uri": "http://localhost:4000/api/users",
181
- },
182
- ]
183
- `);
184
- });
185
- });
186
- async function getAvailablePort(startPort = 4000, maxRetries = 10) {
187
- const net = await import('net');
188
- return await new Promise((resolve, reject) => {
189
- let port = startPort;
190
- let attempts = 0;
191
- const checkPort = () => {
192
- const server = net.createServer();
193
- server.once('error', (err) => {
194
- if (err.code === 'EADDRINUSE') {
195
- attempts++;
196
- if (attempts >= maxRetries) {
197
- reject(new Error('No available ports found'));
198
- }
199
- else {
200
- port++;
201
- checkPort();
202
- }
203
- }
204
- else {
205
- reject(err);
206
- }
207
- });
208
- server.once('listening', () => {
209
- server.close(() => {
210
- resolve(port);
211
- });
212
- });
213
- server.listen(port);
214
- };
215
- checkPort();
216
- });
217
- }
@@ -1,2 +0,0 @@
1
- import { createServer } from 'http'
2
- export { createServer }
@@ -1,3 +0,0 @@
1
- export function createServer() {
2
- throw new Error('createServer is not supported in non Node.js environments')
3
- }
package/src/mcp.test.ts DELETED
@@ -1,267 +0,0 @@
1
- import { describe, it, expect, beforeEach, beforeAll } from 'vitest'
2
- import { EventSource } from 'eventsource'
3
-
4
- import { mcp } from './mcp.js'
5
- import { Client } from '@modelcontextprotocol/sdk/client/index.js'
6
-
7
- import {
8
- ListResourcesResultSchema,
9
- ListToolsRequestSchema,
10
- ListToolsResultSchema,
11
- ReadResourceResultSchema,
12
- CallToolResultSchema,
13
- } from '@modelcontextprotocol/sdk/types.js'
14
- import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
15
- import { z } from 'zod'
16
- import { Spiceflow } from './spiceflow.js'
17
- describe('MCP Plugin', () => {
18
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
- ;(global as any).EventSource = EventSource
20
-
21
- let app: Spiceflow<any>
22
- let port: number
23
- let client: Client
24
- let transport: SSEClientTransport
25
-
26
- beforeAll(async () => {
27
- port = await getAvailablePort()
28
-
29
- app = new Spiceflow({ basePath: '/api' })
30
- .use(mcp({ path: '/mcp' }))
31
- .get('/goSomething', () => 'hi')
32
- .get('/users', () => ({ users: [{ id: 1, name: 'John' }] }))
33
- .get(
34
- '/somethingElse/:id',
35
- ({ params: { id } }) => {
36
- return 'hello ' + id
37
- },
38
- {
39
- params: z.object({ id: z.string() }),
40
- },
41
- )
42
- .get(
43
- '/search',
44
- ({ query }) => {
45
- return { results: [`Found results for: ${query.q}`] }
46
- },
47
- {
48
- query: z
49
- .object({
50
- q: z.string().describe('Search query'),
51
- limit: z.number().optional().describe('Max number of results'),
52
- })
53
- .required(),
54
- },
55
- )
56
- await app.listen(port)
57
-
58
- transport = new SSEClientTransport(new URL(`http://localhost:${port}/api/mcp`))
59
-
60
- client = new Client(
61
- {
62
- name: 'example-client',
63
- version: '1.0.0',
64
- },
65
- {
66
- capabilities: {},
67
- },
68
- )
69
-
70
- await client.connect(transport)
71
- })
72
-
73
- it('should list and call available tools', async () => {
74
- const resources = await client.request(
75
- { method: 'tools/list' },
76
- ListToolsResultSchema,
77
- )
78
-
79
- expect(resources).toBeDefined()
80
- expect(resources).toHaveProperty('tools')
81
- expect(resources).toMatchInlineSnapshot(`
82
- {
83
- "tools": [
84
- {
85
- "description": "GET /api/goSomething",
86
- "inputSchema": {
87
- "properties": {},
88
- "type": "object",
89
- },
90
- "name": "GET /api/goSomething",
91
- },
92
- {
93
- "description": "GET /api/users",
94
- "inputSchema": {
95
- "properties": {},
96
- "type": "object",
97
- },
98
- "name": "GET /api/users",
99
- },
100
- {
101
- "description": "GET /api/somethingElse/{id}",
102
- "inputSchema": {
103
- "properties": {
104
- "params": {
105
- "properties": {
106
- "id": {
107
- "type": "string",
108
- },
109
- },
110
- "required": [
111
- "id",
112
- ],
113
- "type": "object",
114
- },
115
- },
116
- "type": "object",
117
- },
118
- "name": "GET /api/somethingElse/{id}",
119
- },
120
- {
121
- "description": "GET /api/search",
122
- "inputSchema": {
123
- "properties": {
124
- "query": {
125
- "properties": {
126
- "limit": {
127
- "type": "number",
128
- },
129
- "q": {
130
- "type": "string",
131
- },
132
- },
133
- "required": [
134
- "q",
135
- "limit",
136
- ],
137
- "type": "object",
138
- },
139
- },
140
- "type": "object",
141
- },
142
- "name": "GET /api/search",
143
- },
144
- ],
145
- }
146
- `)
147
-
148
- const resourceContent = await client.request(
149
- {
150
- method: 'tools/call',
151
- params: {
152
- name: 'POST /somethingElse/:id',
153
- arguments: {
154
- params: { id: 'xxx' },
155
- },
156
- },
157
- },
158
- CallToolResultSchema,
159
- )
160
-
161
- expect(resourceContent).toBeDefined()
162
- expect(resourceContent).toHaveProperty('content')
163
- expect(resourceContent).toMatchInlineSnapshot(`
164
- {
165
- "content": [
166
- {
167
- "text": "Tool POST /somethingElse/:id not found",
168
- "type": "text",
169
- },
170
- ],
171
- "isError": true,
172
- }
173
- `)
174
- })
175
-
176
- it('should list and read available resources', async () => {
177
- const resources = await client.request(
178
- { method: 'resources/list' },
179
- ListResourcesResultSchema,
180
- )
181
-
182
- expect(resources).toBeDefined()
183
-
184
- expect(resources.resources).toMatchInlineSnapshot(`
185
- [
186
- {
187
- "mimeType": "application/json",
188
- "name": "GET /api/goSomething",
189
- "uri": "http://localhost/api/goSomething",
190
- },
191
- {
192
- "mimeType": "application/json",
193
- "name": "GET /api/users",
194
- "uri": "http://localhost/api/users",
195
- },
196
- {
197
- "mimeType": "application/json",
198
- "name": "GET /api/mcp",
199
- "uri": "http://localhost/api/mcp",
200
- },
201
- {
202
- "mimeType": "application/json",
203
- "name": "GET /api/mcp-openapi",
204
- "uri": "http://localhost/api/mcp-openapi",
205
- },
206
- ]
207
- `)
208
-
209
- const resourceContent = await client.request(
210
- {
211
- method: 'resources/read',
212
- params: {
213
- uri: `http://localhost:${port}/api/users`,
214
- },
215
- },
216
- ReadResourceResultSchema,
217
- )
218
-
219
- expect(resourceContent).toBeDefined()
220
- expect(resourceContent.contents).toMatchInlineSnapshot(`
221
- [
222
- {
223
- "mimeType": "application/json",
224
- "text": "{"users":[{"id":1,"name":"John"}]}",
225
- "uri": "http://localhost:4000/api/users",
226
- },
227
- ]
228
- `)
229
- })
230
- })
231
-
232
- async function getAvailablePort(startPort = 4000, maxRetries = 10) {
233
- const net = await import('net')
234
-
235
- return await new Promise<number>((resolve, reject) => {
236
- let port = startPort
237
- let attempts = 0
238
-
239
- const checkPort = () => {
240
- const server = net.createServer()
241
-
242
- server.once('error', (err: any) => {
243
- if (err.code === 'EADDRINUSE') {
244
- attempts++
245
- if (attempts >= maxRetries) {
246
- reject(new Error('No available ports found'))
247
- } else {
248
- port++
249
- checkPort()
250
- }
251
- } else {
252
- reject(err)
253
- }
254
- })
255
-
256
- server.once('listening', () => {
257
- server.close(() => {
258
- resolve(port)
259
- })
260
- })
261
-
262
- server.listen(port)
263
- }
264
-
265
- checkPort()
266
- })
267
- }