From 0b26024776aa86b5653cf88e4b98c686e4a33c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A5=BF=E5=BD=B1?= Date: Tue, 21 Jul 2026 12:02:53 +0800 Subject: [PATCH 1/2] feat(mcp): add typed tool UI metadata --- core/controller-decorator/src/model/MCPToolMeta.ts | 5 ++++- core/controller-decorator/test/MCPMeta.test.ts | 6 ++++++ .../test/fixtures/MCPController.ts | 9 ++++++++- core/types/controller-decorator/MCPToolParams.ts | 13 ++++++++++++- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/core/controller-decorator/src/model/MCPToolMeta.ts b/core/controller-decorator/src/model/MCPToolMeta.ts index c821dcf77..7e9cdeab6 100644 --- a/core/controller-decorator/src/model/MCPToolMeta.ts +++ b/core/controller-decorator/src/model/MCPToolMeta.ts @@ -1,4 +1,4 @@ -import type { MethodMeta, MiddlewareFunc } from '@eggjs/tegg-types'; +import type { MCPToolParams, MethodMeta, MiddlewareFunc } from '@eggjs/tegg-types'; import { ToolArgsSchemaDetail } from '../../src/util/MCPInfoUtil'; @@ -8,6 +8,7 @@ export class MCPToolMeta implements MethodMeta { readonly aclCode?: string; readonly mcpName?: string; readonly description?: string; + readonly meta?: MCPToolParams['meta']; readonly detail?: ToolArgsSchemaDetail; readonly middlewares: readonly MiddlewareFunc[]; readonly extra?: number; @@ -20,6 +21,7 @@ export class MCPToolMeta implements MethodMeta { needAcl?: boolean; aclCode?: string, description?: string; + meta?: MCPToolParams['meta']; mcpName?: string; detail?: ToolArgsSchemaDetail; extra?: number; @@ -27,6 +29,7 @@ export class MCPToolMeta implements MethodMeta { this.name = opt.name; this.needAcl = !!opt.needAcl; this.description = opt.description; + this.meta = opt.meta; this.mcpName = opt.mcpName; this.middlewares = opt.middlewares; this.aclCode = opt.aclCode; diff --git a/core/controller-decorator/test/MCPMeta.test.ts b/core/controller-decorator/test/MCPMeta.test.ts index 8fb7c2b08..e84b2658b 100644 --- a/core/controller-decorator/test/MCPMeta.test.ts +++ b/core/controller-decorator/test/MCPMeta.test.ts @@ -17,6 +17,12 @@ describe('test/MCPMeta.test.ts', () => { assert(fooControllerMetaData.className === 'MCPFooController'); assert(fooControllerMetaData.prompts[0].name === 'foo'); assert(fooControllerMetaData.tools[0].name === 'bar'); + assert.deepStrictEqual(fooControllerMetaData.tools[0].meta, { + ui: { + resourceUri: 'ui://test/tool', + visibility: [ 'model', 'app' ], + }, + }); assert(fooControllerMetaData.resources[0].name === 'car'); assert(fooControllerMetaData.resources[0].template instanceof ResourceTemplate); assert.strictEqual(fooControllerMetaData.tools[0].detail?.argsSchema as unknown, ToolType); diff --git a/core/controller-decorator/test/fixtures/MCPController.ts b/core/controller-decorator/test/fixtures/MCPController.ts index d81346fb8..92fbb39dc 100644 --- a/core/controller-decorator/test/fixtures/MCPController.ts +++ b/core/controller-decorator/test/fixtures/MCPController.ts @@ -36,7 +36,14 @@ export class MCPFooController { }; } - @MCPTool() + @MCPTool({ + meta: { + ui: { + resourceUri: 'ui://test/tool', + visibility: [ 'model', 'app' ], + }, + }, + }) async bar(@ToolArgsSchema(ToolType as any) args: ToolArgs, @Context() ctx: object): Promise { void ctx; return { diff --git a/core/types/controller-decorator/MCPToolParams.ts b/core/types/controller-decorator/MCPToolParams.ts index 6a42af339..ec51c9c2a 100644 --- a/core/types/controller-decorator/MCPToolParams.ts +++ b/core/types/controller-decorator/MCPToolParams.ts @@ -7,9 +7,20 @@ export type ToolExtra = Parameters['4']>['1']; export type MCPToolResponse = CallToolResult; +export type MCPToolVisibility = 'model' | 'app'; + +export interface MCPToolUIMeta { + resourceUri?: string; + visibility?: MCPToolVisibility[]; +} + +export type MCPToolRegistrationMeta = { + ui: MCPToolUIMeta; +}; + export interface MCPToolParams { name?: string; description?: string; timeout?: number; + meta?: MCPToolRegistrationMeta; } - From 73eb7d330cde4e01641c5bf41c1f40a4f5dbf78a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A5=BF=E5=BD=B1?= Date: Tue, 21 Jul 2026 12:11:59 +0800 Subject: [PATCH 2/2] feat(mcp): expose tool UI metadata --- plugin/controller/lib/impl/mcp/MCPServerHelper.ts | 1 + plugin/controller/test/mcp/helper.test.ts | 14 +++++++++++++- .../service-worker/src/mcp/MCPServerHelper.ts | 1 + .../test/fixtures/mcp/MCPTestController.ts | 10 +++++++++- standalone/service-worker/test/mcp/mcp.test.ts | 6 ++++++ 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/plugin/controller/lib/impl/mcp/MCPServerHelper.ts b/plugin/controller/lib/impl/mcp/MCPServerHelper.ts index eaa3b7b77..5db0d7256 100644 --- a/plugin/controller/lib/impl/mcp/MCPServerHelper.ts +++ b/plugin/controller/lib/impl/mcp/MCPServerHelper.ts @@ -137,6 +137,7 @@ export class MCPServerHelper { this.server.registerTool(name, { description, inputSchema: schema, + _meta: toolMeta.meta, // TODO: outputSchema }, handler); } diff --git a/plugin/controller/test/mcp/helper.test.ts b/plugin/controller/test/mcp/helper.test.ts index c85deb5ce..252172680 100644 --- a/plugin/controller/test/mcp/helper.test.ts +++ b/plugin/controller/test/mcp/helper.test.ts @@ -45,6 +45,12 @@ describe('plugin/controller/test/mcp/mcp.test.ts', () => { name: 'testTool', needAcl: false, middlewares: [], + meta: { + ui: { + resourceUri: 'ui://test/tool', + visibility: [ 'model', 'app' ], + }, + }, contextParamIndex: 1, detail: { argsSchema: ToolType, @@ -128,10 +134,16 @@ describe('plugin/controller/test/mcp/mcp.test.ts', () => { ]); const tools = await client.listTools(); - assert.deepEqual(tools.tools.map(tool => ({ name: tool.name, description: tool.description })), [ + assert.deepEqual(tools.tools.map(tool => ({ name: tool.name, description: tool.description, _meta: tool._meta })), [ { description: undefined, name: 'testTool', + _meta: { + ui: { + resourceUri: 'ui://test/tool', + visibility: [ 'model', 'app' ], + }, + }, }, ]); diff --git a/standalone/service-worker/src/mcp/MCPServerHelper.ts b/standalone/service-worker/src/mcp/MCPServerHelper.ts index 1c795a9f3..30300dfad 100644 --- a/standalone/service-worker/src/mcp/MCPServerHelper.ts +++ b/standalone/service-worker/src/mcp/MCPServerHelper.ts @@ -125,6 +125,7 @@ export class MCPServerHelper { this.server.registerTool(name, { description, inputSchema: schema, + _meta: toolMeta.meta, }, handler); } diff --git a/standalone/service-worker/test/fixtures/mcp/MCPTestController.ts b/standalone/service-worker/test/fixtures/mcp/MCPTestController.ts index e254b7aff..03545d998 100644 --- a/standalone/service-worker/test/fixtures/mcp/MCPTestController.ts +++ b/standalone/service-worker/test/fixtures/mcp/MCPTestController.ts @@ -15,7 +15,15 @@ const AddArgs = { @Middleware(McpTestAdvice) @MCPController({ name: 'test-server', version: '1.0.0' }) export class MCPTestController { - @MCPTool({ description: 'Echo the input message' }) + @MCPTool({ + description: 'Echo the input message', + meta: { + ui: { + resourceUri: 'ui://test/echo', + visibility: [ 'model' ], + }, + }, + }) async echo(@ToolArgsSchema(EchoArgs) args: ToolArgs): Promise { return { content: [{ type: 'text', text: args.message }], diff --git a/standalone/service-worker/test/mcp/mcp.test.ts b/standalone/service-worker/test/mcp/mcp.test.ts index 036010196..5bf6e93cd 100644 --- a/standalone/service-worker/test/mcp/mcp.test.ts +++ b/standalone/service-worker/test/mcp/mcp.test.ts @@ -47,6 +47,12 @@ describe('standalone/service-worker/test/mcp/mcp.test.ts', () => { const echoTool = result.tools.find(t => t.name === 'echo'); assert.strictEqual(echoTool?.description, 'Echo the input message'); + assert.deepStrictEqual(echoTool?._meta, { + ui: { + resourceUri: 'ui://test/echo', + visibility: [ 'model' ], + }, + }); const addTool = result.tools.find(t => t.name === 'add'); assert.strictEqual(addTool?.description, 'Add two numbers');