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.
The /v1/mcp/tool/execute endpoint uses the same authentication as other inference endpoints like /v1/chat/completions: authenticate every request with your Virtual Key.
Pass the key in the Authorization header (or the x-bf-vk header):
-H "Authorization: Bearer sk-bf-your-virtual-key"# or-H "x-bf-vk: sk-bf-your-virtual-key"For details on how virtual keys work and how to scope them, 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="https://app.deepintshield.com",)
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="https://app.deepintshield.com")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 https://app.deepintshield.com/v1/chat/completions \ -H "Authorization: Bearer sk-bf-your-virtual-key" \ -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 https://app.deepintshield.com/v1/mcp/tool/execute \ -H "Authorization: Bearer sk-bf-your-virtual-key" \ -H "Content-Type: application/json" \ -d '{ "id": "call_xyz789", "type": "function", "function": { "name": "filesystem-list_directory", "arguments": "{\"path\": \".\"}" } }'Tool result response:
{ "role": "tool", "content": "[\"notes.txt\", \"app.py\", \"README.md\"]", "tool_call_id": "call_xyz789"}Assemble the full conversation history and continue:
curl -X POST https://app.deepintshield.com/v1/chat/completions \ -H "Authorization: Bearer sk-bf-your-virtual-key" \ -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": "[\"notes.txt\", \"app.py\", \"README.md\"]", "tool_call_id": "call_xyz789" } ] }'Final response:
{ "choices": [{ "message": { "role": "assistant", "content": "The current directory contains 3 files:\n\n1. **notes.txt** - Text notes\n2. **app.py** - Application source file\n3. **README.md** - Documentation" }, "finish_reason": "stop" }]}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\": \"notes.txt\"}" }}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\": \"notes.txt\"}"}Response:
{ "type": "function_call_output", "call_id": "call_xyz789", "output": "{\"key\": \"value\"}"}LLMs often request multiple tools in a single response. Each tool call carries its own id, so you can execute them one at a time and append every result to the conversation history before continuing.
With the Python SDK, pass the whole list of tool calls to the helper and it returns one tool-result message per call, ready to append:
messages.extend(shield.mcp.run_openai_tool_calls(assistant.tool_calls))With the Gateway, send one POST /v1/mcp/tool/execute request per tool call and append each returned tool message to your conversation history.
Beyond name-based allow-lists, DeepIntShield can authorize each MCP interaction against a relationship graph backed by OpenFGA, so access depends on who is related to what - ownership, team membership, and folder→document inheritance. A single tool call is checked at up to three stages:
| Stage | Question | Example check |
|---|---|---|
| Connect | May this principal use this MCP server? | can_connect on mcp_server:<id> |
| Invoke | May this principal call this tool? | can_invoke on mcp_tool:<server>/<tool> |
| Resource | May this principal touch the record the tool acts on? | viewer on document:{args.path} |
The Resource stage is the record-level gate an allow-list can’t express:
read_file is allowed in general, but this principal may only read files they
have a viewer relationship to. Each check is the same relationship policy
operand, evaluated inline and folded into the decision cache, so it adds no
latency to the common path. Server/tool bindings are managed from
MCP Registry → Authorization; resource relationships come from your app data.
Tool execution can fail for several reasons:
tools_to_execute)When execution fails, /v1/mcp/tool/execute returns an error response:
{ "error": { "type": "tool_execution_error", "message": "Tool 'filesystem-delete_file' is not allowed for this request" }}Tool execution responses are designed to be appended directly to your conversation history. Each response already includes:
role field ("tool")tool_call_id for correlationcontentAppend the returned message to your message list and send it back in your next chat request - no reshaping required.