Skip to content

Commit 5304384

Browse files
authored
fix: mcp teggCtxLifecycleMiddleware (#312)
1 parent 2a5affc commit 5304384

File tree

6 files changed

+35
-22
lines changed

6 files changed

+35
-22
lines changed

core/types/controller-decorator/MCPToolParams.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
33
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
44

55
export type ToolArgs<T extends Parameters<McpServer['tool']>['2']> = objectOutputType<T, ZodTypeAny>;
6-
export type ToolExtra = Parameters<Parameters<McpServer['tool']>['3']>['1'];
6+
export type ToolExtra = Parameters<Parameters<McpServer['tool']>['4']>['1'];
77

88
export type MCPToolResponse = CallToolResult;
99

plugin/controller/lib/impl/mcp/MCPControllerRegister.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ export class MCPControllerRegister implements ControllerRegister {
122122
sessionIdGenerator: undefined,
123123
});
124124
self.statelessTransport = transport;
125+
const mw = self.app.middleware.teggCtxLifecycleMiddleware();
125126
const initHandler = async (ctx: Context) => {
126127
if (MCPControllerRegister.hooks.length > 0) {
127128
for (const hook of MCPControllerRegister.hooks) {
@@ -135,7 +136,9 @@ export class MCPControllerRegister implements ControllerRegister {
135136
});
136137

137138
await ctx.app.ctxStorage.run(ctx, async () => {
138-
await self.statelessTransport.handleRequest(ctx.req, ctx.res);
139+
await mw(ctx, async () => {
140+
await self.statelessTransport.handleRequest(ctx.req, ctx.res);
141+
});
139142
});
140143
return;
141144
};
@@ -176,6 +179,7 @@ export class MCPControllerRegister implements ControllerRegister {
176179
mcpStreamServerInit() {
177180
const allRouterFunc = this.router.all;
178181
const self = this;
182+
const mw = self.app.middleware.teggCtxLifecycleMiddleware();
179183
const initHandler = async (ctx: Context) => {
180184
ctx.respond = false;
181185
if (MCPControllerRegister.hooks.length > 0) {
@@ -233,7 +237,9 @@ export class MCPControllerRegister implements ControllerRegister {
233237
await self.mcpServer.connect(transport);
234238

235239
await ctx.app.ctxStorage.run(ctx, async () => {
236-
await transport.handleRequest(ctx.req, ctx.res, body);
240+
await mw(ctx, async () => {
241+
await transport.handleRequest(ctx.req, ctx.res, body);
242+
});
237243
});
238244
} else {
239245
ctx.status = 400;
@@ -261,7 +267,9 @@ export class MCPControllerRegister implements ControllerRegister {
261267
'transfer-encoding': 'chunked',
262268
});
263269
await ctx.app.ctxStorage.run(ctx, async () => {
264-
await transport.handleRequest(ctx.req, ctx.res);
270+
await mw(ctx, async () => {
271+
await transport.handleRequest(ctx.req, ctx.res);
272+
});
265273
});
266274
return;
267275
}
@@ -327,6 +335,7 @@ export class MCPControllerRegister implements ControllerRegister {
327335
}
328336

329337
sseCtxStorageRun(ctx: Context, transport: SSEServerTransport | StreamableHTTPServerTransport) {
338+
const mw = this.app.middleware.teggCtxLifecycleMiddleware();
330339
const closeFunc = transport.onclose;
331340
transport.onclose = (...args) => {
332341
closeFunc?.(...args);
@@ -337,7 +346,9 @@ export class MCPControllerRegister implements ControllerRegister {
337346
const messageFunc = transport.onmessage;
338347
transport.onmessage = async (...args: [ JSONRPCMessage ]) => {
339348
await ctx.app.ctxStorage.run(ctx, async () => {
340-
await messageFunc!(...args);
349+
await mw(ctx, async () => {
350+
await messageFunc!(...args);
351+
});
341352
});
342353
};
343354
}

plugin/controller/test/fixtures/apps/mcp-app/app/controller/AppController.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
ToolExtra,
77
ToolArgsSchema,
88
Extra,
9+
Logger,
10+
Inject,
911
} from '@eggjs/tegg';
1012
import z from 'zod';
1113

@@ -22,6 +24,9 @@ export const NotificationType = {
2224

2325
@MCPController()
2426
export class AppController {
27+
@Inject()
28+
logger: Logger;
29+
2530
@MCPTool({
2631
name: 'start-notification-stream',
2732
description:
@@ -49,6 +54,8 @@ export class AppController {
4954
await sleep(interval);
5055
}
5156

57+
this.logger.info('startNotificationStream finish');
58+
5259
return {
5360
content: [
5461
{
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"name": "controller-app"
2+
"name": "mcp-app"
33
}

plugin/controller/test/mcp/mcp.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import mm from 'egg-mock';
22
import path from 'path';
3+
import fs from 'fs/promises';
34
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
45
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
56
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
@@ -274,6 +275,10 @@ describe('plugin/controller/test/mcp/mcp.test.ts', () => {
274275

275276
await streamableTransport.terminateSession();
276277
await streamableClient.close();
278+
279+
const logContent = await fs.readFile(path.join(__dirname, '../fixtures/apps/mcp-app/logs/mcp-app/mcp-app-web.log'));
280+
281+
assert.ok(logContent.includes('startNotificationStream finish'));
277282
});
278283

279284
it('stateless streamable should work', async () => {

plugin/mcp-proxy/index.ts

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import cluster from 'node:cluster';
1313
import { MCPControllerRegister, MCPControllerHook } from '@eggjs/tegg-controller-plugin/lib/impl/mcp/MCPControllerRegister';
1414
import querystring from 'node:querystring';
1515
import url from 'node:url';
16-
import { Context as EggContext, Request as EggRequest, Response as EggResponse } from '@eggjs/core';
1716

1817
const MAXIMUM_MESSAGE_SIZE = '4mb';
1918

@@ -59,13 +58,7 @@ export const MCPProxyHook: MCPControllerHook = {
5958
await self.app.mcpProxy.registerClient(id, process.pid);
6059
self.app.mcpProxy.setProxyHandler(MCPProtocols.SSE, async (req, res) => {
6160
const sessionId = req.query?.sessionId ?? querystring.parse(url.parse(req.url).query ?? '').sessionId as string;
62-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
63-
// @ts-ignore
64-
self.app.RequestClass = EggRequest;
65-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
66-
// @ts-ignore
67-
self.app.ResponseClass = EggResponse;
68-
const ctx = new EggContext(self.app as any, req, res) as any;
61+
const ctx = self.app.createContext(req, res) as unknown as Context;
6962
if (MCPControllerRegister.hooks.length > 0) {
7063
for (const hook of MCPControllerRegister.hooks) {
7164
await hook.preProxy?.(ctx, req, res);
@@ -129,13 +122,8 @@ export const MCPProxyHook: MCPControllerHook = {
129122
const sessionId = transport.sessionId!;
130123
self.streamTransports[sessionId] = transport;
131124
self.app.mcpProxy.setProxyHandler(MCPProtocols.STREAM, async (req: http.IncomingMessage, res: http.ServerResponse) => {
132-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
133-
// @ts-ignore
134-
self.app.RequestClass = EggRequest;
135-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
136-
// @ts-ignore
137-
self.app.ResponseClass = EggResponse;
138-
const ctx = new EggContext(self.app as any, req, res) as any;
125+
const mw = self.app.middleware.teggCtxLifecycleMiddleware();
126+
const ctx = self.app.createContext(req, res) as unknown as Context;
139127
if (MCPControllerRegister.hooks.length > 0) {
140128
for (const hook of MCPControllerRegister.hooks) {
141129
await hook.preProxy?.(ctx, req, res);
@@ -171,7 +159,9 @@ export const MCPProxyHook: MCPControllerHook = {
171159
}
172160
if (transport) {
173161
await self.app.ctxStorage.run(ctx, async () => {
174-
await transport.handleRequest(ctx.req, ctx.res);
162+
await mw(ctx, async () => {
163+
await transport.handleRequest(ctx.req, ctx.res);
164+
});
175165
});
176166
} else {
177167
res.writeHead(400, { 'content-type': 'application/json' });

0 commit comments

Comments
 (0)