Compare commits
4 Commits
@hono/ua-b
...
main
Author | SHA1 | Date |
---|---|---|
|
4cf126d38a | |
|
80481dd5ed | |
|
1baa0b281d | |
|
5cba9a4819 |
|
@ -3,12 +3,15 @@ name: Sync robots.json
|
|||
on:
|
||||
schedule:
|
||||
# Runs every day at midnight
|
||||
- cron: '0 0 * * *'
|
||||
- cron: '15 0 */3 * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sync-and-pr:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
@ -27,7 +30,35 @@ jobs:
|
|||
- name: Generate data
|
||||
run: yarn workspace @hono/ua-blocker prebuild
|
||||
|
||||
- name: Check for changes
|
||||
id: changes
|
||||
run: |
|
||||
if [[ -n $(git status --porcelain) ]]; then
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Generate changeset
|
||||
if: steps.changes.outputs.has_changes == 'true'
|
||||
run: |
|
||||
# Use a static changeset filename to avoid duplicates
|
||||
CHANGESET_FILE=".changeset/auto-sync-robots.md"
|
||||
|
||||
# Create the changeset file
|
||||
cat << EOF > "$CHANGESET_FILE"
|
||||
---
|
||||
'@hono/ua-blocker': patch
|
||||
---
|
||||
|
||||
chore(ua-blocker): sync \`robots.json\` with upstream
|
||||
EOF
|
||||
|
||||
- name: Format
|
||||
run: yarn prettier --write . !packages packages/ua-blocker
|
||||
|
||||
- name: Create Pull Request if changes exist
|
||||
if: steps.changes.outputs.has_changes == 'true'
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# @hono/mcp
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#1178](https://github.com/honojs/middleware/pull/1178) [`1baa0b281dd4170f0003f9353a5f4c33fdcca610`](https://github.com/honojs/middleware/commit/1baa0b281dd4170f0003f9353a5f4c33fdcca610) Thanks [@MathurAditya724](https://github.com/MathurAditya724)! - init release
|
|
@ -0,0 +1,35 @@
|
|||
# Hono MCP (Model Context Protocol)
|
||||
|
||||
Connect Hono with a Model Context Protocol (MCP) server over HTTP Streaming Transport.
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
||||
import { StreamableHTTPTransport } from '@hono/mcp'
|
||||
import { Hono } from 'hono'
|
||||
|
||||
const app = new Hono()
|
||||
|
||||
// Your MCP server implementation
|
||||
const mcpServer = new McpServer({
|
||||
name: 'my-mcp-server',
|
||||
version: '1.0.0',
|
||||
})
|
||||
|
||||
app.all('/mcp', async (c) => {
|
||||
const transport = new StreamableHTTPTransport()
|
||||
await mcpServer.connect(transport)
|
||||
return transport.handleRequest(c)
|
||||
})
|
||||
|
||||
export default app
|
||||
```
|
||||
|
||||
## Author
|
||||
|
||||
Aditya Mathur <https://github.com/mathuraditya724>
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"name": "@hono/mcp",
|
||||
"version": "0.1.0",
|
||||
"description": "MCP Middleware for Hono",
|
||||
"type": "module",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup ./src/index.ts",
|
||||
"prepack": "yarn build",
|
||||
"publint": "attw --pack && publint",
|
||||
"typecheck": "tsc -b tsconfig.json",
|
||||
"test": "vitest"
|
||||
},
|
||||
"exports": {
|
||||
"import": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/index.d.cts",
|
||||
"default": "./dist/index.cjs"
|
||||
}
|
||||
},
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npmjs.org",
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/honojs/middleware.git",
|
||||
"directory": "packages/mcp"
|
||||
},
|
||||
"homepage": "https://github.com/honojs/middleware",
|
||||
"peerDependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.12.0",
|
||||
"hono": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@arethetypeswrong/cli": "^0.17.4",
|
||||
"@modelcontextprotocol/sdk": "^1.12.0",
|
||||
"publint": "^0.3.9",
|
||||
"tsup": "^8.4.0",
|
||||
"typescript": "^5.8.2",
|
||||
"vitest": "^3.0.8",
|
||||
"zod": "^3.25.34"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,613 @@
|
|||
/**
|
||||
* @module
|
||||
* MCP HTTP Streaming Helper for Hono.
|
||||
*/
|
||||
import type { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js'
|
||||
import type {
|
||||
EventStore,
|
||||
StreamableHTTPServerTransportOptions,
|
||||
} from '@modelcontextprotocol/sdk/server/streamableHttp.js'
|
||||
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
|
||||
import {
|
||||
isInitializeRequest,
|
||||
isJSONRPCError,
|
||||
isJSONRPCRequest,
|
||||
isJSONRPCResponse,
|
||||
JSONRPCMessageSchema,
|
||||
} from '@modelcontextprotocol/sdk/types.js'
|
||||
import type { JSONRPCMessage, RequestId } from '@modelcontextprotocol/sdk/types.js'
|
||||
import type { Context } from 'hono'
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
import type { SSEStreamingApi } from 'hono/streaming'
|
||||
import { streamSSE } from './streaming'
|
||||
|
||||
export class StreamableHTTPTransport implements Transport {
|
||||
#started = false
|
||||
#initialized = false
|
||||
#onsessioninitialized?: (sessionId: string) => void
|
||||
#sessionIdGenerator?: () => string
|
||||
#eventStore?: EventStore
|
||||
#enableJsonResponse = false
|
||||
#standaloneSseStreamId = '_GET_stream'
|
||||
#streamMapping = new Map<
|
||||
string,
|
||||
{
|
||||
ctx: {
|
||||
header: (name: string, value: string) => void
|
||||
json: (data: unknown) => void
|
||||
}
|
||||
stream?: SSEStreamingApi
|
||||
}
|
||||
>()
|
||||
#requestToStreamMapping = new Map<RequestId, string>()
|
||||
#requestResponseMap = new Map<RequestId, JSONRPCMessage>()
|
||||
|
||||
sessionId?: string | undefined
|
||||
onclose?: () => void
|
||||
onerror?: (error: Error) => void
|
||||
onmessage?: (message: JSONRPCMessage, extra?: { authInfo?: AuthInfo }) => void
|
||||
|
||||
constructor(options?: StreamableHTTPServerTransportOptions) {
|
||||
this.#sessionIdGenerator = options?.sessionIdGenerator
|
||||
this.#enableJsonResponse = options?.enableJsonResponse ?? false
|
||||
this.#eventStore = options?.eventStore
|
||||
this.#onsessioninitialized = options?.onsessioninitialized
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the transport. This is required by the Transport interface but is a no-op
|
||||
* for the Streamable HTTP transport as connections are managed per-request.
|
||||
*/
|
||||
async start(): Promise<void> {
|
||||
if (this.#started) {
|
||||
throw new Error('Transport already started')
|
||||
}
|
||||
this.#started = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an incoming HTTP request, whether GET or POST
|
||||
*/
|
||||
async handleRequest(ctx: Context, parsedBody?: unknown): Promise<Response | undefined> {
|
||||
switch (ctx.req.method) {
|
||||
case 'GET':
|
||||
return this.handleGetRequest(ctx)
|
||||
case 'POST':
|
||||
return this.handlePostRequest(ctx, parsedBody)
|
||||
case 'DELETE':
|
||||
return this.handleDeleteRequest(ctx)
|
||||
default:
|
||||
return this.handleUnsupportedRequest(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles GET requests for SSE stream
|
||||
*/
|
||||
private async handleGetRequest(ctx: Context) {
|
||||
try {
|
||||
// The client MUST include an Accept header, listing text/event-stream as a supported content type.
|
||||
const acceptHeader = ctx.req.header('Accept')
|
||||
if (!acceptHeader?.includes('text/event-stream')) {
|
||||
throw new HTTPException(406, {
|
||||
res: Response.json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
message: 'Not Acceptable: Client must accept text/event-stream',
|
||||
},
|
||||
id: null,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
// If an Mcp-Session-Id is returned by the server during initialization,
|
||||
// clients using the Streamable HTTP transport MUST include it
|
||||
// in the Mcp-Session-Id header on all of their subsequent HTTP requests.
|
||||
this.validateSession(ctx)
|
||||
|
||||
// After initialization, always include the session ID if we have one
|
||||
if (this.sessionId !== undefined) {
|
||||
ctx.header('mcp-session-id', this.sessionId)
|
||||
}
|
||||
|
||||
let streamId: string | ((stream: SSEStreamingApi) => Promise<string>) =
|
||||
this.#standaloneSseStreamId
|
||||
|
||||
// Handle resumability: check for Last-Event-ID header
|
||||
if (this.#eventStore) {
|
||||
const lastEventId = ctx.req.header('last-event-id')
|
||||
if (lastEventId) {
|
||||
streamId = (stream) =>
|
||||
this.#eventStore!.replayEventsAfter(lastEventId, {
|
||||
send: async (eventId: string, message: JSONRPCMessage) => {
|
||||
try {
|
||||
await stream.writeSSE({
|
||||
id: eventId,
|
||||
event: 'message',
|
||||
data: JSON.stringify(message),
|
||||
})
|
||||
} catch {
|
||||
this.onerror?.(new Error('Failed replay events'))
|
||||
throw new HTTPException(500, {
|
||||
message: 'Failed replay events',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there's already an active standalone SSE stream for this session
|
||||
if (typeof streamId === 'string' && this.#streamMapping.get(streamId) !== undefined) {
|
||||
// Only one GET SSE stream is allowed per session
|
||||
throw new HTTPException(409, {
|
||||
res: Response.json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
message: 'Conflict: Only one SSE stream is allowed per session',
|
||||
},
|
||||
id: null,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
return streamSSE(ctx, async (stream) => {
|
||||
const resolvedStreamId = typeof streamId === 'string' ? streamId : await streamId(stream)
|
||||
|
||||
// Assign the response to the standalone SSE stream
|
||||
this.#streamMapping.set(resolvedStreamId, {
|
||||
ctx,
|
||||
stream,
|
||||
})
|
||||
|
||||
// Keep connection alive
|
||||
const keepAlive = setInterval(() => {
|
||||
if (!stream.closed) {
|
||||
stream.writeSSE({ data: '', event: 'ping' }).catch(() => clearInterval(keepAlive))
|
||||
}
|
||||
}, 30000)
|
||||
|
||||
// Set up close handler for client disconnects
|
||||
stream.onAbort(() => {
|
||||
this.#streamMapping.delete(resolvedStreamId)
|
||||
clearInterval(keepAlive)
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
if (error instanceof HTTPException) {
|
||||
throw error
|
||||
}
|
||||
|
||||
this.onerror?.(error as Error)
|
||||
|
||||
// return JSON-RPC formatted error
|
||||
throw new HTTPException(400, {
|
||||
res: Response.json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32700,
|
||||
message: 'Parse error',
|
||||
data: String(error),
|
||||
},
|
||||
id: null,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles POST requests containing JSON-RPC messages
|
||||
*/
|
||||
private async handlePostRequest(ctx: Context, parsedBody?: unknown) {
|
||||
try {
|
||||
// Validate the Accept header
|
||||
const acceptHeader = ctx.req.header('Accept')
|
||||
// The client MUST include an Accept header, listing both application/json and text/event-stream as supported content types.
|
||||
if (
|
||||
!acceptHeader?.includes('application/json') ||
|
||||
!acceptHeader.includes('text/event-stream')
|
||||
) {
|
||||
throw new HTTPException(406, {
|
||||
res: Response.json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
message:
|
||||
'Not Acceptable: Client must accept both application/json and text/event-stream',
|
||||
},
|
||||
id: null,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
const ct = ctx.req.header('Content-Type')
|
||||
if (!ct?.includes('application/json')) {
|
||||
throw new HTTPException(415, {
|
||||
res: Response.json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
message: 'Unsupported Media Type: Content-Type must be application/json',
|
||||
},
|
||||
id: null,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
const authInfo: AuthInfo | undefined = ctx.get('auth')
|
||||
|
||||
let rawMessage = parsedBody
|
||||
if (rawMessage === undefined) {
|
||||
rawMessage = await ctx.req.json()
|
||||
}
|
||||
|
||||
let messages: JSONRPCMessage[]
|
||||
|
||||
// handle batch and single messages
|
||||
if (Array.isArray(rawMessage)) {
|
||||
messages = rawMessage.map((msg) => JSONRPCMessageSchema.parse(msg))
|
||||
} else {
|
||||
messages = [JSONRPCMessageSchema.parse(rawMessage)]
|
||||
}
|
||||
|
||||
// Check if this is an initialization request
|
||||
// https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle/
|
||||
const isInitializationRequest = messages.some(isInitializeRequest)
|
||||
if (isInitializationRequest) {
|
||||
// If it's a server with session management and the session ID is already set we should reject the request
|
||||
// to avoid re-initialization.
|
||||
if (this.#initialized && this.sessionId !== undefined) {
|
||||
throw new HTTPException(400, {
|
||||
res: Response.json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32600,
|
||||
message: 'Invalid Request: Server already initialized',
|
||||
},
|
||||
id: null,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
if (messages.length > 1) {
|
||||
throw new HTTPException(400, {
|
||||
res: Response.json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32600,
|
||||
message: 'Invalid Request: Only one initialization request is allowed',
|
||||
},
|
||||
id: null,
|
||||
}),
|
||||
})
|
||||
}
|
||||
this.sessionId = this.#sessionIdGenerator?.()
|
||||
this.#initialized = true
|
||||
|
||||
// If we have a session ID and an onsessioninitialized handler, call it immediately
|
||||
// This is needed in cases where the server needs to keep track of multiple sessions
|
||||
if (this.sessionId && this.#onsessioninitialized) {
|
||||
this.#onsessioninitialized(this.sessionId)
|
||||
}
|
||||
}
|
||||
|
||||
// If an Mcp-Session-Id is returned by the server during initialization,
|
||||
// clients using the Streamable HTTP transport MUST include it
|
||||
// in the Mcp-Session-Id header on all of their subsequent HTTP requests.
|
||||
if (!isInitializationRequest) {
|
||||
this.validateSession(ctx)
|
||||
}
|
||||
|
||||
// check if it contains requests
|
||||
const hasRequests = messages.some(isJSONRPCRequest)
|
||||
|
||||
if (!hasRequests) {
|
||||
// handle each message
|
||||
for (const message of messages) {
|
||||
this.onmessage?.(message, { authInfo })
|
||||
}
|
||||
|
||||
// if it only contains notifications or responses, return 202
|
||||
return ctx.body(null, 202)
|
||||
}
|
||||
|
||||
if (hasRequests) {
|
||||
// The default behavior is to use SSE streaming
|
||||
// but in some cases server will return JSON responses
|
||||
const streamId = crypto.randomUUID()
|
||||
|
||||
if (!this.#enableJsonResponse && this.sessionId !== undefined) {
|
||||
ctx.header('mcp-session-id', this.sessionId)
|
||||
}
|
||||
|
||||
if (this.#enableJsonResponse) {
|
||||
// Store the response for this request to send messages back through this connection
|
||||
// We need to track by request ID to maintain the connection
|
||||
const result = await new Promise<any>((resolve) => {
|
||||
for (const message of messages) {
|
||||
if (isJSONRPCRequest(message)) {
|
||||
this.#streamMapping.set(streamId, {
|
||||
ctx: {
|
||||
header: ctx.header,
|
||||
json: resolve,
|
||||
},
|
||||
})
|
||||
this.#requestToStreamMapping.set(message.id, streamId)
|
||||
}
|
||||
}
|
||||
|
||||
// handle each message
|
||||
for (const message of messages) {
|
||||
this.onmessage?.(message, { authInfo })
|
||||
}
|
||||
})
|
||||
|
||||
return ctx.json(result)
|
||||
}
|
||||
|
||||
return streamSSE(ctx, async (stream) => {
|
||||
// Store the response for this request to send messages back through this connection
|
||||
// We need to track by request ID to maintain the connection
|
||||
for (const message of messages) {
|
||||
if (isJSONRPCRequest(message)) {
|
||||
this.#streamMapping.set(streamId, {
|
||||
ctx,
|
||||
stream,
|
||||
})
|
||||
this.#requestToStreamMapping.set(message.id, streamId)
|
||||
}
|
||||
}
|
||||
|
||||
// Set up close handler for client disconnects
|
||||
stream.onAbort(() => {
|
||||
this.#streamMapping.delete(streamId)
|
||||
})
|
||||
|
||||
// handle each message
|
||||
for (const message of messages) {
|
||||
this.onmessage?.(message, { authInfo })
|
||||
}
|
||||
// The server SHOULD NOT close the SSE stream before sending all JSON-RPC responses
|
||||
// This will be handled by the send() method when responses are ready
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof HTTPException) {
|
||||
throw error
|
||||
}
|
||||
|
||||
this.onerror?.(error as Error)
|
||||
|
||||
// return JSON-RPC formatted error
|
||||
throw new HTTPException(400, {
|
||||
res: Response.json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32700,
|
||||
message: 'Parse error',
|
||||
data: String(error),
|
||||
},
|
||||
id: null,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles DELETE requests to terminate sessions
|
||||
*/
|
||||
private async handleDeleteRequest(ctx: Context) {
|
||||
this.validateSession(ctx)
|
||||
|
||||
await this.close()
|
||||
|
||||
return ctx.body(null, 200)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles unsupported requests (PUT, PATCH, etc.)
|
||||
*/
|
||||
private handleUnsupportedRequest(ctx: Context) {
|
||||
return ctx.json(
|
||||
{
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
message: 'Method not allowed.',
|
||||
},
|
||||
id: null,
|
||||
},
|
||||
{
|
||||
status: 405,
|
||||
headers: {
|
||||
Allow: 'GET, POST, DELETE',
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates session ID for non-initialization requests
|
||||
* Returns true if the session is valid, false otherwise
|
||||
*/
|
||||
private validateSession(ctx: Context): boolean {
|
||||
if (this.#sessionIdGenerator === undefined) {
|
||||
// If the sessionIdGenerator ID is not set, the session management is disabled
|
||||
// and we don't need to validate the session ID
|
||||
return true
|
||||
}
|
||||
if (!this.#initialized) {
|
||||
// If the server has not been initialized yet, reject all requests
|
||||
throw new HTTPException(400, {
|
||||
res: Response.json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
message: 'Bad Request: Server not initialized',
|
||||
},
|
||||
id: null,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
const sessionId = ctx.req.header('mcp-session-id')
|
||||
|
||||
if (!sessionId) {
|
||||
// Non-initialization requests without a session ID should return 400 Bad Request
|
||||
throw new HTTPException(400, {
|
||||
res: Response.json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
message: 'Bad Request: Mcp-Session-Id header is required',
|
||||
},
|
||||
id: null,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
if (Array.isArray(sessionId)) {
|
||||
throw new HTTPException(400, {
|
||||
res: Response.json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
message: 'Bad Request: Mcp-Session-Id header must be a single value',
|
||||
},
|
||||
id: null,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
if (sessionId !== this.sessionId) {
|
||||
// Reject requests with invalid session ID with 404 Not Found
|
||||
throw new HTTPException(404, {
|
||||
res: Response.json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32001,
|
||||
message: 'Session not found',
|
||||
},
|
||||
id: null,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
// Close all SSE connections
|
||||
|
||||
for (const { stream } of this.#streamMapping.values()) {
|
||||
stream?.close()
|
||||
}
|
||||
|
||||
this.#streamMapping.clear()
|
||||
|
||||
// Clear any pending responses
|
||||
this.#requestResponseMap.clear()
|
||||
this.onclose?.()
|
||||
}
|
||||
|
||||
async send(message: JSONRPCMessage, options?: { relatedRequestId?: RequestId }): Promise<void> {
|
||||
let requestId = options?.relatedRequestId
|
||||
if (isJSONRPCResponse(message) || isJSONRPCError(message)) {
|
||||
// If the message is a response, use the request ID from the message
|
||||
requestId = message.id
|
||||
}
|
||||
|
||||
// Check if this message should be sent on the standalone SSE stream (no request ID)
|
||||
// Ignore notifications from tools (which have relatedRequestId set)
|
||||
// Those will be sent via dedicated response SSE streams
|
||||
if (requestId === undefined) {
|
||||
// For standalone SSE streams, we can only send requests and notifications
|
||||
if (isJSONRPCResponse(message) || isJSONRPCError(message)) {
|
||||
throw new Error(
|
||||
'Cannot send a response on a standalone SSE stream unless resuming a previous client request'
|
||||
)
|
||||
}
|
||||
const standaloneSse = this.#streamMapping.get(this.#standaloneSseStreamId)
|
||||
|
||||
if (standaloneSse === undefined) {
|
||||
// The spec says the server MAY send messages on the stream, so it's ok to discard if no stream
|
||||
return
|
||||
}
|
||||
|
||||
// Generate and store event ID if event store is provided
|
||||
let eventId: string | undefined
|
||||
if (this.#eventStore) {
|
||||
// Stores the event and gets the generated event ID
|
||||
eventId = await this.#eventStore.storeEvent(this.#standaloneSseStreamId, message)
|
||||
}
|
||||
|
||||
// Send the message to the standalone SSE stream
|
||||
return standaloneSse.stream?.writeSSE({
|
||||
id: eventId,
|
||||
event: 'message',
|
||||
data: JSON.stringify(message),
|
||||
})
|
||||
}
|
||||
|
||||
// Get the response for this request
|
||||
const streamId = this.#requestToStreamMapping.get(requestId)
|
||||
const response = this.#streamMapping.get(streamId!)
|
||||
if (!streamId) {
|
||||
throw new Error(`No connection established for request ID: ${String(requestId)}`)
|
||||
}
|
||||
|
||||
if (!this.#enableJsonResponse) {
|
||||
// For SSE responses, generate event ID if event store is provided
|
||||
let eventId: string | undefined
|
||||
|
||||
if (this.#eventStore) {
|
||||
eventId = await this.#eventStore.storeEvent(streamId, message)
|
||||
}
|
||||
|
||||
if (response) {
|
||||
// Write the event to the response stream
|
||||
await response.stream?.writeSSE({
|
||||
id: eventId,
|
||||
event: 'message',
|
||||
data: JSON.stringify(message),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (isJSONRPCResponse(message) || isJSONRPCError(message)) {
|
||||
this.#requestResponseMap.set(requestId, message)
|
||||
const relatedIds = Array.from(this.#requestToStreamMapping.entries())
|
||||
.filter(([, streamId]) => this.#streamMapping.get(streamId) === response)
|
||||
.map(([id]) => id)
|
||||
|
||||
// Check if we have responses for all requests using this connection
|
||||
const allResponsesReady = relatedIds.every((id) => this.#requestResponseMap.has(id))
|
||||
|
||||
if (allResponsesReady) {
|
||||
if (!response) {
|
||||
throw new Error(`No connection established for request ID: ${String(requestId)}`)
|
||||
}
|
||||
if (this.#enableJsonResponse) {
|
||||
// All responses ready, send as JSON
|
||||
if (this.sessionId !== undefined) {
|
||||
response.ctx.header('mcp-session-id', this.sessionId)
|
||||
}
|
||||
|
||||
const responses = relatedIds.map((id) => this.#requestResponseMap.get(id)!)
|
||||
|
||||
response.ctx.json(responses.length === 1 ? responses[0] : responses)
|
||||
return
|
||||
} else {
|
||||
response.stream?.close()
|
||||
}
|
||||
// Clean up
|
||||
for (const id of relatedIds) {
|
||||
this.#requestResponseMap.delete(id)
|
||||
this.#requestToStreamMapping.delete(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
import type { Context } from 'hono'
|
||||
import { SSEStreamingApi } from 'hono/streaming'
|
||||
|
||||
let isOldBunVersion = (): boolean => {
|
||||
// @ts-expect-error @types/bun is not installed
|
||||
const version: string = typeof Bun !== 'undefined' ? Bun.version : undefined
|
||||
if (version === undefined) {
|
||||
return false
|
||||
}
|
||||
const result = version.startsWith('1.1') || version.startsWith('1.0') || version.startsWith('0.')
|
||||
// Avoid running this check on every call
|
||||
isOldBunVersion = () => result
|
||||
return result
|
||||
}
|
||||
|
||||
const run = async (
|
||||
stream: SSEStreamingApi,
|
||||
cb: (stream: SSEStreamingApi) => Promise<void>,
|
||||
onError?: (e: Error, stream: SSEStreamingApi) => Promise<void>
|
||||
): Promise<void> => {
|
||||
try {
|
||||
await cb(stream)
|
||||
} catch (e) {
|
||||
if (e instanceof Error && onError) {
|
||||
await onError(e, stream)
|
||||
|
||||
await stream.writeSSE({
|
||||
event: 'error',
|
||||
data: e.message,
|
||||
})
|
||||
} else {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const contextStash: WeakMap<ReadableStream, Context> = new WeakMap<ReadableStream, Context>()
|
||||
|
||||
export const streamSSE = (
|
||||
c: Context,
|
||||
cb: (stream: SSEStreamingApi) => Promise<void>,
|
||||
onError?: (e: Error, stream: SSEStreamingApi) => Promise<void>
|
||||
): Response => {
|
||||
const { readable, writable } = new TransformStream()
|
||||
const stream = new SSEStreamingApi(writable, readable)
|
||||
|
||||
// Until Bun v1.1.27, Bun didn't call cancel() on the ReadableStream for Response objects from Bun.serve()
|
||||
if (isOldBunVersion()) {
|
||||
c.req.raw.signal.addEventListener('abort', () => {
|
||||
if (!stream.closed) {
|
||||
stream.abort()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// in bun, `c` is destroyed when the request is returned, so hold it until the end of streaming
|
||||
contextStash.set(stream.responseReadable, c)
|
||||
|
||||
c.header('Transfer-Encoding', 'chunked')
|
||||
c.header('Content-Type', 'text/event-stream')
|
||||
c.header('Cache-Control', 'no-cache')
|
||||
c.header('Connection', 'keep-alive')
|
||||
|
||||
run(stream, cb, onError)
|
||||
|
||||
return c.newResponse(stream.responseReadable)
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo",
|
||||
"emitDeclarationOnly": true,
|
||||
"isolatedDeclarations": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["**/*.test.ts"],
|
||||
"references": []
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc/packages/hello",
|
||||
"types": ["vitest/globals"]
|
||||
},
|
||||
"include": ["**/*.test.ts", "vitest.config.ts"],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { defineProject } from 'vitest/config'
|
||||
|
||||
export default defineProject({
|
||||
test: {
|
||||
globals: true,
|
||||
},
|
||||
})
|
329
yarn.lock
329
yarn.lock
|
@ -2070,6 +2070,23 @@ __metadata:
|
|||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@hono/mcp@workspace:packages/mcp":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@hono/mcp@workspace:packages/mcp"
|
||||
dependencies:
|
||||
"@arethetypeswrong/cli": "npm:^0.17.4"
|
||||
"@modelcontextprotocol/sdk": "npm:^1.12.0"
|
||||
publint: "npm:^0.3.9"
|
||||
tsup: "npm:^8.4.0"
|
||||
typescript: "npm:^5.8.2"
|
||||
vitest: "npm:^3.0.8"
|
||||
zod: "npm:^3.25.34"
|
||||
peerDependencies:
|
||||
"@modelcontextprotocol/sdk": ^1.12.0
|
||||
hono: "*"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@hono/medley-router@workspace:packages/medley-router":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@hono/medley-router@workspace:packages/medley-router"
|
||||
|
@ -2898,6 +2915,25 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@modelcontextprotocol/sdk@npm:^1.12.0":
|
||||
version: 1.12.0
|
||||
resolution: "@modelcontextprotocol/sdk@npm:1.12.0"
|
||||
dependencies:
|
||||
ajv: "npm:^6.12.6"
|
||||
content-type: "npm:^1.0.5"
|
||||
cors: "npm:^2.8.5"
|
||||
cross-spawn: "npm:^7.0.5"
|
||||
eventsource: "npm:^3.0.2"
|
||||
express: "npm:^5.0.1"
|
||||
express-rate-limit: "npm:^7.5.0"
|
||||
pkce-challenge: "npm:^5.0.0"
|
||||
raw-body: "npm:^3.0.0"
|
||||
zod: "npm:^3.23.8"
|
||||
zod-to-json-schema: "npm:^3.24.1"
|
||||
checksum: 10c0/daa71d3005dc4f02ca0e251c603cd2894cbde972a07f02c2b5d728e6d4608b141b357160a53c718caed877374a2270e26d228b48b0caf6f5bf1416de713a57e1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mswjs/interceptors@npm:^0.37.0":
|
||||
version: 0.37.6
|
||||
resolution: "@mswjs/interceptors@npm:0.37.6"
|
||||
|
@ -4434,6 +4470,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"accepts@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "accepts@npm:2.0.0"
|
||||
dependencies:
|
||||
mime-types: "npm:^3.0.0"
|
||||
negotiator: "npm:^1.0.0"
|
||||
checksum: 10c0/98374742097e140891546076215f90c32644feacf652db48412329de4c2a529178a81aa500fbb13dd3e6cbf6e68d829037b123ac037fc9a08bcec4b87b358eef
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"accepts@npm:~1.3.8":
|
||||
version: 1.3.8
|
||||
resolution: "accepts@npm:1.3.8"
|
||||
|
@ -4541,7 +4587,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ajv@npm:^6.12.4":
|
||||
"ajv@npm:^6.12.4, ajv@npm:^6.12.6":
|
||||
version: 6.12.6
|
||||
resolution: "ajv@npm:6.12.6"
|
||||
dependencies:
|
||||
|
@ -4976,6 +5022,23 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"body-parser@npm:^2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "body-parser@npm:2.2.0"
|
||||
dependencies:
|
||||
bytes: "npm:^3.1.2"
|
||||
content-type: "npm:^1.0.5"
|
||||
debug: "npm:^4.4.0"
|
||||
http-errors: "npm:^2.0.0"
|
||||
iconv-lite: "npm:^0.6.3"
|
||||
on-finished: "npm:^2.4.1"
|
||||
qs: "npm:^6.14.0"
|
||||
raw-body: "npm:^3.0.0"
|
||||
type-is: "npm:^2.0.0"
|
||||
checksum: 10c0/a9ded39e71ac9668e2211afa72e82ff86cc5ef94de1250b7d1ba9cc299e4150408aaa5f1e8b03dd4578472a3ce6d1caa2a23b27a6c18e526e48b4595174c116c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"boolbase@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "boolbase@npm:1.0.0"
|
||||
|
@ -5105,7 +5168,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bytes@npm:3.1.2":
|
||||
"bytes@npm:3.1.2, bytes@npm:^3.1.2":
|
||||
version: 3.1.2
|
||||
resolution: "bytes@npm:3.1.2"
|
||||
checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e
|
||||
|
@ -5786,7 +5849,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"content-type@npm:^1.0.4, content-type@npm:~1.0.4, content-type@npm:~1.0.5":
|
||||
"content-disposition@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "content-disposition@npm:1.0.0"
|
||||
dependencies:
|
||||
safe-buffer: "npm:5.2.1"
|
||||
checksum: 10c0/c7b1ba0cea2829da0352ebc1b7f14787c73884bc707c8bc2271d9e3bf447b372270d09f5d3980dc5037c749ceef56b9a13fccd0b0001c87c3f12579967e4dd27
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"content-type@npm:^1.0.4, content-type@npm:^1.0.5, content-type@npm:~1.0.4, content-type@npm:~1.0.5":
|
||||
version: 1.0.5
|
||||
resolution: "content-type@npm:1.0.5"
|
||||
checksum: 10c0/b76ebed15c000aee4678c3707e0860cb6abd4e680a598c0a26e17f0bfae723ec9cc2802f0ff1bc6e4d80603719010431d2231018373d4dde10f9ccff9dadf5af
|
||||
|
@ -5807,6 +5879,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cookie-signature@npm:^1.2.1":
|
||||
version: 1.2.2
|
||||
resolution: "cookie-signature@npm:1.2.2"
|
||||
checksum: 10c0/54e05df1a293b3ce81589b27dddc445f462f6fa6812147c033350cd3561a42bc14481674e05ed14c7bd0ce1e8bb3dc0e40851bad75415733711294ddce0b7bc6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cookie@npm:0.6.0":
|
||||
version: 0.6.0
|
||||
resolution: "cookie@npm:0.6.0"
|
||||
|
@ -5835,7 +5914,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cookie@npm:^0.7.2":
|
||||
"cookie@npm:^0.7.1, cookie@npm:^0.7.2":
|
||||
version: 0.7.2
|
||||
resolution: "cookie@npm:0.7.2"
|
||||
checksum: 10c0/9596e8ccdbf1a3a88ae02cf5ee80c1c50959423e1022e4e60b91dd87c622af1da309253d8abdb258fb5e3eacb4f08e579dc58b4897b8087574eee0fd35dfa5d2
|
||||
|
@ -6046,6 +6125,18 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"debug@npm:^4.3.5":
|
||||
version: 4.4.1
|
||||
resolution: "debug@npm:4.4.1"
|
||||
dependencies:
|
||||
ms: "npm:^2.1.3"
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
checksum: 10c0/d2b44bc1afd912b49bb7ebb0d50a860dc93a4dd7d946e8de94abc957bb63726b7dd5aa48c18c2386c379ec024c46692e15ed3ed97d481729f929201e671fcd55
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"decode-named-character-reference@npm:^1.0.0":
|
||||
version: 1.1.0
|
||||
resolution: "decode-named-character-reference@npm:1.1.0"
|
||||
|
@ -6138,7 +6229,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"depd@npm:2.0.0, depd@npm:~2.0.0":
|
||||
"depd@npm:2.0.0, depd@npm:^2.0.0, depd@npm:~2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "depd@npm:2.0.0"
|
||||
checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c
|
||||
|
@ -6389,6 +6480,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"encodeurl@npm:^2.0.0, encodeurl@npm:~2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "encodeurl@npm:2.0.0"
|
||||
checksum: 10c0/5d317306acb13e6590e28e27924c754163946a2480de11865c991a3a7eed4315cd3fba378b543ca145829569eefe9b899f3d84bb09870f675ae60bc924b01ceb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"encodeurl@npm:~1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "encodeurl@npm:1.0.2"
|
||||
|
@ -6396,13 +6494,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"encodeurl@npm:~2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "encodeurl@npm:2.0.0"
|
||||
checksum: 10c0/5d317306acb13e6590e28e27924c754163946a2480de11865c991a3a7eed4315cd3fba378b543ca145829569eefe9b899f3d84bb09870f675ae60bc924b01ceb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"encoding@npm:^0.1.13":
|
||||
version: 0.1.13
|
||||
resolution: "encoding@npm:0.1.13"
|
||||
|
@ -6881,7 +6972,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"escape-html@npm:~1.0.3":
|
||||
"escape-html@npm:^1.0.3, escape-html@npm:~1.0.3":
|
||||
version: 1.0.3
|
||||
resolution: "escape-html@npm:1.0.3"
|
||||
checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3
|
||||
|
@ -7240,7 +7331,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"etag@npm:~1.8.1":
|
||||
"etag@npm:^1.8.1, etag@npm:~1.8.1":
|
||||
version: 1.8.1
|
||||
resolution: "etag@npm:1.8.1"
|
||||
checksum: 10c0/12be11ef62fb9817314d790089a0a49fae4e1b50594135dcb8076312b7d7e470884b5100d249b28c18581b7fd52f8b485689ffae22a11ed9ec17377a33a08f84
|
||||
|
@ -7268,6 +7359,22 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eventsource-parser@npm:^3.0.1":
|
||||
version: 3.0.2
|
||||
resolution: "eventsource-parser@npm:3.0.2"
|
||||
checksum: 10c0/067c6e60b7c68a4577630cc7e11d2aaeef52005e377a213308c7c2350596a175d5a179671d85f570726dce3f451c15d174ece4479ce68a1805686c88950d08dd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eventsource@npm:^3.0.2":
|
||||
version: 3.0.7
|
||||
resolution: "eventsource@npm:3.0.7"
|
||||
dependencies:
|
||||
eventsource-parser: "npm:^3.0.1"
|
||||
checksum: 10c0/c48a73c38f300e33e9f11375d4ee969f25cbb0519608a12378a38068055ae8b55b6e0e8a49c3f91c784068434efe1d9f01eb49b6315b04b0da9157879ce2f67d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"exegesis-express@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "exegesis-express@npm:4.0.0"
|
||||
|
@ -7347,6 +7454,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"express-rate-limit@npm:^7.5.0":
|
||||
version: 7.5.0
|
||||
resolution: "express-rate-limit@npm:7.5.0"
|
||||
peerDependencies:
|
||||
express: ^4.11 || 5 || ^5.0.0-beta.1
|
||||
checksum: 10c0/3e96afa05b4f577395688ede37e0cb19901f20c350b32575fb076f3d25176209fb88d3648151755c232aaf304147c58531f070757978f376e2f08326449299fd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"express@npm:^4.16.4":
|
||||
version: 4.21.2
|
||||
resolution: "express@npm:4.21.2"
|
||||
|
@ -7386,6 +7502,41 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"express@npm:^5.0.1":
|
||||
version: 5.1.0
|
||||
resolution: "express@npm:5.1.0"
|
||||
dependencies:
|
||||
accepts: "npm:^2.0.0"
|
||||
body-parser: "npm:^2.2.0"
|
||||
content-disposition: "npm:^1.0.0"
|
||||
content-type: "npm:^1.0.5"
|
||||
cookie: "npm:^0.7.1"
|
||||
cookie-signature: "npm:^1.2.1"
|
||||
debug: "npm:^4.4.0"
|
||||
encodeurl: "npm:^2.0.0"
|
||||
escape-html: "npm:^1.0.3"
|
||||
etag: "npm:^1.8.1"
|
||||
finalhandler: "npm:^2.1.0"
|
||||
fresh: "npm:^2.0.0"
|
||||
http-errors: "npm:^2.0.0"
|
||||
merge-descriptors: "npm:^2.0.0"
|
||||
mime-types: "npm:^3.0.0"
|
||||
on-finished: "npm:^2.4.1"
|
||||
once: "npm:^1.4.0"
|
||||
parseurl: "npm:^1.3.3"
|
||||
proxy-addr: "npm:^2.0.7"
|
||||
qs: "npm:^6.14.0"
|
||||
range-parser: "npm:^1.2.1"
|
||||
router: "npm:^2.2.0"
|
||||
send: "npm:^1.1.0"
|
||||
serve-static: "npm:^2.2.0"
|
||||
statuses: "npm:^2.0.1"
|
||||
type-is: "npm:^2.0.1"
|
||||
vary: "npm:^1.1.2"
|
||||
checksum: 10c0/80ce7c53c5f56887d759b94c3f2283e2e51066c98d4b72a4cc1338e832b77f1e54f30d0239cc10815a0f849bdb753e6a284d2fa48d4ab56faf9c501f55d751d6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"exsolve@npm:^1.0.1":
|
||||
version: 1.0.4
|
||||
resolution: "exsolve@npm:1.0.4"
|
||||
|
@ -7574,6 +7725,20 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"finalhandler@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "finalhandler@npm:2.1.0"
|
||||
dependencies:
|
||||
debug: "npm:^4.4.0"
|
||||
encodeurl: "npm:^2.0.0"
|
||||
escape-html: "npm:^1.0.3"
|
||||
on-finished: "npm:^2.4.1"
|
||||
parseurl: "npm:^1.3.3"
|
||||
statuses: "npm:^2.0.1"
|
||||
checksum: 10c0/da0bbca6d03873472ee890564eb2183f4ed377f25f3628a0fc9d16dac40bed7b150a0d82ebb77356e4c6d97d2796ad2dba22948b951dddee2c8768b0d1b9fb1f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"find-cache-dir@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "find-cache-dir@npm:5.0.0"
|
||||
|
@ -7772,6 +7937,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fresh@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "fresh@npm:2.0.0"
|
||||
checksum: 10c0/0557548194cb9a809a435bf92bcfbc20c89e8b5eb38861b73ced36750437251e39a111fc3a18b98531be9dd91fe1411e4969f229dc579ec0251ce6c5d4900bbc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fs-extra@npm:^10.1.0":
|
||||
version: 10.1.0
|
||||
resolution: "fs-extra@npm:10.1.0"
|
||||
|
@ -8392,7 +8564,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"http-errors@npm:2.0.0":
|
||||
"http-errors@npm:2.0.0, http-errors@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "http-errors@npm:2.0.0"
|
||||
dependencies:
|
||||
|
@ -8464,7 +8636,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"iconv-lite@npm:^0.6.2":
|
||||
"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3":
|
||||
version: 0.6.3
|
||||
resolution: "iconv-lite@npm:0.6.3"
|
||||
dependencies:
|
||||
|
@ -9882,6 +10054,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"media-typer@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "media-typer@npm:1.1.0"
|
||||
checksum: 10c0/7b4baa40b25964bb90e2121ee489ec38642127e48d0cc2b6baa442688d3fde6262bfdca86d6bbf6ba708784afcac168c06840c71facac70e390f5f759ac121b9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"merge-descriptors@npm:1.0.3":
|
||||
version: 1.0.3
|
||||
resolution: "merge-descriptors@npm:1.0.3"
|
||||
|
@ -9889,6 +10068,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"merge-descriptors@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "merge-descriptors@npm:2.0.0"
|
||||
checksum: 10c0/95389b7ced3f9b36fbdcf32eb946dc3dd1774c2fdf164609e55b18d03aa499b12bd3aae3a76c1c7185b96279e9803525550d3eb292b5224866060a288f335cb3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"merge2@npm:^1.3.0, merge2@npm:^1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "merge2@npm:1.4.1"
|
||||
|
@ -10264,7 +10450,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mime-db@npm:>= 1.43.0 < 2":
|
||||
"mime-db@npm:>= 1.43.0 < 2, mime-db@npm:^1.54.0":
|
||||
version: 1.54.0
|
||||
resolution: "mime-db@npm:1.54.0"
|
||||
checksum: 10c0/8d907917bc2a90fa2df842cdf5dfeaf509adc15fe0531e07bb2f6ab15992416479015828d6a74200041c492e42cce3ebf78e5ce714388a0a538ea9c53eece284
|
||||
|
@ -10280,6 +10466,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mime-types@npm:^3.0.0, mime-types@npm:^3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "mime-types@npm:3.0.1"
|
||||
dependencies:
|
||||
mime-db: "npm:^1.54.0"
|
||||
checksum: 10c0/bd8c20d3694548089cf229016124f8f40e6a60bbb600161ae13e45f793a2d5bb40f96bbc61f275836696179c77c1d6bf4967b2a75e0a8ad40fe31f4ed5be4da5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mime@npm:1.6.0":
|
||||
version: 1.6.0
|
||||
resolution: "mime@npm:1.6.0"
|
||||
|
@ -10927,7 +11122,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"on-finished@npm:2.4.1, on-finished@npm:^2.2.0":
|
||||
"on-finished@npm:2.4.1, on-finished@npm:^2.2.0, on-finished@npm:^2.4.1":
|
||||
version: 2.4.1
|
||||
resolution: "on-finished@npm:2.4.1"
|
||||
dependencies:
|
||||
|
@ -11483,6 +11678,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pkce-challenge@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "pkce-challenge@npm:5.0.0"
|
||||
checksum: 10c0/c6706d627fdbb6f22bf8cc5d60d96d6b6a7bb481399b336a3d3f4e9bfba3e167a2c32f8ec0b5e74be686a0ba3bcc9894865d4c2dd1b91cea4c05dba1f28602c3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pkg-dir@npm:^7.0.0":
|
||||
version: 7.0.0
|
||||
resolution: "pkg-dir@npm:7.0.0"
|
||||
|
@ -11757,7 +11959,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"proxy-addr@npm:~2.0.7":
|
||||
"proxy-addr@npm:^2.0.7, proxy-addr@npm:~2.0.7":
|
||||
version: 2.0.7
|
||||
resolution: "proxy-addr@npm:2.0.7"
|
||||
dependencies:
|
||||
|
@ -11864,7 +12066,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"qs@npm:^6.6.0":
|
||||
"qs@npm:^6.14.0, qs@npm:^6.6.0":
|
||||
version: 6.14.0
|
||||
resolution: "qs@npm:6.14.0"
|
||||
dependencies:
|
||||
|
@ -11930,7 +12132,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"range-parser@npm:~1.2.1":
|
||||
"range-parser@npm:^1.2.1, range-parser@npm:~1.2.1":
|
||||
version: 1.2.1
|
||||
resolution: "range-parser@npm:1.2.1"
|
||||
checksum: 10c0/96c032ac2475c8027b7a4e9fe22dc0dfe0f6d90b85e496e0f016fbdb99d6d066de0112e680805075bd989905e2123b3b3d002765149294dce0c1f7f01fcc2ea0
|
||||
|
@ -11949,6 +12151,18 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"raw-body@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "raw-body@npm:3.0.0"
|
||||
dependencies:
|
||||
bytes: "npm:3.1.2"
|
||||
http-errors: "npm:2.0.0"
|
||||
iconv-lite: "npm:0.6.3"
|
||||
unpipe: "npm:1.0.0"
|
||||
checksum: 10c0/f8daf4b724064a4811d118745a781ca0fb4676298b8adadfd6591155549cfea0a067523cf7dd3baeb1265fecc9ce5dfb2fc788c12c66b85202a336593ece0f87
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rc@npm:^1.2.8":
|
||||
version: 1.2.8
|
||||
resolution: "rc@npm:1.2.8"
|
||||
|
@ -12604,6 +12818,19 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"router@npm:^2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "router@npm:2.2.0"
|
||||
dependencies:
|
||||
debug: "npm:^4.4.0"
|
||||
depd: "npm:^2.0.0"
|
||||
is-promise: "npm:^4.0.0"
|
||||
parseurl: "npm:^1.3.3"
|
||||
path-to-regexp: "npm:^8.0.0"
|
||||
checksum: 10c0/3279de7450c8eae2f6e095e9edacbdeec0abb5cb7249c6e719faa0db2dba43574b4fff5892d9220631c9abaff52dd3cad648cfea2aaace845e1a071915ac8867
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rspack-resolver@npm:^1.2.2":
|
||||
version: 1.2.2
|
||||
resolution: "rspack-resolver@npm:1.2.2"
|
||||
|
@ -12794,6 +13021,25 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"send@npm:^1.1.0, send@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "send@npm:1.2.0"
|
||||
dependencies:
|
||||
debug: "npm:^4.3.5"
|
||||
encodeurl: "npm:^2.0.0"
|
||||
escape-html: "npm:^1.0.3"
|
||||
etag: "npm:^1.8.1"
|
||||
fresh: "npm:^2.0.0"
|
||||
http-errors: "npm:^2.0.0"
|
||||
mime-types: "npm:^3.0.1"
|
||||
ms: "npm:^2.1.3"
|
||||
on-finished: "npm:^2.4.1"
|
||||
range-parser: "npm:^1.2.1"
|
||||
statuses: "npm:^2.0.1"
|
||||
checksum: 10c0/531bcfb5616948d3468d95a1fd0adaeb0c20818ba4a500f439b800ca2117971489e02074ce32796fd64a6772ea3e7235fe0583d8241dbd37a053dc3378eff9a5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"serve-static@npm:1.16.2":
|
||||
version: 1.16.2
|
||||
resolution: "serve-static@npm:1.16.2"
|
||||
|
@ -12806,6 +13052,18 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"serve-static@npm:^2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "serve-static@npm:2.2.0"
|
||||
dependencies:
|
||||
encodeurl: "npm:^2.0.0"
|
||||
escape-html: "npm:^1.0.3"
|
||||
parseurl: "npm:^1.3.3"
|
||||
send: "npm:^1.2.0"
|
||||
checksum: 10c0/30e2ed1dbff1984836cfd0c65abf5d3f3f83bcd696c99d2d3c97edbd4e2a3ff4d3f87108a7d713640d290a7b6fe6c15ddcbc61165ab2eaad48ea8d3b52c7f913
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"set-function-length@npm:^1.1.1":
|
||||
version: 1.1.1
|
||||
resolution: "set-function-length@npm:1.1.1"
|
||||
|
@ -13979,6 +14237,17 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"type-is@npm:^2.0.0, type-is@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "type-is@npm:2.0.1"
|
||||
dependencies:
|
||||
content-type: "npm:^1.0.5"
|
||||
media-typer: "npm:^1.1.0"
|
||||
mime-types: "npm:^3.0.0"
|
||||
checksum: 10c0/7f7ec0a060b16880bdad36824ab37c26019454b67d73e8a465ed5a3587440fbe158bc765f0da68344498235c877e7dbbb1600beccc94628ed05599d667951b99
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"type-is@npm:~1.6.18":
|
||||
version: 1.6.18
|
||||
resolution: "type-is@npm:1.6.18"
|
||||
|
@ -14520,7 +14789,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vary@npm:^1, vary@npm:~1.1.2":
|
||||
"vary@npm:^1, vary@npm:^1.1.2, vary@npm:~1.1.2":
|
||||
version: 1.1.2
|
||||
resolution: "vary@npm:1.1.2"
|
||||
checksum: 10c0/f15d588d79f3675135ba783c91a4083dcd290a2a5be9fcb6514220a1634e23df116847b1cc51f66bfb0644cf9353b2abb7815ae499bab06e46dd33c1a6bf1f4f
|
||||
|
@ -15196,6 +15465,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod-to-json-schema@npm:^3.24.1":
|
||||
version: 3.24.5
|
||||
resolution: "zod-to-json-schema@npm:3.24.5"
|
||||
peerDependencies:
|
||||
zod: ^3.24.1
|
||||
checksum: 10c0/0745b94ba53e652d39f262641cdeb2f75d24339fb6076a38ce55bcf53d82dfaea63adf524ebc5f658681005401687f8e9551c4feca7c4c882e123e66091dfb90
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod@npm:3.22.3":
|
||||
version: 3.22.3
|
||||
resolution: "zod@npm:3.22.3"
|
||||
|
@ -15231,6 +15509,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod@npm:^3.25.34":
|
||||
version: 3.25.34
|
||||
resolution: "zod@npm:3.25.34"
|
||||
checksum: 10c0/29f836f6dacace3ae247318362019937f429146676f6d185dbf62f808bba3783dff11f60d49864c4d9589b3d85c353a9fbd36c2fbe8c91b06447ab600243a6c5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod@npm:~3.25.6":
|
||||
version: 3.25.36
|
||||
resolution: "zod@npm:3.25.36"
|
||||
|
|
Loading…
Reference in New Issue