diff --git a/packages/core/src/tools/mcp-tool.test.ts b/packages/core/src/tools/mcp-tool.test.ts index ee97771369..1d5b9d262c 100644 --- a/packages/core/src/tools/mcp-tool.test.ts +++ b/packages/core/src/tools/mcp-tool.test.ts @@ -1021,6 +1021,48 @@ describe('DiscoveredMCPTool', () => { }); }); + describe('shouldConfirmExecute with read-only hint', () => { + it('should return false if isReadOnly is true', async () => { + const bus = createMockMessageBus(); + getMockMessageBusInstance(bus).defaultToolDecision = 'ask_user'; + const readOnlyTool = new DiscoveredMCPTool( + mockCallableToolInstance, + serverName, + serverToolName, + baseDescription, + inputSchema, + bus, + false, // trust + true, // isReadOnly + ); + const invocation = readOnlyTool.build({ param: 'mock' }); + expect( + await invocation.shouldConfirmExecute(new AbortController().signal), + ).toBe(false); + }); + + it('should return confirmation details if isReadOnly is false', async () => { + const bus = createMockMessageBus(); + getMockMessageBusInstance(bus).defaultToolDecision = 'ask_user'; + const readWriteTool = new DiscoveredMCPTool( + mockCallableToolInstance, + serverName, + serverToolName, + baseDescription, + inputSchema, + bus, + false, // trust + false, // isReadOnly + ); + const invocation = readWriteTool.build({ param: 'mock' }); + const confirmation = await invocation.shouldConfirmExecute( + new AbortController().signal, + ); + expect(confirmation).not.toBe(false); + expect(confirmation).toHaveProperty('type', 'mcp'); + }); + }); + describe('DiscoveredMCPToolInvocation', () => { it('should return the stringified params from getDescription', () => { const params = { param: 'testValue', param2: 'anotherOne' }; diff --git a/packages/core/src/tools/mcp-tool.ts b/packages/core/src/tools/mcp-tool.ts index fe4038b6e8..fa14f198b0 100644 --- a/packages/core/src/tools/mcp-tool.ts +++ b/packages/core/src/tools/mcp-tool.ts @@ -168,6 +168,7 @@ export class DiscoveredMCPToolInvocation extends BaseToolInvocation< private readonly toolDescription?: string, private readonly toolParameterSchema?: unknown, toolAnnotationsData?: Record, + readonly isReadOnly: boolean = false, ) { // Use composite format for policy checks: serverName__toolName // This enables server wildcards (e.g., "google-workspace__*") @@ -205,6 +206,10 @@ export class DiscoveredMCPToolInvocation extends BaseToolInvocation< return false; // server is trusted, no confirmation needed } + if (this.isReadOnly) { + return false; // read-only tools do not require confirmation + } + if ( DiscoveredMCPToolInvocation.allowlist.has(serverAllowListKey) || DiscoveredMCPToolInvocation.allowlist.has(toolAllowListKey) @@ -442,6 +447,7 @@ export class DiscoveredMCPTool extends BaseDeclarativeTool< this.description, this.parameterSchema, this._toolAnnotations, + this.isReadOnly, ); } }