Agent Mode
Enable autonomous tool execution with auto-approval
When an LLM returns tool calls in its response, DeepIntShield does not automatically execute them. Instead, your application explicitly calls the tool execution API, giving you full control over:
The basic flow is: Chat Request → Review Tool Calls → Execute Tools → Continue Conversation. For detailed architecture diagrams, see the MCP Architecture documentation.
The /v1/mcp/tool/execute endpoint uses the same authentication as other inference endpoints like /v1/chat/completions:
| Auth Configuration | Behavior |
|---|---|
disable_auth_on_inference: true | No auth required |
disable_auth_on_inference: false | Auth required |
Virtual keys and authentication are independent layers that work together. For details on how to use virtual keys with authentication, see Authentication and Virtual Keys.
The deepintshield Python SDK exposes a generic shield.mcp surface that
works with any MCP server connected to your gateway. It includes adapters for
OpenAI, Anthropic, and LangChain.
pip install 'deepintshield[openai]'from deepintshield import DeepintShield
shield = DeepintShield( virtual_key="sk-bf-...", base_url="http://localhost:8080",)
result = shield.mcp.call( server="DeepWiki", # case-sensitive client name from MCP Registry tool="ask_question", # bare tool name; the SDK adds the '<server>-' prefix repoName="facebook/react", question="What is Suspense?",)print(result.text)from deepintshield import DeepintShield, Tool
shield = DeepintShield(virtual_key="sk-bf-...", base_url="http://localhost:8080")openai = shield.openai()
tools = [ Tool(server="DeepWiki", name="ask_question", description="Ask a question about a public GitHub repository.", schema={"type": "object", "properties": {"repoName": {"type": "string"}, "question": {"type": "string"}}, "required": ["repoName", "question"]}),]
messages = [{"role": "user", "content": "Summarize facebook/react's reconciler."}]first = openai.chat.completions.create( model="gpt-4o-mini", messages=messages, tools=shield.mcp.to_openai(tools), tool_choice="required",)assistant = first.choices[0].messagemessages.append(assistant.model_dump(exclude_none=True))messages.extend(shield.mcp.run_openai_tool_calls(assistant.tool_calls))
final = openai.chat.completions.create(model="gpt-4o-mini", messages=messages)print(final.choices[0].message.content)curl -X POST http://localhost:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-4o", "messages": [ { "role": "user", "content": "List files in the current directory" } ] }'Response with tool calls:
{ "id": "chatcmpl-abc123", "choices": [{ "index": 0, "message": { "role": "assistant", "content": null, "tool_calls": [{ "id": "call_xyz789", "type": "function", "function": { "name": "filesystem_list_directory", "arguments": "{\"path\": \".\"}" } }] }, "finish_reason": "tool_calls" }]}The request body matches the tool call object from the response:
curl -X POST http://localhost:8080/v1/mcp/tool/execute \ -H "Content-Type: application/json" \ -d '{ "id": "call_xyz789", "type": "function", "function": { "name": "filesystem_list_directory", "arguments": "{\"path\": \".\"}" } }'Tool result response:
{ "role": "tool", "content": "[\"config.json\", \"main.go\", \"README.md\"]", "tool_call_id": "call_xyz789"}Assemble the full conversation history and continue:
curl -X POST http://localhost:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-4o", "messages": [ { "role": "user", "content": "List files in the current directory" }, { "role": "assistant", "content": null, "tool_calls": [{ "id": "call_xyz789", "type": "function", "function": { "name": "filesystem_list_directory", "arguments": "{\"path\": \".\"}" } }] }, { "role": "tool", "content": "[\"config.json\", \"main.go\", \"README.md\"]", "tool_call_id": "call_xyz789" } ] }'Final response:
{ "choices": [{ "message": { "role": "assistant", "content": "The current directory contains 3 files:\n\n1. **config.json** - Configuration file\n2. **main.go** - Go source file\n3. **README.md** - Documentation" }, "finish_reason": "stop" }]}package main
import ( "context" "fmt" deepintshield "github.com/maximhq/deepintshield/core" "github.com/maximhq/deepintshield/core/schemas")
func main() { // Initialize DeepIntShield with MCP (see Connecting to Servers) client, _ := deepintshield.Init(context.Background(), config)
// Step 1: Send initial request firstMessage := schemas.ChatMessage{ Role: schemas.ChatMessageRoleUser, Content: schemas.ChatMessageContent{ ContentStr: deepintshield.Ptr("List files in the current directory"), }, }
request := &schemas.DeepIntShieldChatRequest{ Provider: schemas.OpenAI, Model: "gpt-4o", Input: []schemas.ChatMessage{firstMessage}, }
response, err := client.ChatCompletionRequest(schemas.NewDeepIntShieldContext(context.Background(), schemas.NoDeadline), request) if err != nil { panic(err) }
// Build conversation history history := []schemas.ChatMessage{firstMessage}
// Step 2: Process tool calls if response.Choices[0].Message.ToolCalls != nil { assistantMessage := response.Choices[0].Message history = append(history, assistantMessage)
for _, toolCall := range *assistantMessage.ToolCalls { fmt.Printf("Tool requested: %s\n", *toolCall.Function.Name)
// YOUR APPROVAL LOGIC HERE // - Validate arguments // - Check permissions // - Get user confirmation if needed
// Step 3: Execute the tool toolResult, err := client.ExecuteChatMCPTool(context.Background(), toolCall) if err != nil { fmt.Printf("Tool execution failed: %v\n", err) continue }
fmt.Printf("Tool result: %s\n", *toolResult.Content.ContentStr) history = append(history, *toolResult) } }
// Step 4: Continue conversation with results finalRequest := &schemas.DeepIntShieldChatRequest{ Provider: schemas.OpenAI, Model: "gpt-4o", Input: history, }
finalResponse, err := client.ChatCompletionRequest(schemas.NewDeepIntShieldContext(context.Background(), schemas.NoDeadline), finalRequest) if err != nil { panic(err) }
fmt.Printf("Final response: %s\n", *finalResponse.Choices[0].Message.Content.ContentStr)}DeepIntShield supports two API formats for tool execution:
Use ?format=chat or omit the parameter:
POST /v1/mcp/tool/execute?format=chatRequest:
{ "id": "call_xyz789", "type": "function", "function": { "name": "filesystem_read_file", "arguments": "{\"path\": \"config.json\"}" }}Response:
{ "role": "tool", "content": "{\"key\": \"value\"}", "tool_call_id": "call_xyz789"}Use ?format=responses for the Responses API format:
POST /v1/mcp/tool/execute?format=responsesRequest:
{ "type": "function_call_output", "call_id": "call_xyz789", "name": "filesystem_read_file", "arguments": "{\"path\": \"config.json\"}"}Response:
{ "type": "function_call_output", "call_id": "call_xyz789", "output": "{\"key\": \"value\"}"}LLMs often request multiple tools in a single response. Execute them in sequence or parallel:
for _, toolCall := range *response.Choices[0].Message.ToolCalls { result, err := client.ExecuteChatMCPTool(ctx, toolCall) if err != nil { // Handle error continue } history = append(history, *result)}toolCalls := *response.Choices[0].Message.ToolCallsresults := make([]*schemas.ChatMessage, len(toolCalls))var wg sync.WaitGroup
for i, toolCall := range toolCalls { wg.Add(1) go func(idx int, tc schemas.ChatAssistantMessageToolCall) { defer wg.Done() result, err := client.ExecuteChatMCPTool(ctx, tc) if err == nil { results[idx] = result } }(i, toolCall)}
wg.Wait()
for _, result := range results { if result != nil { history = append(history, *result) }}Tool execution can fail for various reasons:
result, err := client.ExecuteChatMCPTool(ctx, toolCall)if err != nil { switch { case errors.Is(err, context.DeadlineExceeded): // Tool execution timed out case strings.Contains(err.Error(), "tool not found"): // Tool doesn't exist or client disconnected case strings.Contains(err.Error(), "not allowed"): // Tool filtered out by configuration default: // Other execution error }}Gateway error responses:
{ "error": { "type": "tool_execution_error", "message": "Tool 'filesystem_delete_file' is not allowed for this request" }}Tool execution responses are designed to be directly appended to your conversation history:
// Tool result is already in the correct formattoolResult, _ := client.ExecuteChatMCPTool(ctx, toolCall)
// Just append it directlyhistory = append(history, *toolResult)The response includes:
role field ("tool")tool_call_id for correlationcontent