cprime-supergateway 3.4.3

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 (62) hide show
  1. package/.github/workflows/docker-publish.yaml +79 -0
  2. package/.husky/pre-commit +17 -0
  3. package/.prettierignore +8 -0
  4. package/.prettierrc +5 -0
  5. package/AGENTS.md +29 -0
  6. package/LICENSE +21 -0
  7. package/README.md +348 -0
  8. package/dist/gateways/sseToStdio.js +139 -0
  9. package/dist/gateways/stdioToSse.js +147 -0
  10. package/dist/gateways/stdioToStatefulStreamableHttp.js +188 -0
  11. package/dist/gateways/stdioToStatelessStreamableHttp.js +208 -0
  12. package/dist/gateways/stdioToWs.js +113 -0
  13. package/dist/gateways/streamableHttpToStdio.js +134 -0
  14. package/dist/index.js +266 -0
  15. package/dist/lib/corsOrigin.js +23 -0
  16. package/dist/lib/getLogger.js +44 -0
  17. package/dist/lib/getVersion.js +16 -0
  18. package/dist/lib/headers.js +31 -0
  19. package/dist/lib/onSignals.js +27 -0
  20. package/dist/lib/serializeCorsOrigin.js +6 -0
  21. package/dist/lib/sessionAccessCounter.js +77 -0
  22. package/dist/server/websocket.js +102 -0
  23. package/dist/services/EncryptionService.js +236 -0
  24. package/dist/types.js +1 -0
  25. package/docker/base.Dockerfile +9 -0
  26. package/docker/deno.Dockerfile +2 -0
  27. package/docker/uvx.Dockerfile +3 -0
  28. package/docker-bake.hcl +51 -0
  29. package/package.json +61 -0
  30. package/scripts/decrypt-sample.ts +34 -0
  31. package/scripts/encryption-play.ts +145 -0
  32. package/src/gateways/sseToStdio.ts +195 -0
  33. package/src/gateways/stdioToSse.ts +260 -0
  34. package/src/gateways/stdioToStatefulStreamableHttp.ts +274 -0
  35. package/src/gateways/stdioToStatelessStreamableHttp.ts +303 -0
  36. package/src/gateways/stdioToWs.ts +151 -0
  37. package/src/gateways/streamableHttpToStdio.ts +196 -0
  38. package/src/index.ts +286 -0
  39. package/src/lib/corsOrigin.ts +31 -0
  40. package/src/lib/getLogger.ts +83 -0
  41. package/src/lib/getVersion.ts +17 -0
  42. package/src/lib/headers.ts +55 -0
  43. package/src/lib/initMongoClient.ts +10 -0
  44. package/src/lib/mcpServerLogRepository.ts +48 -0
  45. package/src/lib/onSignals.ts +39 -0
  46. package/src/lib/serializeCorsOrigin.ts +14 -0
  47. package/src/lib/sessionAccessCounter.ts +118 -0
  48. package/src/server/websocket.ts +121 -0
  49. package/src/services/encryptionService.ts +309 -0
  50. package/src/types.ts +4 -0
  51. package/supergateway.png +0 -0
  52. package/tests/baseUrl.test.ts +62 -0
  53. package/tests/concurrency.test.ts +137 -0
  54. package/tests/helpers/mock-mcp-server.js +94 -0
  55. package/tests/protocolVersion.test.ts +60 -0
  56. package/tests/stdioToStatefulStreamableHttp.test.ts +70 -0
  57. package/tests/stdioToStatelessStreamableHttp.test.ts +71 -0
  58. package/tests/streamableHttpCli.test.ts +24 -0
  59. package/tests/streamableHttpToStdio.test.ts +64 -0
  60. package/tsconfig.build.json +8 -0
  61. package/tsconfig.json +12 -0
  62. package/tsconfig.test.json +10 -0
