spiceflow 1.1.18 → 1.2.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 +236 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-transport.d.ts +45 -0
- package/dist/mcp-transport.d.ts.map +1 -0
- package/dist/mcp-transport.js +107 -0
- package/dist/mcp-transport.js.map +1 -0
- package/dist/mcp.d.ts +36 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +211 -0
- package/dist/mcp.js.map +1 -0
- package/dist/mcp.test.d.ts +2 -0
- package/dist/mcp.test.d.ts.map +1 -0
- package/dist/mcp.test.js +224 -0
- package/dist/mcp.test.js.map +1 -0
- package/dist/openapi.d.ts +14 -27
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +101 -49
- package/dist/openapi.js.map +1 -1
- package/dist/openapi.test.js +242 -18
- package/dist/openapi.test.js.map +1 -1
- package/dist/spiceflow.d.ts +5 -3
- package/dist/spiceflow.d.ts.map +1 -1
- package/dist/spiceflow.js +42 -10
- package/dist/spiceflow.js.map +1 -1
- package/dist/spiceflow.test.js +21 -3
- package/dist/spiceflow.test.js.map +1 -1
- package/dist/types.d.ts +7 -13
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/package.json +15 -2
- package/src/index.ts +2 -1
- package/src/mcp-transport.ts +148 -0
- package/src/mcp.test.ts +273 -0
- package/src/mcp.ts +270 -0
- package/src/openapi.test.ts +238 -18
- package/src/openapi.ts +136 -66
- package/src/spiceflow.test.ts +27 -3
- package/src/spiceflow.ts +83 -13
- package/src/types.ts +129 -140
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<h3>spiceflow</h3>
|
|
6
6
|
<br/>
|
|
7
7
|
<p>fast, simple and type safe API framework</p>
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
<br/>
|
|
10
10
|
<br/>
|
|
11
11
|
</div>
|
|
@@ -18,6 +18,7 @@ Spiceflow is a lightweight, type-safe API framework for building web services us
|
|
|
18
18
|
|
|
19
19
|
- Type safety
|
|
20
20
|
- OpenAPI compatibility
|
|
21
|
+
- Native support for [Fern](https://github.com/fern-api/fern) docs and SDK generation
|
|
21
22
|
- RPC client generation
|
|
22
23
|
- Simple and intuitive API
|
|
23
24
|
- Uses web standards for requests and responses
|
|
@@ -62,7 +63,6 @@ app.post('/echo', async ({ request }) => {
|
|
|
62
63
|
const body = await request.json()
|
|
63
64
|
return body
|
|
64
65
|
})
|
|
65
|
-
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
## Requests and Responses
|
|
@@ -91,7 +91,6 @@ new Spiceflow().post(
|
|
|
91
91
|
> Notice that to get the body of the request, you need to call `request.json()` to parse the body as JSON.
|
|
92
92
|
> Spiceflow does not parse the Body automatically, there is no body field in the Spiceflow route argument, instead you call either `request.json()` or `request.formData()` to get the body and validate it at the same time. This works by wrapping the request in a `SpiceflowRequest` instance, which has a `json()` and `formData()` method that parse the body and validate it. The returned data will have the correct schema type instead of `any`.
|
|
93
93
|
|
|
94
|
-
|
|
95
94
|
### Response Schema
|
|
96
95
|
|
|
97
96
|
```ts
|
|
@@ -282,6 +281,88 @@ new Spiceflow().use(({ request }) => {
|
|
|
282
281
|
})
|
|
283
282
|
```
|
|
284
283
|
|
|
284
|
+
## How errors are handled in Spiceflow client
|
|
285
|
+
|
|
286
|
+
The Spiceflow client provides type-safe error handling by returning either a `data` or `error` property. When using the client:
|
|
287
|
+
|
|
288
|
+
- Thrown errors appear in the `error` field
|
|
289
|
+
- Response objects can be thrown or returned
|
|
290
|
+
- Responses with status codes 200-299 appear in the `data` field
|
|
291
|
+
- Responses with status codes <200 or ≥300 appear in the `error` field
|
|
292
|
+
|
|
293
|
+
The example below demonstrates handling different types of responses:
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
import { Spiceflow } from 'spiceflow'
|
|
297
|
+
import { createSpiceflowClient } from 'spiceflow/client'
|
|
298
|
+
|
|
299
|
+
const app = new Spiceflow()
|
|
300
|
+
.get('/error', () => {
|
|
301
|
+
throw new Error('Something went wrong')
|
|
302
|
+
})
|
|
303
|
+
.get('/unauthorized', () => {
|
|
304
|
+
return new Response('Unauthorized access', { status: 401 })
|
|
305
|
+
})
|
|
306
|
+
.get('/success', () => {
|
|
307
|
+
throw new Response('Success message', { status: 200 })
|
|
308
|
+
return ''
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
const client = createSpiceflowClient<typeof app>('http://localhost:3000')
|
|
312
|
+
|
|
313
|
+
async function handleErrors() {
|
|
314
|
+
const errorResponse = await client.error.get()
|
|
315
|
+
console.log('Calling error endpoint...')
|
|
316
|
+
// Logs: Error occurred: Something went wrong
|
|
317
|
+
if (errorResponse.error) {
|
|
318
|
+
console.error('Error occurred:', errorResponse.error)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const unauthorizedResponse = await client.unauthorized.get()
|
|
322
|
+
console.log('Calling unauthorized endpoint...')
|
|
323
|
+
// Logs: Unauthorized: Unauthorized access (Status: 401)
|
|
324
|
+
if (unauthorizedResponse.error) {
|
|
325
|
+
console.error('Unauthorized:', unauthorizedResponse.error)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const successResponse = await client.success.get()
|
|
329
|
+
console.log('Calling success endpoint...')
|
|
330
|
+
// Logs: Success: Success message
|
|
331
|
+
if (successResponse.data) {
|
|
332
|
+
console.log('Success:', successResponse.data)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Using the client server side, without network requests
|
|
338
|
+
|
|
339
|
+
When using the client server-side, you can pass the Spiceflow app instance directly to `createSpiceflowClient()` instead of providing a URL. This allows you to make "virtual" requests that are handled directly by the app without making actual network requests. This is useful for testing, generating documentation, or any other scenario where you want to interact with your API endpoints programmatically without setting up a server.
|
|
340
|
+
|
|
341
|
+
Here's an example:
|
|
342
|
+
|
|
343
|
+
```tsx
|
|
344
|
+
import { Spiceflow } from 'spiceflow'
|
|
345
|
+
import { createSpiceflowClient } from 'spiceflow/client'
|
|
346
|
+
import { openapi } from 'spiceflow/openapi'
|
|
347
|
+
import { writeFile } from 'node:fs/promises'
|
|
348
|
+
|
|
349
|
+
const app = new Spiceflow()
|
|
350
|
+
.use(openapi({ path: '/openapi' }))
|
|
351
|
+
.get('/users', () => [
|
|
352
|
+
{ id: 1, name: 'John' },
|
|
353
|
+
{ id: 2, name: 'Jane' },
|
|
354
|
+
])
|
|
355
|
+
.post('/users', ({ request }) => request.json())
|
|
356
|
+
|
|
357
|
+
// Create client by passing app instance directly
|
|
358
|
+
const client = createSpiceflowClient(app)
|
|
359
|
+
|
|
360
|
+
// Get OpenAPI schema and write to disk
|
|
361
|
+
const { data } = await client.openapi.get()
|
|
362
|
+
await writeFile('openapi.json', JSON.stringify(data, null, 2))
|
|
363
|
+
console.log('OpenAPI schema saved to openapi.json')
|
|
364
|
+
```
|
|
365
|
+
|
|
285
366
|
## Modifying Response with Middleware
|
|
286
367
|
|
|
287
368
|
Middleware in Spiceflow can be used to modify the response before it's sent to the client. This is useful for adding headers, transforming the response body, or performing any other operations on the response.
|
|
@@ -434,3 +515,155 @@ new Spiceflow()
|
|
|
434
515
|
return { ok: true }
|
|
435
516
|
})
|
|
436
517
|
```
|
|
518
|
+
|
|
519
|
+
## Model Context Protocol (MCP)
|
|
520
|
+
|
|
521
|
+
Spiceflow includes a Model Context Protocol (MCP) plugin that exposes your API routes as tools and resources that can be used by AI language models like Claude. The MCP plugin makes it easy to let AI assistants interact with your API endpoints in a controlled way.
|
|
522
|
+
|
|
523
|
+
When you mount the MCP plugin (default path is `/mcp`), it automatically:
|
|
524
|
+
|
|
525
|
+
- Exposes all your routes as callable tools with proper input validation
|
|
526
|
+
- Exposes GET routes without query/path parameters as `resources`
|
|
527
|
+
- Provides an SSE-based transport for real-time communication
|
|
528
|
+
- Handles serialization of requests and responses
|
|
529
|
+
|
|
530
|
+
This makes it simple to let AI models like Claude discover and call your API endpoints programmatically. Here's an example:
|
|
531
|
+
|
|
532
|
+
```tsx
|
|
533
|
+
// Import the MCP plugin and client
|
|
534
|
+
import { mcp } from 'spiceflow/mcp'
|
|
535
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
|
536
|
+
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
|
|
537
|
+
import { Spiceflow } from 'spiceflow'
|
|
538
|
+
import {
|
|
539
|
+
ListToolsResultSchema,
|
|
540
|
+
CallToolResultSchema,
|
|
541
|
+
ListResourcesResultSchema,
|
|
542
|
+
} from '@modelcontextprotocol/sdk/types.js'
|
|
543
|
+
|
|
544
|
+
// Create a new app with some example routes
|
|
545
|
+
const app = new Spiceflow()
|
|
546
|
+
// Mount the MCP plugin at /mcp (default path)
|
|
547
|
+
.use(mcp())
|
|
548
|
+
// These routes will be available as tools
|
|
549
|
+
.get('/hello', () => 'Hello World')
|
|
550
|
+
.get('/users/:id', ({ params }) => ({ id: params.id }))
|
|
551
|
+
.post('/echo', async ({ request }) => {
|
|
552
|
+
const body = await request.json()
|
|
553
|
+
return body
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
// Start the server
|
|
557
|
+
app.listen(3000)
|
|
558
|
+
|
|
559
|
+
// Example client usage:
|
|
560
|
+
const transport = new SSEClientTransport(new URL('http://localhost:3000/mcp'))
|
|
561
|
+
|
|
562
|
+
const client = new Client(
|
|
563
|
+
{ name: 'example-client', version: '1.0.0' },
|
|
564
|
+
{ capabilities: {} },
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
await client.connect(transport)
|
|
568
|
+
|
|
569
|
+
// List available tools
|
|
570
|
+
const tools = await client.request(
|
|
571
|
+
{ method: 'tools/list' },
|
|
572
|
+
ListToolsResultSchema,
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
// Call a tool
|
|
576
|
+
const result = await client.request(
|
|
577
|
+
{
|
|
578
|
+
method: 'tools/call',
|
|
579
|
+
params: {
|
|
580
|
+
name: 'GET /hello',
|
|
581
|
+
arguments: {},
|
|
582
|
+
},
|
|
583
|
+
},
|
|
584
|
+
CallToolResultSchema,
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
// List available resources (only GET /hello is exposed since it has no params)
|
|
588
|
+
const resources = await client.request(
|
|
589
|
+
{ method: 'resources/list' },
|
|
590
|
+
ListResourcesResultSchema,
|
|
591
|
+
)
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
## Generating Fern docs and SDK
|
|
595
|
+
|
|
596
|
+
Spiceflow has native support for Fern docs and SDK generation using openapi plugin.
|
|
597
|
+
|
|
598
|
+
The openapi types also have additional types for `x-fern` extensions to help you customize your docs and SDK.
|
|
599
|
+
|
|
600
|
+
Here is an example script to help you generate an openapi.yml file that you can then use with Fern:
|
|
601
|
+
|
|
602
|
+
```ts
|
|
603
|
+
import fs from 'fs'
|
|
604
|
+
import path from 'path'
|
|
605
|
+
import yaml from 'js-yaml'
|
|
606
|
+
import { Spiceflow } from 'spiceflow'
|
|
607
|
+
import { openapi } from 'spiceflow/openapi'
|
|
608
|
+
import { createSpiceflowClient } from 'spiceflow/client'
|
|
609
|
+
|
|
610
|
+
const app = new Spiceflow()
|
|
611
|
+
.use(openapi({ path: '/openapi' }))
|
|
612
|
+
.get('/hello', () => 'Hello World')
|
|
613
|
+
|
|
614
|
+
async function main() {
|
|
615
|
+
console.log('Creating Spiceflow client...')
|
|
616
|
+
const client = createSpiceflowClient(app)
|
|
617
|
+
|
|
618
|
+
console.log('Fetching OpenAPI spec...')
|
|
619
|
+
const { data: openapiJson, error } = await client.openapi.get()
|
|
620
|
+
if (error) {
|
|
621
|
+
console.error('Failed to fetch OpenAPI spec:', error)
|
|
622
|
+
throw error
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const outputPath = path.resolve('./openapi.yml')
|
|
626
|
+
console.log('Writing OpenAPI spec to', outputPath)
|
|
627
|
+
fs.writeFileSync(
|
|
628
|
+
outputPath,
|
|
629
|
+
yaml.dump(openapiJson, {
|
|
630
|
+
indent: 2,
|
|
631
|
+
lineWidth: -1,
|
|
632
|
+
}),
|
|
633
|
+
)
|
|
634
|
+
console.log('Successfully wrote OpenAPI spec')
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
main().catch((e) => {
|
|
638
|
+
console.error('Failed to generate OpenAPI spec:', e)
|
|
639
|
+
process.exit(1)
|
|
640
|
+
})
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
Then follow Fern docs to generate the SDK and docs. You will need to create some Fern yml config files.
|
|
644
|
+
|
|
645
|
+
You can take a look at the [`scripts/example-app.ts`](spiceflow/scripts/example-app.ts) file for an example app that generates the docs and SDK.
|
|
646
|
+
|
|
647
|
+
## Fern SDK streaming support
|
|
648
|
+
|
|
649
|
+
When you use an async generator in your app, Spiceflow will automatically add the required `x-fern` extensions to the OpenAPI spec to support streaming.
|
|
650
|
+
|
|
651
|
+
Here is what streaming looks like in the Fern generated SDK:
|
|
652
|
+
|
|
653
|
+
```ts
|
|
654
|
+
import { ExampleSdkClient } from './sdk-typescript'
|
|
655
|
+
|
|
656
|
+
const sdk = new ExampleSdkClient({
|
|
657
|
+
environment: 'http://localhost:3000',
|
|
658
|
+
})
|
|
659
|
+
|
|
660
|
+
// Get stream data
|
|
661
|
+
const stream = await sdk.getStream()
|
|
662
|
+
for await (const data of stream) {
|
|
663
|
+
console.log('Stream data:', data)
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Simple GET request
|
|
667
|
+
const response = await sdk.getUsers()
|
|
668
|
+
console.log('Users:', response)
|
|
669
|
+
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export { Spiceflow
|
|
1
|
+
export { Spiceflow } from './spiceflow.js';
|
|
2
|
+
export type { AnySpiceflow } from './spiceflow.js';
|
|
2
3
|
export { InternalServerError, ParseError, ValidationError } from './error.js';
|
|
3
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAClD,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE1C,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
2
|
+
import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Server transport for SSE: this will send messages over an SSE connection and receive messages from HTTP POST requests.
|
|
5
|
+
*
|
|
6
|
+
* This transport is only available in Node.js environments.
|
|
7
|
+
*/
|
|
8
|
+
export declare class SSEServerTransportSpiceflow implements Transport {
|
|
9
|
+
private _sessionId;
|
|
10
|
+
private _endpoint;
|
|
11
|
+
private _writableStream?;
|
|
12
|
+
response?: Response;
|
|
13
|
+
onclose?: () => void;
|
|
14
|
+
onerror?: (error: Error) => void;
|
|
15
|
+
onmessage?: (message: JSONRPCMessage) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Creates a new SSE server transport, which will direct the client to POST messages to the relative or absolute URL identified by `_endpoint`.
|
|
18
|
+
*/
|
|
19
|
+
constructor(endpoint: string);
|
|
20
|
+
/**
|
|
21
|
+
* Handles the initial SSE connection request.
|
|
22
|
+
*
|
|
23
|
+
* This should be called when a GET request is made to establish the SSE stream.
|
|
24
|
+
*/
|
|
25
|
+
start(): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Handles incoming POST messages.
|
|
28
|
+
*
|
|
29
|
+
* This should be called when a POST request is made to send a message to the server.
|
|
30
|
+
*/
|
|
31
|
+
handlePostMessage(req: Request, parsedBody?: unknown): Promise<Response>;
|
|
32
|
+
/**
|
|
33
|
+
* Handle a client message, regardless of how it arrived. This can be used to inform the server of messages that arrive via a means different than HTTP POST.
|
|
34
|
+
*/
|
|
35
|
+
handleMessage(message: unknown): Promise<void>;
|
|
36
|
+
close(): Promise<void>;
|
|
37
|
+
send(message: JSONRPCMessage): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Returns the session ID for this transport.
|
|
40
|
+
*
|
|
41
|
+
* This can be used to route incoming POST requests.
|
|
42
|
+
*/
|
|
43
|
+
get sessionId(): string;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=mcp-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-transport.d.ts","sourceRoot":"","sources":["../src/mcp-transport.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAA;AACzE,OAAO,EACL,cAAc,EAEf,MAAM,oCAAoC,CAAA;AAG3C;;;;GAIG;AACH,qBAAa,2BAA4B,YAAW,SAAS;IAC3D,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,eAAe,CAAC,CAAyC;IACjE,QAAQ,CAAC,EAAE,QAAQ,CAAA;IAEnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAChC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAA;IAE7C;;OAEG;gBACS,QAAQ,EAAE,MAAM;IAK5B;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAuC5B;;;;OAIG;IACG,iBAAiB,CACrB,GAAG,EAAE,OAAO,EACZ,UAAU,CAAC,EAAE,OAAO,GACnB,OAAO,CAAC,QAAQ,CAAC;IAmBpB;;OAEG;IACG,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAY9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAStB,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAalD;;;;OAIG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;CACF"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { JSONRPCMessageSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
/**
|
|
4
|
+
* Server transport for SSE: this will send messages over an SSE connection and receive messages from HTTP POST requests.
|
|
5
|
+
*
|
|
6
|
+
* This transport is only available in Node.js environments.
|
|
7
|
+
*/
|
|
8
|
+
export class SSEServerTransportSpiceflow {
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new SSE server transport, which will direct the client to POST messages to the relative or absolute URL identified by `_endpoint`.
|
|
11
|
+
*/
|
|
12
|
+
constructor(endpoint) {
|
|
13
|
+
this._sessionId = randomUUID();
|
|
14
|
+
this._endpoint = endpoint;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Handles the initial SSE connection request.
|
|
18
|
+
*
|
|
19
|
+
* This should be called when a GET request is made to establish the SSE stream.
|
|
20
|
+
*/
|
|
21
|
+
async start() {
|
|
22
|
+
var _a;
|
|
23
|
+
if (this.response) {
|
|
24
|
+
throw new Error('SSEServerTransport already started! If using Server class, note that connect() calls start() automatically.');
|
|
25
|
+
}
|
|
26
|
+
const headers = new Headers({
|
|
27
|
+
'Content-Type': 'text/event-stream',
|
|
28
|
+
'Cache-Control': 'no-cache',
|
|
29
|
+
// https://github.com/vercel/next.js/issues/9965
|
|
30
|
+
'content-encoding': 'none',
|
|
31
|
+
Connection: 'keep-alive',
|
|
32
|
+
});
|
|
33
|
+
// Create a TransformStream
|
|
34
|
+
const transformStream = new TransformStream();
|
|
35
|
+
const { readable, writable } = transformStream;
|
|
36
|
+
// Create the Response from the readable side
|
|
37
|
+
this.response = new Response(readable, { headers });
|
|
38
|
+
// Obtain a writer from the writable end
|
|
39
|
+
this._writableStream = writable.getWriter();
|
|
40
|
+
(_a = this._writableStream) === null || _a === void 0 ? void 0 : _a.write(new TextEncoder().encode(`event: endpoint\ndata: ${encodeURI(this._endpoint)}?sessionId=${this._sessionId}\n\n`));
|
|
41
|
+
// readable.getReader().closed.then(() => {
|
|
42
|
+
// this.response = undefined
|
|
43
|
+
// this._writableStream = undefined
|
|
44
|
+
// this.onclose?.()
|
|
45
|
+
// })
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Handles incoming POST messages.
|
|
49
|
+
*
|
|
50
|
+
* This should be called when a POST request is made to send a message to the server.
|
|
51
|
+
*/
|
|
52
|
+
async handlePostMessage(req, parsedBody) {
|
|
53
|
+
if (!this.response) {
|
|
54
|
+
const message = 'SSE connection not established';
|
|
55
|
+
throw new Error(message);
|
|
56
|
+
}
|
|
57
|
+
let body = await req.json();
|
|
58
|
+
try {
|
|
59
|
+
await this.handleMessage(typeof body === 'string' ? JSON.parse(body) : body);
|
|
60
|
+
}
|
|
61
|
+
catch (_a) {
|
|
62
|
+
return new Response(`Invalid message: ${body}`, { status: 400 });
|
|
63
|
+
}
|
|
64
|
+
return new Response('Accepted', { status: 202 });
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Handle a client message, regardless of how it arrived. This can be used to inform the server of messages that arrive via a means different than HTTP POST.
|
|
68
|
+
*/
|
|
69
|
+
async handleMessage(message) {
|
|
70
|
+
var _a, _b;
|
|
71
|
+
let parsedMessage;
|
|
72
|
+
try {
|
|
73
|
+
parsedMessage = JSONRPCMessageSchema.parse(message);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
(_b = this.onmessage) === null || _b === void 0 ? void 0 : _b.call(this, parsedMessage);
|
|
80
|
+
}
|
|
81
|
+
async close() {
|
|
82
|
+
var _a;
|
|
83
|
+
if (this._writableStream) {
|
|
84
|
+
await this._writableStream.close();
|
|
85
|
+
}
|
|
86
|
+
this.response = undefined;
|
|
87
|
+
this._writableStream = undefined;
|
|
88
|
+
(_a = this.onclose) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
89
|
+
}
|
|
90
|
+
async send(message) {
|
|
91
|
+
if (!this._writableStream) {
|
|
92
|
+
throw new Error('Not connected');
|
|
93
|
+
}
|
|
94
|
+
const encoder = new TextEncoder();
|
|
95
|
+
const data = encoder.encode(`event: message\ndata: ${JSON.stringify(message)}\n\n`);
|
|
96
|
+
await this._writableStream.write(data);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Returns the session ID for this transport.
|
|
100
|
+
*
|
|
101
|
+
* This can be used to route incoming POST requests.
|
|
102
|
+
*/
|
|
103
|
+
get sessionId() {
|
|
104
|
+
return this._sessionId;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=mcp-transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-transport.js","sourceRoot":"","sources":["../src/mcp-transport.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Spiceflow } from './spiceflow.js';
|
|
2
|
+
export declare const mcp: <Path extends string = "/mcp">({ path, name, version, }?: {
|
|
3
|
+
path?: Path | undefined;
|
|
4
|
+
name?: string | undefined;
|
|
5
|
+
version?: string | undefined;
|
|
6
|
+
}) => Spiceflow<"", true, {
|
|
7
|
+
state: {};
|
|
8
|
+
}, {
|
|
9
|
+
type: {};
|
|
10
|
+
error: {};
|
|
11
|
+
}, {
|
|
12
|
+
schema: {};
|
|
13
|
+
macro: {};
|
|
14
|
+
macroFn: {};
|
|
15
|
+
}, {
|
|
16
|
+
[x: string]: {
|
|
17
|
+
post: {
|
|
18
|
+
body: unknown;
|
|
19
|
+
params: {};
|
|
20
|
+
query: unknown;
|
|
21
|
+
response: {
|
|
22
|
+
200: Response | "ok";
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
} & import("./types.js").CreateClient<`${Path extends "/" ? "/index" : Path extends "" ? Path : Path extends `/${string}` ? Path : Path}`, {
|
|
27
|
+
get: {
|
|
28
|
+
body: unknown;
|
|
29
|
+
params: { [Param in import("./types.js").GetPathParameter<Path> as Param extends `${string}?` ? never : Param]: string; } & { [Param_1 in import("./types.js").GetPathParameter<Path> as Param_1 extends `${infer OptionalParam}?` ? OptionalParam : never]?: string | undefined; } extends infer T ? { [K in keyof T]: ({ [Param in import("./types.js").GetPathParameter<Path> as Param extends `${string}?` ? never : Param]: string; } & { [Param_1 in import("./types.js").GetPathParameter<Path> as Param_1 extends `${infer OptionalParam}?` ? OptionalParam : never]?: string | undefined; })[K]; } : never;
|
|
30
|
+
query: unknown;
|
|
31
|
+
response: {
|
|
32
|
+
200: Response | undefined;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
}>>;
|
|
36
|
+
//# sourceMappingURL=mcp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAUA,OAAO,EAAe,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAUvD,eAAO,MAAM,GAAG,GAAI,IAAI,SAAS,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkOtC,CAAA"}
|