diff --git a/provider/openaiprovider/responses.go b/provider/openaiprovider/responses.go index a6084178..aeabbe93 100644 --- a/provider/openaiprovider/responses.go +++ b/provider/openaiprovider/responses.go @@ -453,6 +453,11 @@ func responsesBuildCompletionParams(config AgentConfig, messages []*message.Mess if tl.Authorization != "" { variant.Authorization = openai.String(tl.Authorization) } + // Honor the caller's approval mode. When unset, the Responses API + // defaults require_approval to "always". + if tl.ApprovalMode != "" { + variant.RequireApproval.OfMcpToolApprovalSetting = openai.String(string(tl.ApprovalMode)) + } params.Tools = append(params.Tools, responses.ToolUnionParam{ OfMcp: &variant, }) diff --git a/provider/openaiprovider/responses_test.go b/provider/openaiprovider/responses_test.go index 43437c1b..a0959abc 100644 --- a/provider/openaiprovider/responses_test.go +++ b/provider/openaiprovider/responses_test.go @@ -3425,6 +3425,38 @@ func TestResponsesMCPServerToolAddressRouting(t *testing.T) { } } +// An MCPServer ApprovalMode is forwarded as the Responses require_approval +// setting; unset leaves it off (provider default). +func TestResponsesMCPServerApprovalMode(t *testing.T) { + const output = `{"id":"resp","object":"response","created_at":1,"status":"completed","model":"gpt-4o-mini","output":[]}` + tests := []struct { + name string + mode hostedtool.MCPApprovalMode + wantTool string + }{ + {"never", hostedtool.MCPApprovalNever, `{"type":"mcp","server_label":"drive","server_url":"https://example.com/mcp","require_approval":"never"}`}, + {"always", hostedtool.MCPApprovalAlways, `{"type":"mcp","server_label":"drive","server_url":"https://example.com/mcp","require_approval":"always"}`}, + {"unset", "", `{"type":"mcp","server_label":"drive","server_url":"https://example.com/mcp"}`}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + input := `{"model":"gpt-4o-mini","input":[{"type":"message","role":"user","content":[{"type":"input_text","text":"hello"}]}],"tools":[` + tt.wantTool + `]}` + server := newTestResponsesServer(t, input, output) + defer server.Close() + _, err := newTestResponsesClient(server, "gpt-4o-mini").RunText(t.Context(), "hello", + agent.WithTool(&hostedtool.MCPServer{ + ServerName: "drive", + ServerAddress: "https://example.com/mcp", + ApprovalMode: tt.mode, + }), + ).Collect() + if err != nil { + t.Fatalf("error = %v", err) + } + }) + } +} + func TestResponsesCodeInterpreterTool_NonStreaming(t *testing.T) { const input = ` { diff --git a/tool/hostedtool/hostedtool.go b/tool/hostedtool/hostedtool.go index 49ceea90..72a84b9d 100644 --- a/tool/hostedtool/hostedtool.go +++ b/tool/hostedtool/hostedtool.go @@ -84,8 +84,22 @@ type MCPServer struct { AllowedTools []string // Headers contains optional HTTP headers to send when connecting to the MCP server. Headers map[string]string + // ApprovalMode controls whether calls to this MCP server require approval. + // The zero value leaves the provider default (which, for the OpenAI Responses + // API, requires approval for every call). + ApprovalMode MCPApprovalMode } +// MCPApprovalMode controls whether hosted MCP tool calls require human approval. +type MCPApprovalMode string + +const ( + // MCPApprovalAlways requires approval for every hosted MCP tool call. + MCPApprovalAlways MCPApprovalMode = "always" + // MCPApprovalNever auto-approves hosted MCP tool calls. + MCPApprovalNever MCPApprovalMode = "never" +) + func (t *MCPServer) Name() string { return "mcp" }