Skip to content

Commit 6acf4df

Browse files
authored
McpGateway extensive logging (microsoft#299043)
* McpGateway extensive logging * update * update
1 parent c5d8f77 commit 6acf4df

10 files changed

Lines changed: 139 additions & 28 deletions

File tree

src/vs/code/electron-main/app.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1288,7 +1288,7 @@ export class CodeApplication extends Disposable {
12881288
// MCP
12891289
const mcpDiscoveryChannel = ProxyChannel.fromService(accessor.get(INativeMcpDiscoveryHelperService), disposables);
12901290
mainProcessElectronServer.registerChannel(NativeMcpDiscoveryHelperChannelName, mcpDiscoveryChannel);
1291-
const mcpGatewayChannel = this._register(new McpGatewayChannel(mainProcessElectronServer, accessor.get(IMcpGatewayService)));
1291+
const mcpGatewayChannel = this._register(new McpGatewayChannel(mainProcessElectronServer, accessor.get(IMcpGatewayService), accessor.get(ILoggerMainService)));
12921292
mainProcessElectronServer.registerChannel(McpGatewayChannelName, mcpGatewayChannel);
12931293

12941294
// Logger

src/vs/platform/mcp/node/mcpGatewayChannel.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { Event } from '../../../base/common/event.js';
77
import { Disposable } from '../../../base/common/lifecycle.js';
88
import { IPCServer, IServerChannel } from '../../../base/parts/ipc/common/ipc.js';
9+
import { ILoggerService } from '../../log/common/log.js';
910
import { IGatewayCallToolResult, IGatewayServerResources, IGatewayServerResourceTemplates, IMcpGatewayService, McpGatewayToolBrokerChannelName } from '../common/mcpGateway.js';
1011
import { MCP } from '../common/modelContextProtocol.js';
1112

@@ -19,17 +20,24 @@ export class McpGatewayChannel<TContext> extends Disposable implements IServerCh
1920

2021
constructor(
2122
private readonly _ipcServer: IPCServer<TContext>,
22-
@IMcpGatewayService private readonly mcpGatewayService: IMcpGatewayService
23+
@IMcpGatewayService private readonly mcpGatewayService: IMcpGatewayService,
24+
@ILoggerService private readonly _loggerService: ILoggerService,
2325
) {
2426
super();
25-
this._register(_ipcServer.onDidRemoveConnection(c => mcpGatewayService.disposeGatewaysForClient(c.ctx)));
27+
this._register(_ipcServer.onDidRemoveConnection(c => {
28+
this._loggerService.getLogger('mcpGateway')?.info(`[McpGateway][Channel] Client disconnected: ${c.ctx}, cleaning up gateways`);
29+
mcpGatewayService.disposeGatewaysForClient(c.ctx);
30+
}));
2631
}
2732

2833
listen<T>(_ctx: TContext, _event: string): Event<T> {
2934
throw new Error('Invalid listen');
3035
}
3136

3237
async call<T>(ctx: TContext, command: string, args?: unknown): Promise<T> {
38+
const logger = this._loggerService.getLogger('mcpGateway');
39+
logger?.debug(`[McpGateway][Channel] IPC call: ${command} from client ${ctx}`);
40+
3341
switch (command) {
3442
case 'createGateway': {
3543
const brokerChannel = ipcChannelForContext(this._ipcServer, ctx);
@@ -42,9 +50,11 @@ export class McpGatewayChannel<TContext> extends Disposable implements IServerCh
4250
readResource: (serverIndex, uri) => brokerChannel.call<MCP.ReadResourceResult>('readResource', { serverIndex, uri }),
4351
listResourceTemplates: () => brokerChannel.call<readonly IGatewayServerResourceTemplates[]>('listResourceTemplates'),
4452
});
53+
logger?.info(`[McpGateway][Channel] Gateway created: ${result.gatewayId} for client ${ctx}`);
4554
return result as T;
4655
}
4756
case 'disposeGateway': {
57+
logger?.info(`[McpGateway][Channel] Disposing gateway: ${args as string} for client ${ctx}`);
4858
await this.mcpGatewayService.disposeGateway(args as string);
4959
return undefined as T;
5060
}

src/vs/platform/mcp/node/mcpGatewayService.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export class McpGatewayService extends Disposable implements IMcpGatewayService
5656

5757
const gateway = new McpGatewayRoute(gatewayId, this._logger, toolInvoker);
5858
this._gateways.set(gatewayId, gateway);
59+
this._logger.info(`[McpGatewayService] Active gateways: ${this._gateways.size}`);
5960

6061
// Track client ownership if clientId provided (for cleanup on disconnect)
6162
if (clientId) {
@@ -83,7 +84,7 @@ export class McpGatewayService extends Disposable implements IMcpGatewayService
8384
gateway.dispose();
8485
this._gateways.delete(gatewayId);
8586
this._gatewayToClient.delete(gatewayId);
86-
this._logger.info(`[McpGatewayService] Disposed gateway: ${gatewayId}`);
87+
this._logger.info(`[McpGatewayService] Disposed gateway: ${gatewayId} (remaining: ${this._gateways.size})`);
8788

8889
// If no more gateways, shut down the server
8990
if (this._gateways.size === 0) {
@@ -101,7 +102,7 @@ export class McpGatewayService extends Disposable implements IMcpGatewayService
101102
}
102103

103104
if (gatewaysToDispose.length > 0) {
104-
this._logger.info(`[McpGatewayService] Disposing ${gatewaysToDispose.length} gateway(s) for disconnected client ${clientId}`);
105+
this._logger.info(`[McpGatewayService] Disposing ${gatewaysToDispose.length} gateway(s) for disconnected client ${clientId}: [${gatewaysToDispose.join(', ')}]`);
105106

106107
for (const gatewayId of gatewaysToDispose) {
107108
this._gateways.get(gatewayId)?.dispose();
@@ -204,6 +205,8 @@ export class McpGatewayService extends Disposable implements IMcpGatewayService
204205
const url = new URL(req.url!, `http://${req.headers.host}`);
205206
const pathParts = url.pathname.split('/').filter(Boolean);
206207

208+
this._logger.debug(`[McpGatewayService] ${req.method} ${url.pathname} (active gateways: ${this._gateways.size})`);
209+
207210
// Expected path: /gateway/{gatewayId}
208211
if (pathParts.length >= 2 && pathParts[0] === 'gateway') {
209212
const gatewayId = pathParts[1];
@@ -216,11 +219,13 @@ export class McpGatewayService extends Disposable implements IMcpGatewayService
216219
}
217220

218221
// Not found
222+
this._logger.warn(`[McpGatewayService] ${req.method} ${url.pathname}: gateway not found`);
219223
res.writeHead(404, { 'Content-Type': 'application/json' });
220224
res.end(JSON.stringify({ error: 'Gateway not found' }));
221225
}
222226

223227
override dispose(): void {
228+
this._logger.info(`[McpGatewayService] Disposing service (gateways: ${this._gateways.size})`);
224229
this._stopServer();
225230
for (const gateway of this._gateways.values()) {
226231
gateway.dispose();
@@ -247,6 +252,8 @@ class McpGatewayRoute extends Disposable {
247252
}
248253

249254
handleRequest(req: http.IncomingMessage, res: http.ServerResponse): void {
255+
this._logger.debug(`[McpGateway][route ${this.gatewayId}] ${req.method} request (sessions: ${this._sessions.size})`);
256+
250257
if (req.method === 'POST') {
251258
void this._handlePost(req, res);
252259
return;
@@ -266,6 +273,7 @@ class McpGatewayRoute extends Disposable {
266273
}
267274

268275
public override dispose(): void {
276+
this._logger.info(`[McpGateway][route ${this.gatewayId}] Disposing route (sessions: ${this._sessions.size})`);
269277
for (const session of this._sessions.values()) {
270278
session.dispose();
271279
}
@@ -286,6 +294,7 @@ class McpGatewayRoute extends Disposable {
286294
return;
287295
}
288296

297+
this._logger.info(`[McpGateway][route ${this.gatewayId}] Deleting session ${sessionId}`);
289298
session.dispose();
290299
this._sessions.delete(sessionId);
291300
res.writeHead(204);
@@ -305,6 +314,7 @@ class McpGatewayRoute extends Disposable {
305314
return;
306315
}
307316

317+
this._logger.info(`[McpGateway][route ${this.gatewayId}] SSE connection requested for session ${sessionId}`);
308318
session.attachSseClient(req, res);
309319
}
310320

@@ -315,10 +325,13 @@ class McpGatewayRoute extends Disposable {
315325
return;
316326
}
317327

328+
this._logger.debug(`[McpGateway][route ${this.gatewayId}] Handling POST`);
329+
318330
let message: JsonRpcMessage | JsonRpcMessage[];
319331
try {
320332
message = JSON.parse(body) as JsonRpcMessage | JsonRpcMessage[];
321333
} catch (error) {
334+
this._logger.warn(`[McpGateway][route ${this.gatewayId}] JSON parse error: ${error instanceof Error ? error.message : String(error)}`);
322335
res.writeHead(400, { 'Content-Type': 'application/json' });
323336
res.end(JSON.stringify(JsonRpcProtocol.createParseError('Parse error', error instanceof Error ? error.message : String(error))));
324337
return;
@@ -339,13 +352,16 @@ class McpGatewayRoute extends Disposable {
339352
};
340353

341354
if (responses.length === 0) {
355+
this._logger.debug(`[McpGateway][route ${this.gatewayId}] POST response: 202 (no content)`);
342356
res.writeHead(202, headers);
343357
res.end();
344358
return;
345359
}
346360

361+
const responseBody = JSON.stringify(Array.isArray(message) ? responses : responses[0]);
362+
this._logger.debug(`[McpGateway][route ${this.gatewayId}] POST response: 200, body: ${responseBody}`);
347363
res.writeHead(200, headers);
348-
res.end(JSON.stringify(Array.isArray(message) ? responses : responses[0]));
364+
res.end(responseBody);
349365
} catch (error) {
350366
this._logger.error('[McpGatewayService] Failed handling gateway request', error);
351367
this._respondHttpError(res, 500, 'Internal server error');
@@ -356,6 +372,7 @@ class McpGatewayRoute extends Disposable {
356372
if (headerSessionId) {
357373
const existing = this._sessions.get(headerSessionId);
358374
if (!existing) {
375+
this._logger.warn(`[McpGateway][route ${this.gatewayId}] Session not found: ${headerSessionId}`);
359376
this._respondHttpError(res, 404, 'Session not found');
360377
return undefined;
361378
}
@@ -369,6 +386,7 @@ class McpGatewayRoute extends Disposable {
369386
}
370387

371388
const sessionId = generateUuid();
389+
this._logger.info(`[McpGateway][route ${this.gatewayId}] Creating new session ${sessionId}`);
372390
const session = new McpGatewaySession(sessionId, this._logger, () => {
373391
this._sessions.delete(sessionId);
374392
}, this._toolInvoker);
@@ -377,6 +395,7 @@ class McpGatewayRoute extends Disposable {
377395
}
378396

379397
private _respondHttpError(res: http.ServerResponse, statusCode: number, error: string): void {
398+
this._logger.debug(`[McpGateway][route ${this.gatewayId}] HTTP error response: ${statusCode} ${error}`);
380399
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
381400
res.end(JSON.stringify({ jsonrpc: '2.0', error: { code: statusCode, message: error } } satisfies JsonRpcMessage));
382401
}

src/vs/platform/mcp/node/mcpGatewaySession.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ export class McpGatewaySession extends Disposable {
105105
return;
106106
}
107107

108+
this._logService.info(`[McpGateway][session ${this.id}] Tools changed, notifying client`);
108109
this._rpc.sendNotification({ method: 'notifications/tools/list_changed' });
109110
}));
110111

@@ -113,6 +114,7 @@ export class McpGatewaySession extends Disposable {
113114
return;
114115
}
115116

117+
this._logService.info(`[McpGateway][session ${this.id}] Resources changed, notifying client`);
116118
this._rpc.sendNotification({ method: 'notifications/resources/list_changed' });
117119
}));
118120
}
@@ -126,9 +128,11 @@ export class McpGatewaySession extends Disposable {
126128

127129
res.write(': connected\n\n');
128130
this._sseClients.add(res);
131+
this._logService.info(`[McpGateway][session ${this.id}] SSE client attached (total: ${this._sseClients.size})`);
129132

130133
res.on('close', () => {
131134
this._sseClients.delete(res);
135+
this._logService.info(`[McpGateway][session ${this.id}] SSE client detached (total: ${this._sseClients.size})`);
132136
});
133137
}
134138

@@ -145,6 +149,7 @@ export class McpGatewaySession extends Disposable {
145149
}
146150

147151
public override dispose(): void {
152+
this._logService.info(`[McpGateway][session ${this.id}] Disposing session (SSE clients: ${this._sseClients.size})`);
148153
for (const client of this._sseClients) {
149154
if (!client.destroyed) {
150155
client.end();
@@ -160,10 +165,12 @@ export class McpGatewaySession extends Disposable {
160165
if (this._isCollectingPostResponses) {
161166
this._pendingResponses.push(message);
162167
}
168+
this._logService.debug(`[McpGateway][session ${this.id}] --> response: ${JSON.stringify(message)}`);
163169
return;
164170
}
165171

166172
if (isJsonRpcNotification(message)) {
173+
this._logService.debug(`[McpGateway][session ${this.id}] --> notification: ${(message as IJsonRpcNotification).method}`);
167174
this._broadcastSse(message);
168175
return;
169176
}
@@ -173,11 +180,13 @@ export class McpGatewaySession extends Disposable {
173180

174181
private _broadcastSse(message: JsonRpcMessage): void {
175182
if (this._sseClients.size === 0) {
183+
this._logService.debug(`[McpGateway][session ${this.id}] No SSE clients to broadcast to, dropping message`);
176184
return;
177185
}
178186

179187
const payload = JSON.stringify(message);
180188
const eventId = String(++this._lastEventId);
189+
this._logService.debug(`[McpGateway][session ${this.id}] Broadcasting SSE event id=${eventId} to ${this._sseClients.size}`);
181190
const lines = payload.split(/\r?\n/g);
182191
const data = [
183192
`id: ${eventId}`,
@@ -198,11 +207,14 @@ export class McpGatewaySession extends Disposable {
198207
}
199208

200209
private async _handleRequest(request: IJsonRpcRequest): Promise<unknown> {
210+
this._logService.debug(`[McpGateway][session ${this.id}] <-- request: ${request.method} (id=${String(request.id)})`);
211+
201212
if (request.method === 'initialize') {
202213
return this._handleInitialize(request);
203214
}
204215

205216
if (!this._isInitialized) {
217+
this._logService.warn(`[McpGateway][session ${this.id}] Rejected request '${request.method}': session not initialized`);
206218
throw new JsonRpcError(MCP_INVALID_REQUEST, 'Session is not initialized');
207219
}
208220

@@ -220,13 +232,17 @@ export class McpGatewaySession extends Disposable {
220232
case 'resources/templates/list':
221233
return this._handleListResourceTemplates();
222234
default:
235+
this._logService.warn(`[McpGateway][session ${this.id}] Unknown method: ${request.method}`);
223236
throw new JsonRpcError(MCP_METHOD_NOT_FOUND, `Method not found: ${request.method}`);
224237
}
225238
}
226239

227240
private _handleNotification(notification: IJsonRpcNotification): void {
241+
this._logService.debug(`[McpGateway][session ${this.id}] <-- notification: ${notification.method}`);
242+
228243
if (notification.method === 'notifications/initialized') {
229244
this._isInitialized = true;
245+
this._logService.info(`[McpGateway][session ${this.id}] Session initialized`);
230246
this._rpc.sendNotification({ method: 'notifications/tools/list_changed' });
231247
this._rpc.sendNotification({ method: 'notifications/resources/list_changed' });
232248
}
@@ -276,21 +292,27 @@ export class McpGatewaySession extends Disposable {
276292
? params.arguments as Record<string, unknown>
277293
: {};
278294

295+
this._logService.debug(`[McpGateway][session ${this.id}] Calling tool '${params.name}' with args: ${JSON.stringify(argumentsValue)}`);
296+
279297
try {
280298
const { result, serverIndex } = await this._toolInvoker.callTool(params.name, argumentsValue);
299+
this._logService.debug(`[McpGateway][session ${this.id}] Tool '${params.name}' completed (isError=${result.isError ?? false}, content blocks=${result.content.length})`);
281300
return {
282301
...result,
283302
content: encodeResourceUrisInContent(result.content, serverIndex),
284303
};
285304
} catch (error) {
286-
this._logService.error('[McpGatewayService] Tool call invocation failed', error);
305+
this._logService.error(`[McpGateway][session ${this.id}] Tool '${params.name}' invocation failed`, error);
287306
throw new JsonRpcError(MCP_INVALID_PARAMS, String(error));
288307
}
289308
}
290309

291310
private _handleListTools(): unknown {
292311
return this._toolInvoker.listTools()
293-
.then(tools => ({ tools }));
312+
.then(tools => {
313+
this._logService.debug(`[McpGateway][session ${this.id}] Listed ${tools.length} tool(s): [${tools.map(t => t.name).join(', ')}]`);
314+
return { tools };
315+
});
294316
}
295317

296318
private async _handleListResources(): Promise<MCP.ListResourcesResult> {
@@ -304,6 +326,7 @@ export class McpGatewaySession extends Disposable {
304326
});
305327
}
306328
}
329+
this._logService.debug(`[McpGateway][session ${this.id}] Listed ${allResources.length} resource(s) from ${serverResults.length} server(s)`);
307330
return { resources: allResources };
308331
}
309332

@@ -314,16 +337,18 @@ export class McpGatewaySession extends Disposable {
314337
}
315338

316339
const { serverIndex, originalUri } = decodeGatewayResourceUri(params.uri);
340+
this._logService.debug(`[McpGateway][session ${this.id}] Reading resource '${originalUri}' from server ${serverIndex}`);
317341
try {
318342
const result = await this._toolInvoker.readResource(serverIndex, originalUri);
343+
this._logService.debug(`[McpGateway][session ${this.id}] Resource read returned ${result.contents.length} content(s)`);
319344
return {
320345
contents: result.contents.map(content => ({
321346
...content,
322347
uri: encodeGatewayResourceUri(content.uri, serverIndex),
323348
})),
324349
};
325350
} catch (error) {
326-
this._logService.error('[McpGatewayService] Resource read failed', error);
351+
this._logService.error(`[McpGateway][session ${this.id}] Resource read failed for '${originalUri}'`, error);
327352
throw new JsonRpcError(MCP_INVALID_PARAMS, String(error));
328353
}
329354
}
@@ -339,6 +364,7 @@ export class McpGatewaySession extends Disposable {
339364
});
340365
}
341366
}
367+
this._logService.debug(`[McpGateway][session ${this.id}] Listed ${allTemplates.length} resource template(s) from ${serverResults.length} server(s)`);
342368
return { resourceTemplates: allTemplates };
343369
}
344370
}

0 commit comments

Comments
 (0)