feat(core): automatically execute read-only MCP tools

This commit is contained in:
A.K.M. Adib
2026-03-30 12:43:03 -04:00
parent 9cf410478c
commit 15ae8540d6
2 changed files with 48 additions and 0 deletions
+42
View File
@@ -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' };
+6
View File
@@ -168,6 +168,7 @@ export class DiscoveredMCPToolInvocation extends BaseToolInvocation<
private readonly toolDescription?: string,
private readonly toolParameterSchema?: unknown,
toolAnnotationsData?: Record<string, unknown>,
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,
);
}
}