cprime-supergateway 3.4.5 → 3.4.7

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.
@@ -10,7 +10,11 @@ import { serializeCorsOrigin } from '../lib/serializeCorsOrigin.js';
10
10
  import { EncryptionService } from '../services/encryptionService.js';
11
11
  import { initMongoClient } from '../lib/initMongoClient.js';
12
12
  import { McpServerLogRepository } from '../lib/mcpServerLogRepository.js';
13
+ import { LogService } from '../services/logService.js';
13
14
  const encryptionService = new EncryptionService('env');
15
+ const mongoClient = initMongoClient();
16
+ const mcpServerLogRepository = new McpServerLogRepository(mongoClient, process.env.MONGO_DB ?? 'localhost', process.env.LOGS_COLLECTION ?? 'mcp_server_logs');
17
+ const logService = new LogService(mcpServerLogRepository);
14
18
  let decryptedEnvs = {};
15
19
  if (process.env.ENCRYPTED_ENV) {
16
20
  const plaintext = await encryptionService.decryptText(process.env.ENCRYPTED_ENV, process.env.AAD_JSON ? JSON.parse(process.env.AAD_JSON) : {});
@@ -38,8 +42,6 @@ export async function stdioToSse(args) {
38
42
  logger.info(` - CORS: ${corsOrigin ? `enabled (${serializeCorsOrigin({ corsOrigin })})` : 'disabled'}`);
39
43
  logger.info(` - Health endpoints: ${healthEndpoints.length ? healthEndpoints.join(', ') : '(none)'}`);
40
44
  onSignals({ logger });
41
- const mongoClient = initMongoClient();
42
- const mcpServerLogRepository = new McpServerLogRepository(mongoClient, process.env.MONGO_DB ?? 'localhost', process.env.LOGS_COLLECTION ?? 'mcp_server_logs');
43
45
  const child = spawn(stdioCmd, {
44
46
  shell: true,
45
47
  env: { ...process.env, ...decryptedEnvs },
@@ -48,7 +50,6 @@ export async function stdioToSse(args) {
48
50
  logger.error(`Child exited: code=${code}, signal=${signal}`);
49
51
  process.exit(code ?? 1);
50
52
  });
51
- const server = new Server({ name: 'supergateway', version: getVersion() }, { capabilities: {} });
52
53
  const sessions = {};
53
54
  const app = express();
54
55
  if (corsOrigin) {
@@ -74,6 +75,7 @@ export async function stdioToSse(args) {
74
75
  res,
75
76
  headers,
76
77
  });
78
+ const server = new Server({ name: 'supergateway', version: getVersion() }, { capabilities: {} });
77
79
  const sseTransport = new SSEServerTransport(`${baseUrl}${messagePath}`, res);
78
80
  await server.connect(sseTransport);
79
81
  const sessionId = sseTransport.sessionId;
@@ -87,18 +89,9 @@ export async function stdioToSse(args) {
87
89
  }
88
90
  sseTransport.onmessage = (msg) => {
89
91
  logger.info(`SSE → Child (session ${sessionId}): ${JSON.stringify(msg)}`);
90
- mcpServerLogRepository
91
- .insert({
92
- ip: req.ip ?? '',
93
- userId: req.query.userId ?? '',
92
+ logService.log(msg, 'request-rpc', logger, {
93
+ ...sessions[sessionId],
94
94
  sessionId,
95
- type: 'rpc',
96
- data: msg,
97
- createdAt: new Date(),
98
- updatedAt: new Date(),
99
- })
100
- .catch((err) => {
101
- logger.error(`Failed to insert log:`, JSON.stringify(err));
102
95
  });
103
96
  child.stdin.write(JSON.stringify(msg) + '\n');
104
97
  };
@@ -153,18 +146,9 @@ export async function stdioToSse(args) {
153
146
  for (const [sid, session] of Object.entries(sessions)) {
154
147
  try {
155
148
  session.transport.send(jsonMsg);
156
- mcpServerLogRepository
157
- .insert({
158
- ip: session.ip,
159
- userId: session.userId,
149
+ logService.log(jsonMsg, 'response-rpc', logger, {
150
+ ...session,
160
151
  sessionId: sid,
161
- type: 'system',
162
- data: line,
163
- createdAt: new Date(),
164
- updatedAt: new Date(),
165
- })
166
- .catch((err) => {
167
- logger.error(`Failed to insert log:`, JSON.stringify(err));
168
152
  });
169
153
  }
170
154
  catch (err) {
@@ -175,10 +159,22 @@ export async function stdioToSse(args) {
175
159
  }
176
160
  catch {
177
161
  logger.error(`Child non-JSON: ${line}`);
162
+ for (const [sid, session] of Object.entries(sessions)) {
163
+ logService.log(line.toString(), 'system', logger, {
164
+ ...session,
165
+ sessionId: sid,
166
+ });
167
+ }
178
168
  }
179
169
  });
180
170
  });
181
171
  child.stderr.on('data', (chunk) => {
182
172
  logger.error(`Child stderr: ${chunk.toString('utf8')}`);
173
+ for (const [sid, session] of Object.entries(sessions)) {
174
+ logService.log(chunk.toString('utf8'), 'system', logger, {
175
+ ...session,
176
+ sessionId: sid,
177
+ });
178
+ }
183
179
  });
184
180
  }
@@ -0,0 +1,24 @@
1
+ export class LogService {
2
+ repository;
3
+ constructor(repository) {
4
+ this.repository = repository;
5
+ }
6
+ async log(data, type, logger, session) {
7
+ const logDto = {
8
+ ip: session.ip,
9
+ userId: session.userId,
10
+ sessionId: session.sessionId,
11
+ type: type,
12
+ data: data,
13
+ createdAt: new Date(),
14
+ updatedAt: new Date(),
15
+ };
16
+ // Insert to MongoDB with error handling
17
+ try {
18
+ await this.repository.insert(logDto);
19
+ }
20
+ catch (err) {
21
+ logger.error(`Failed to insert log:`, JSON.stringify(err));
22
+ }
23
+ }
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cprime-supergateway",
3
- "version": "3.4.5",
3
+ "version": "3.4.7",
4
4
  "description": "Run MCP stdio servers over SSE, Streamable HTTP or visa versa",
5
5
  "repository": {
6
6
  "type": "git",
@@ -12,6 +12,7 @@ import { serializeCorsOrigin } from '../lib/serializeCorsOrigin.js'
12
12
  import { EncryptionService } from '../services/encryptionService.js'
13
13
  import { initMongoClient } from '../lib/initMongoClient.js'
14
14
  import { McpServerLogRepository } from '../lib/mcpServerLogRepository.js'
15
+ import { ILogService, LogService } from '../services/logService.js'
15
16
 
16
17
  export interface StdioToSseArgs {
17
18
  stdioCmd: string
@@ -26,6 +27,13 @@ export interface StdioToSseArgs {
26
27
  }
27
28
 
28
29
  const encryptionService = new EncryptionService('env')
30
+ const mongoClient = initMongoClient()
31
+ const mcpServerLogRepository = new McpServerLogRepository(
32
+ mongoClient,
33
+ process.env.MONGO_DB ?? 'localhost',
34
+ process.env.LOGS_COLLECTION ?? 'mcp_server_logs',
35
+ )
36
+ const logService: ILogService = new LogService(mcpServerLogRepository)
29
37
  let decryptedEnvs = {}
30
38
  if (process.env.ENCRYPTED_ENV) {
31
39
  const plaintext = await encryptionService.decryptText(
@@ -84,13 +92,6 @@ export async function stdioToSse(args: StdioToSseArgs) {
84
92
 
85
93
  onSignals({ logger })
86
94
 
87
- const mongoClient = initMongoClient()
88
- const mcpServerLogRepository = new McpServerLogRepository(
89
- mongoClient,
90
- process.env.MONGO_DB ?? 'localhost',
91
- process.env.LOGS_COLLECTION ?? 'mcp_server_logs',
92
- )
93
-
94
95
  const child: ChildProcessWithoutNullStreams = spawn(stdioCmd, {
95
96
  shell: true,
96
97
  env: { ...process.env, ...decryptedEnvs },
@@ -100,11 +101,6 @@ export async function stdioToSse(args: StdioToSseArgs) {
100
101
  process.exit(code ?? 1)
101
102
  })
102
103
 
103
- const server = new Server(
104
- { name: 'supergateway', version: getVersion() },
105
- { capabilities: {} },
106
- )
107
-
108
104
  const sessions: Record<
109
105
  string,
110
106
  {
@@ -144,6 +140,10 @@ export async function stdioToSse(args: StdioToSseArgs) {
144
140
  headers,
145
141
  })
146
142
 
143
+ const server = new Server(
144
+ { name: 'supergateway', version: getVersion() },
145
+ { capabilities: {} },
146
+ )
147
147
  const sseTransport = new SSEServerTransport(`${baseUrl}${messagePath}`, res)
148
148
  await server.connect(sseTransport)
149
149
 
@@ -159,19 +159,10 @@ export async function stdioToSse(args: StdioToSseArgs) {
159
159
 
160
160
  sseTransport.onmessage = (msg: JSONRPCMessage) => {
161
161
  logger.info(`SSE → Child (session ${sessionId}): ${JSON.stringify(msg)}`)
162
- mcpServerLogRepository
163
- .insert({
164
- ip: req.ip ?? '',
165
- userId: (req.query.userId as string) ?? '',
166
- sessionId,
167
- type: 'rpc',
168
- data: msg,
169
- createdAt: new Date(),
170
- updatedAt: new Date(),
171
- })
172
- .catch((err) => {
173
- logger.error(`Failed to insert log:`, JSON.stringify(err))
174
- })
162
+ logService.log(msg, 'request-rpc', logger, {
163
+ ...sessions[sessionId],
164
+ sessionId,
165
+ })
175
166
  child.stdin.write(JSON.stringify(msg) + '\n')
176
167
  }
177
168
 
@@ -232,19 +223,10 @@ export async function stdioToSse(args: StdioToSseArgs) {
232
223
  for (const [sid, session] of Object.entries(sessions)) {
233
224
  try {
234
225
  session.transport.send(jsonMsg)
235
- mcpServerLogRepository
236
- .insert({
237
- ip: session.ip,
238
- userId: session.userId,
239
- sessionId: sid,
240
- type: 'system',
241
- data: line,
242
- createdAt: new Date(),
243
- updatedAt: new Date(),
244
- })
245
- .catch((err) => {
246
- logger.error(`Failed to insert log:`, JSON.stringify(err))
247
- })
226
+ logService.log(jsonMsg, 'response-rpc', logger, {
227
+ ...session,
228
+ sessionId: sid,
229
+ })
248
230
  } catch (err) {
249
231
  logger.error(`Failed to send to session ${sid}:`, err)
250
232
  delete sessions[sid]
@@ -252,11 +234,23 @@ export async function stdioToSse(args: StdioToSseArgs) {
252
234
  }
253
235
  } catch {
254
236
  logger.error(`Child non-JSON: ${line}`)
237
+ for (const [sid, session] of Object.entries(sessions)) {
238
+ logService.log(line.toString(), 'system', logger, {
239
+ ...session,
240
+ sessionId: sid,
241
+ })
242
+ }
255
243
  }
256
244
  })
257
245
  })
258
246
 
259
247
  child.stderr.on('data', (chunk: Buffer) => {
260
248
  logger.error(`Child stderr: ${chunk.toString('utf8')}`)
249
+ for (const [sid, session] of Object.entries(sessions)) {
250
+ logService.log(chunk.toString('utf8'), 'system', logger, {
251
+ ...session,
252
+ sessionId: sid,
253
+ })
254
+ }
261
255
  })
262
256
  }
@@ -39,7 +39,7 @@ export class McpServerLogRepository {
39
39
 
40
40
  export type McpServerLogDto = {
41
41
  ip: string
42
- type: 'rpc' | 'error' | 'system'
42
+ type: 'request-rpc' | 'response-rpc' | 'system'
43
43
  userId: string
44
44
  sessionId: string
45
45
  data: JSONRPCMessage | string
@@ -0,0 +1,43 @@
1
+ import { Logger } from '../types.js'
2
+ import {
3
+ McpServerLogRepository,
4
+ type McpServerLogDto,
5
+ } from '../lib/mcpServerLogRepository.js'
6
+ import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js'
7
+
8
+ export interface ILogService {
9
+ log(
10
+ data: string | object,
11
+ type: string,
12
+ logger: Logger,
13
+ session: { ip: string; userId: string; sessionId: string },
14
+ ): Promise<void>
15
+ }
16
+
17
+ export class LogService implements ILogService {
18
+ constructor(private readonly repository: McpServerLogRepository) {}
19
+
20
+ async log(
21
+ data: string | object,
22
+ type: string,
23
+ logger: Logger,
24
+ session: { ip: string; userId: string; sessionId: string },
25
+ ): Promise<void> {
26
+ const logDto: McpServerLogDto = {
27
+ ip: session.ip,
28
+ userId: session.userId,
29
+ sessionId: session.sessionId,
30
+ type: type as McpServerLogDto['type'],
31
+ data: data as JSONRPCMessage | string,
32
+ createdAt: new Date(),
33
+ updatedAt: new Date(),
34
+ }
35
+
36
+ // Insert to MongoDB with error handling
37
+ try {
38
+ await this.repository.insert(logDto)
39
+ } catch (err) {
40
+ logger.error(`Failed to insert log:`, JSON.stringify(err))
41
+ }
42
+ }
43
+ }