feat: let plan mode search and read connected mcp servers (#11205)

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
hugocasa
2026-09-17 16:24:30 +02:00
committed by GitHub
co-authored by Claude Opus 5
parent a571117f3f
commit 5371519f0f
2 changed files with 11 additions and 3 deletions
@@ -127,6 +127,12 @@ describe('read/write split', () => {
expect(getTool('call_mcp_write_tool').requiresConfirmation).toBe(true)
})
it('admits search and reads in plan mode, never writes', () => {
expect(getTool('search_mcp_tools').planModeSafe).toBe(true)
expect(getTool('call_mcp_read_tool').planModeSafe).toBe(true)
expect(getTool('call_mcp_write_tool').planModeSafe).toBeFalsy()
})
// The rejection sends the model to the write tool, which classifies from the
// same cached listing: without dropping it, that retry is refused too and the
// model has nowhere to go until the entry expires.
@@ -345,8 +345,9 @@ const callMcpToolSchema = z.object({
/**
* The read and write call tools differ only in which side of the `readOnlyHint`
* split they accept, and that check is what keeps a mutating call behind the
* user's confirmation — building both from one body keeps them from drifting.
* split they accept, and that check is what keeps a mutating call out of plan
* mode and behind the user's confirmation — building both from one body keeps
* them from drifting.
*/
function createCallTool(servers: McpServer[], mode: 'read' | 'write'): Tool<{}> {
const isRead = mode === 'read'
@@ -360,7 +361,7 @@ function createCallTool(servers: McpServer[], mode: 'read' | 'write'): Tool<{}>
),
showDetails: true,
...(isRead
? {}
? { planModeSafe: true }
: {
requiresConfirmation: true,
confirmationMessage: (args: any) => `Call ${args?.tool ?? ''} on ${args?.server ?? ''}`
@@ -429,6 +430,7 @@ export function createMcpTools(servers: McpServer[]): Tool<{}>[] {
'search_mcp_tools',
'Search the tools exposed by the MCP servers connected to this workspace (listed in the system prompt). Returns server + tool names to pass to call_mcp_read_tool or call_mcp_write_tool, each with the input schema its arguments must follow.'
),
planModeSafe: true,
fn: async ({ args, workspace, toolId, toolCallbacks }) => {
const parsed = searchMcpToolsSchema.parse(args)
toolCallbacks.setToolStatus(toolId, { content: 'Searching MCP tools...' })