@@ -0,0 +1,151 @@
1
+ import express from 'express'
2
+ import cors, { type CorsOptions } from 'cors'
3
+ import { createServer } from 'http'
4
+ import { spawn, ChildProcessWithoutNullStreams } from 'child_process'
5
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js'
6
+ import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js'
7
+ import { Logger } from '../types.js'
8
+ import { getVersion } from '../lib/getVersion.js'
9
+ import { WebSocketServerTransport } from '../server/websocket.js'
10
+ import { onSignals } from '../lib/onSignals.js'
11
+ import { serializeCorsOrigin } from '../lib/serializeCorsOrigin.js'
12
+
13
+ export interface StdioToWsArgs {
14
+ stdioCmd: string
15
+ port: number
16
+ messagePath: string
17
+ logger: Logger
18
+ corsOrigin: CorsOptions['origin']
19
+ healthEndpoints: string[]
20
+ }
21
+
22
+ export async function stdioToWs(args: StdioToWsArgs) {
23
+ const { stdioCmd, port, messagePath, logger, healthEndpoints, corsOrigin } =
24
+ args
25
+ logger.info(` - port: ${port}`)
26
+ logger.info(` - stdio: ${stdioCmd}`)
27
+ logger.info(` - messagePath: ${messagePath}`)
28
+ logger.info(
29
+ ` - CORS: ${corsOrigin ? `enabled (${serializeCorsOrigin({ corsOrigin })})` : 'disabled'}`,
30
+ )
31
+ logger.info(
32
+ ` - Health endpoints: ${healthEndpoints.length ? healthEndpoints.join(', ') : '(none)'}`,
33
+ )
34
+
35
+ let wsTransport: WebSocketServerTransport | null = null
36
+ let child: ChildProcessWithoutNullStreams | null = null
37
+ let isReady = false
38
+
39
+ const cleanup = () => {
40
+ if (wsTransport) {
41
+ wsTransport.close().catch((err) => {
42
+ logger.error(`Error stopping WebSocket server: ${err.message}`)
43
+ })
44
+ }
45
+ if (child) {
46
+ child.kill()
47
+ }
48
+ }
49
+
50
+ onSignals({
51
+ logger,
52
+ cleanup,
53
+ })
54
+
55
+ try {
56
+ child = spawn(stdioCmd, { shell: true })
57
+ child.on('exit', (code, signal) => {
58
+ logger.error(`Child exited: code=${code}, signal=${signal}`)
59
+ cleanup()
60
+ process.exit(code ?? 1)
61
+ })
62
+
63
+ const server = new Server(
64
+ { name: 'supergateway', version: getVersion() },
65
+ { capabilities: {} },
66
+ )
67
+
68
+ // Handle child process output
69
+ let buffer = ''
70
+ child.stdout.on('data', (chunk: Buffer) => {
71
+ buffer += chunk.toString('utf8')
72
+ const lines = buffer.split(/\r?\n/)
73
+ buffer = lines.pop() ?? ''
74
+ lines.forEach((line) => {
75
+ if (!line.trim()) return
76
+ try {
77
+ const jsonMsg = JSON.parse(line)
78
+ logger.info(`Child → WebSocket: ${JSON.stringify(jsonMsg)}`)
79
+ // Broadcast to all connected clients
80
+ wsTransport?.send(jsonMsg, jsonMsg.id).catch((err) => {
81
+ logger.error('Failed to broadcast message:', err)
82
+ })
83
+ } catch {
84
+ logger.error(`Child non-JSON: ${line}`)
85
+ }
86
+ })
87
+ })
88
+
89
+ child.stderr.on('data', (chunk: Buffer) => {
90
+ logger.info(`Child stderr: ${chunk.toString('utf8')}`)
91
+ })
92
+
93
+ const app = express()
94
+
95
+ if (corsOrigin) {
96
+ app.use(cors({ origin: corsOrigin }))
97
+ }
98
+
99
+ for (const ep of healthEndpoints) {
100
+ app.get(ep, (_req, res) => {
101
+ if (child?.killed) {
102
+ res.status(500).send('Child process has been killed')
103
+ }
104
+
105
+ if (!isReady) {
106
+ res.status(500).send('Server is not ready')
107
+ }
108
+
109
+ res.send('ok')
110
+ })
111
+ }
112
+
113
+ const httpServer = createServer(app)
114
+
115
+ wsTransport = new WebSocketServerTransport({
116
+ path: messagePath,
117
+ server: httpServer,
118
+ })
119
+
120
+ await server.connect(wsTransport)
121
+
122
+ wsTransport.onmessage = (msg: JSONRPCMessage) => {
123
+ const line = JSON.stringify(msg)
124
+ logger.info(`WebSocket → Child: ${line}`)
125
+ child!.stdin.write(line + '\n')
126
+ }
127
+
128
+ wsTransport.onconnection = (clientId: string) => {
129
+ logger.info(`New WebSocket connection: ${clientId}`)
130
+ }
131
+
132
+ wsTransport.ondisconnection = (clientId: string) => {
133
+ logger.info(`WebSocket connection closed: ${clientId}`)
134
+ }
135
+
136
+ wsTransport.onerror = (err: Error) => {
137
+ logger.error(`WebSocket error: ${err.message}`)
138
+ }
139
+
140
+ isReady = true
141
+
142
+ httpServer.listen(port, () => {
143
+ logger.info(`Listening on port ${port}`)
144
+ logger.info(`WebSocket endpoint: ws://localhost:${port}${messagePath}`)
145
+ })
146
+ } catch (err: any) {
147
+ logger.error(`Failed to start: ${err.message}`)
148
+ cleanup()
149
+ process.exit(1)
150
+ }
151
+ }
@@ -0,0 +1,196 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js'
2
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
3
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js'
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
5
+ import type {
6
+ JSONRPCMessage,
7
+ JSONRPCRequest,
8
+ ClientCapabilities,
9
+ Implementation,
10
+ } from '@modelcontextprotocol/sdk/types.js'
11
+ import { InitializeRequestSchema } from '@modelcontextprotocol/sdk/types.js'
12
+ import { z } from 'zod'
13
+ import { getVersion } from '../lib/getVersion.js'
14
+ import { Logger } from '../types.js'
15
+ import { onSignals } from '../lib/onSignals.js'
16
+
17
+ export interface StreamableHttpToStdioArgs {
18
+ streamableHttpUrl: string
19
+ logger: Logger
20
+ headers: Record<string, string>
21
+ }
22
+
23
+ let mcpClient: Client | undefined
24
+
25
+ const newInitializeMcpClient = ({ message }: { message: JSONRPCRequest }) => {
26
+ const clientInfo = message.params?.clientInfo as Implementation | undefined
27
+ const clientCapabilities = message.params?.capabilities as
28
+ | ClientCapabilities
29
+ | undefined
30
+
31
+ return new Client(
32
+ {
33
+ name: clientInfo?.name ?? 'supergateway',
34
+ version: clientInfo?.version ?? getVersion(),
35
+ },
36
+ {
37
+ capabilities: clientCapabilities ?? {},
38
+ },
39
+ )
40
+ }
41
+
42
+ const newFallbackMcpClient = async ({
43
+ mcpTransport,
44
+ }: {
45
+ mcpTransport: StreamableHTTPClientTransport
46
+ }) => {
47
+ const fallbackMcpClient = new Client(
48
+ {
49
+ name: 'supergateway',
50
+ version: getVersion(),
51
+ },
52
+ {
53
+ capabilities: {},
54
+ },
55
+ )
56
+
57
+ await fallbackMcpClient.connect(mcpTransport)
58
+ return fallbackMcpClient
59
+ }
60
+
61
+ export async function streamableHttpToStdio(args: StreamableHttpToStdioArgs) {
62
+ const { streamableHttpUrl, logger, headers } = args
63
+
64
+ logger.info(` - streamableHttp: ${streamableHttpUrl}`)
65
+ logger.info(
66
+ ` - Headers: ${Object.keys(headers).length ? JSON.stringify(headers) : '(none)'}`,
67
+ )
68
+ logger.info('Connecting to Streamable HTTP...')
69
+
70
+ onSignals({ logger })
71
+
72
+ const mcpTransport = new StreamableHTTPClientTransport(
73
+ new URL(streamableHttpUrl),
74
+ {
75
+ requestInit: {
76
+ headers,
77
+ },
78
+ },
79
+ )
80
+
81
+ mcpTransport.onerror = (err) => {
82
+ logger.error('Streamable HTTP error:', err)
83
+ }
84
+
85
+ mcpTransport.onclose = () => {
86
+ logger.error('Streamable HTTP connection closed')
87
+ process.exit(1)
88
+ }
89
+
90
+ const stdioServer = new Server(
91
+ {
92
+ name: 'supergateway',
93
+ version: getVersion(),
94
+ },
95
+ {
96
+ capabilities: {},
97
+ },
98
+ )
99
+
100
+ const stdioTransport = new StdioServerTransport()
101
+ await stdioServer.connect(stdioTransport)
102
+
103
+ const wrapResponse = (req: JSONRPCRequest, payload: object) => ({
104
+ jsonrpc: req.jsonrpc || '2.0',
105
+ id: req.id,
106
+ ...payload,
107
+ })
108
+
109
+ stdioServer.transport!.onmessage = async (message: JSONRPCMessage) => {
110
+ const isRequest = 'method' in message && 'id' in message
111
+ if (isRequest) {
112
+ logger.info('Stdio → Streamable HTTP:', message)
113
+ const req = message as JSONRPCRequest
114
+ let result
115
+
116
+ try {
117
+ if (!mcpClient) {
118
+ if (message.method === 'initialize') {
119
+ mcpClient = newInitializeMcpClient({
120
+ message,
121
+ })
122
+
123
+ const originalRequest = mcpClient.request
124
+
125
+ mcpClient.request = async function (
126
+ possibleInitRequestMessage,
127
+ ...restArgs
128
+ ) {
129
+ if (
130
+ InitializeRequestSchema.safeParse(possibleInitRequestMessage)
131
+ .success &&
132
+ message.params?.protocolVersion
133
+ ) {
134
+ // respect the protocol version from the stdio client's init request
135
+ possibleInitRequestMessage.params!.protocolVersion =
136
+ message.params.protocolVersion
137
+ }
138
+ result = await originalRequest.apply(this, [
139
+ possibleInitRequestMessage,
140
+ ...restArgs,
141
+ ])
142
+ return result
143
+ }
144
+
145
+ await mcpClient.connect(mcpTransport)
146
+ mcpClient.request = originalRequest
147
+ } else {
148
+ logger.info(
149
+ 'Streamable HTTP client not initialized, creating fallback client',
150
+ )
151
+ mcpClient = await newFallbackMcpClient({ mcpTransport })
152
+ }
153
+
154
+ logger.info('Streamable HTTP connected')
155
+ } else {
156
+ result = await mcpClient.request(req, z.any())
157
+ }
158
+ } catch (err) {
159
+ logger.error('Request error:', err)
160
+ const errorCode =
161
+ err && typeof err === 'object' && 'code' in err
162
+ ? (err as any).code
163
+ : -32000
164
+ let errorMsg =
165
+ err && typeof err === 'object' && 'message' in err
166
+ ? (err as any).message
167
+ : 'Internal error'
168
+ const prefix = `MCP error ${errorCode}:`
169
+ if (errorMsg.startsWith(prefix)) {
170
+ errorMsg = errorMsg.slice(prefix.length).trim()
171
+ }
172
+ const errorResp = wrapResponse(req, {
173
+ error: {
174
+ code: errorCode,
175
+ message: errorMsg,
176
+ },
177
+ })
178
+ process.stdout.write(JSON.stringify(errorResp) + '\n')
179
+ return
180
+ }
181
+ const response = wrapResponse(
182
+ req,
183
+ result.hasOwnProperty('error')
184
+ ? { error: { ...result.error } }
185
+ : { result: { ...result } },
186
+ )
187
+ logger.info('Response:', response)
188
+ process.stdout.write(JSON.stringify(response) + '\n')
189
+ } else {
190
+ logger.info('Streamable HTTP → Stdio:', message)
191
+ process.stdout.write(JSON.stringify(message) + '\n')
192
+ }
193
+ }
194
+
195
+ logger.info('Stdio server listening')
196
+ }
package/src/index.ts ADDED
@@ -0,0 +1,286 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * index.ts
4
+ *
5
+ * Run MCP stdio servers over SSE, convert between stdio, SSE, WS.
6
+ *
7
+ * Usage:
8
+ * # stdio→SSE
9
+ * npx -y supergateway --stdio "npx -y @modelcontextprotocol/server-filesystem /" \
10
+ * --port 8000 --baseUrl http://localhost:8000 --ssePath /sse --messagePath /message
11
+ *
12
+ * # SSE→stdio
13
+ * npx -y supergateway --sse "https://mcp-server-ab71a6b2-cd55-49d0-adba-562bc85956e3.supermachine.app"
14
+ *
15
+ * # stdio→WS
16
+ * npx -y supergateway --stdio "npx -y @modelcontextprotocol/server-filesystem /" --outputTransport ws
17
+ *
18
+ * # Streamable HTTP→stdio
19
+ * npx -y supergateway --streamableHttp "https://mcp-server.example.com/mcp"
20
+ */
21
+
22
+ import yargs from 'yargs'
23
+ import { hideBin } from 'yargs/helpers'
24
+ import { stdioToSse } from './gateways/stdioToSse.js'
25
+ import { sseToStdio } from './gateways/sseToStdio.js'
26
+ import { stdioToWs } from './gateways/stdioToWs.js'
27
+ import { streamableHttpToStdio } from './gateways/streamableHttpToStdio.js'
28
+ import { headers } from './lib/headers.js'
29
+ import { corsOrigin } from './lib/corsOrigin.js'
30
+ import { getLogger } from './lib/getLogger.js'
31
+ import { stdioToStatelessStreamableHttp } from './gateways/stdioToStatelessStreamableHttp.js'
32
+ import { stdioToStatefulStreamableHttp } from './gateways/stdioToStatefulStreamableHttp.js'
33
+
34
+ async function main() {
35
+ const argv = yargs(hideBin(process.argv))
36
+ .option('stdio', {
37
+ type: 'string',
38
+ description: 'Command to run an MCP server over Stdio',
39
+ })
40
+ .option('sse', {
41
+ type: 'string',
42
+ description: 'SSE URL to connect to',
43
+ })
44
+ .option('streamableHttp', {
45
+ type: 'string',
46
+ description: 'Streamable HTTP URL to connect to',
47
+ })
48
+ .option('outputTransport', {
49
+ type: 'string',
50
+ choices: ['stdio', 'sse', 'ws', 'streamableHttp'],
51
+ default: () => {
52
+ const args = hideBin(process.argv)
53
+
54
+ if (args.includes('--stdio')) return 'sse'
55
+ if (args.includes('--sse')) return 'stdio'
56
+ if (args.includes('--streamableHttp')) return 'stdio'
57
+
58
+ return undefined
59
+ },
60
+ description:
61
+ 'Transport for output. Default is "sse" when using --stdio and "stdio" when using --sse or --streamableHttp.',
62
+ })
63
+ .option('port', {
64
+ type: 'number',
65
+ default: 8000,
66
+ description: '(stdio→SSE, stdio→WS) Port for output MCP server',
67
+ })
68
+ .option('baseUrl', {
69
+ type: 'string',
70
+ default: '',
71
+ description: '(stdio→SSE) Base URL for output MCP server',
72
+ })
73
+ .option('ssePath', {
74
+ type: 'string',
75
+ default: '/sse',
76
+ description: '(stdio→SSE) Path for SSE subscriptions',
77
+ })
78
+ .option('messagePath', {
79
+ type: 'string',
80
+ default: '/message',
81
+ description: '(stdio→SSE, stdio→WS) Path for messages',
82
+ })
83
+ .option('streamableHttpPath', {
84
+ type: 'string',
85
+ default: '/mcp',
86
+ description: '(stdio→StreamableHttp) Path for StreamableHttp',
87
+ })
88
+ .option('logLevel', {
89
+ choices: ['debug', 'info', 'none'] as const,
90
+ default: 'info',
91
+ description: 'Logging level',
92
+ })
93
+ .option('cors', {
94
+ type: 'array',
95
+ description:
96
+ 'Enable CORS. Use --cors with no values to allow all origins, or supply one or more allowed origins (e.g. --cors "http://example.com" or --cors "/example\\.com$/" for regex matching).',
97
+ })
98
+ .option('healthEndpoint', {
99
+ type: 'array',
100
+ default: [],
101
+ description:
102
+ 'One or more endpoints returning "ok", e.g. --healthEndpoint /healthz --healthEndpoint /readyz',
103
+ })
104
+ .option('header', {
105
+ type: 'array',
106
+ default: [],
107
+ description:
108
+ 'Headers to be added to the request headers, e.g. --header "x-user-id: 123"',
109
+ })
110
+ .option('oauth2Bearer', {
111
+ type: 'string',
112
+ description:
113
+ 'Authorization header to be added, e.g. --oauth2Bearer "some-access-token" adds "Authorization: Bearer some-access-token"',
114
+ })
115
+ .option('stateful', {
116
+ type: 'boolean',
117
+ default: false,
118
+ description:
119
+ 'Whether the server is stateful. Only supported for stdio→StreamableHttp.',
120
+ })
121
+ .option('sessionTimeout', {
122
+ type: 'number',
123
+ description:
124
+ 'Session timeout in milliseconds. Only supported for stateful stdio→StreamableHttp. If not set, the session will only be deleted when client transport explicitly terminates the session.',
125
+ })
126
+ .option('protocolVersion', {
127
+ type: 'string',
128
+ description:
129
+ 'MCP protocol version to use for auto-initialization. Defaults to "2024-11-05" if not specified.',
130
+ default: '2024-11-05',
131
+ })
132
+ .help()
133
+ .parseSync()
134
+
135
+ const hasStdio = Boolean(argv.stdio)
136
+ const hasSse = Boolean(argv.sse)
137
+ const hasStreamableHttp = Boolean(argv.streamableHttp)
138
+
139
+ const activeCount = [hasStdio, hasSse, hasStreamableHttp].filter(
140
+ Boolean,
141
+ ).length
142
+
143
+ const logger = getLogger({
144
+ logLevel: argv.logLevel,
145
+ outputTransport: argv.outputTransport as string,
146
+ })
147
+
148
+ if (activeCount === 0) {
149
+ logger.error(
150
+ 'Error: You must specify one of --stdio, --sse, or --streamableHttp',
151
+ )
152
+ process.exit(1)
153
+ } else if (activeCount > 1) {
154
+ logger.error(
155
+ 'Error: Specify only one of --stdio, --sse, or --streamableHttp, not multiple',
156
+ )
157
+ process.exit(1)
158
+ }
159
+
160
+ logger.info('Starting...')
161
+ logger.info(
162
+ 'Supergateway is supported by Supermachine (hosted MCPs) - https://supermachine.ai',
163
+ )
164
+ logger.info(` - outputTransport: ${argv.outputTransport}`)
165
+
166
+ try {
167
+ if (hasStdio) {
168
+ if (argv.outputTransport === 'sse') {
169
+ await stdioToSse({
170
+ stdioCmd: argv.stdio!,
171
+ port: argv.port,
172
+ baseUrl: argv.baseUrl,
173
+ ssePath: argv.ssePath,
174
+ messagePath: argv.messagePath,
175
+ logger,
176
+ corsOrigin: corsOrigin({ argv }),
177
+ healthEndpoints: argv.healthEndpoint as string[],
178
+ headers: headers({
179
+ argv,
180
+ logger,
181
+ }),
182
+ })
183
+ } else if (argv.outputTransport === 'ws') {
184
+ await stdioToWs({
185
+ stdioCmd: argv.stdio!,
186
+ port: argv.port,
187
+ messagePath: argv.messagePath,
188
+ logger,
189
+ corsOrigin: corsOrigin({ argv }),
190
+ healthEndpoints: argv.healthEndpoint as string[],
191
+ })
192
+ } else if (argv.outputTransport === 'streamableHttp') {
193
+ const stateful = argv.stateful
194
+ if (stateful) {
195
+ logger.info('Running stateful server')
196
+
197
+ let sessionTimeout: null | number
198
+ if (typeof argv.sessionTimeout === 'number') {
199
+ if (argv.sessionTimeout <= 0) {
200
+ logger.error(
201
+ `Error: \`sessionTimeout\` must be a positive number, received: ${argv.sessionTimeout}`,
202
+ )
203
+ process.exit(1)
204
+ }
205
+
206
+ sessionTimeout = argv.sessionTimeout
207
+ } else {
208
+ sessionTimeout = null
209
+ }
210
+
211
+ await stdioToStatefulStreamableHttp({
212
+ stdioCmd: argv.stdio!,
213
+ port: argv.port,
214
+ streamableHttpPath: argv.streamableHttpPath,
215
+ logger,
216
+ corsOrigin: corsOrigin({ argv }),
217
+ healthEndpoints: argv.healthEndpoint as string[],
218
+ headers: headers({
219
+ argv,
220
+ logger,
221
+ }),
222
+ sessionTimeout,
223
+ })
224
+ } else {
225
+ logger.info('Running stateless server')
226
+
227
+ await stdioToStatelessStreamableHttp({
228
+ stdioCmd: argv.stdio!,
229
+ port: argv.port,
230
+ streamableHttpPath: argv.streamableHttpPath,
231
+ logger,
232
+ corsOrigin: corsOrigin({ argv }),
233
+ healthEndpoints: argv.healthEndpoint as string[],
234
+ headers: headers({
235
+ argv,
236
+ logger,
237
+ }),
238
+ protocolVersion: argv.protocolVersion,
239
+ })
240
+ }
241
+ } else {
242
+ logger.error(`Error: stdio→${argv.outputTransport} not supported`)
243
+ process.exit(1)
244
+ }
245
+ } else if (hasSse) {
246
+ if (argv.outputTransport === 'stdio') {
247
+ await sseToStdio({
248
+ sseUrl: argv.sse!,
249
+ logger,
250
+ headers: headers({
251
+ argv,
252
+ logger,
253
+ }),
254
+ })
255
+ } else {
256
+ logger.error(`Error: sse→${argv.outputTransport} not supported`)
257
+ process.exit(1)
258
+ }
259
+ } else if (hasStreamableHttp) {
260
+ if (argv.outputTransport === 'stdio') {
261
+ await streamableHttpToStdio({
262
+ streamableHttpUrl: argv.streamableHttp!,
263
+ logger,
264
+ headers: headers({
265
+ argv,
266
+ logger,
267
+ }),
268
+ })
269
+ } else {
270
+ logger.error(
271
+ `Error: streamableHttp→${argv.outputTransport} not supported`,
272
+ )
273
+ process.exit(1)
274
+ }
275
+ } else {
276
+ logger.error('Error: Invalid input transport')
277
+ process.exit(1)
278
+ }
279
+ } catch (err) {
280
+ logger.error('Fatal error:', err)
281
+ process.exit(1)
282
+ }
283
+ }
284
+
285
+ main()
286
+ // test commit
@@ -0,0 +1,31 @@
1
+ export const corsOrigin = ({
2
+ argv,
3
+ }: {
4
+ argv: {
5
+ cors: (string | number)[] | undefined
6
+ }
7
+ }) => {
8
+ if (!argv.cors) {
9
+ return false
10
+ }
11
+
12
+ if (argv.cors.length === 0) {
13
+ return '*'
14
+ }
15
+
16
+ const origins = argv.cors.map((item) => `${item}`)
17
+
18
+ if (origins.includes('*')) return '*'
19
+
20
+ return origins.map((origin) => {
21
+ if (/^\/.*\/$/.test(origin)) {
22
+ const pattern = origin.slice(1, -1)
23
+ try {
24
+ return new RegExp(pattern)
25
+ } catch (error) {
26
+ return origin
27
+ }
28
+ }
29
+ return origin
30
+ })
31
+ }