Skip to content

Tool Execution

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:

  • Which tool calls to execute
  • User approval workflows
  • Security validation
  • Audit logging

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):

Terminal window
-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.

Terminal window
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].message
messages.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)

DeepIntShield supports two API formats for tool execution:

Use ?format=chat or omit the parameter:

Terminal window
POST /v1/mcp/tool/execute?format=chat

Request:

{
"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:

Terminal window
POST /v1/mcp/tool/execute?format=responses

Request:

{
"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:

StageQuestionExample check
ConnectMay this principal use this MCP server?can_connect on mcp_server:<id>
InvokeMay this principal call this tool?can_invoke on mcp_tool:<server>/<tool>
ResourceMay 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:

  • The tool execution timed out
  • The tool does not exist, or its MCP client is disconnected
  • The tool is filtered out by configuration (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:

  • The correct role field ("tool")
  • A matching tool_call_id for correlation
  • Properly formatted content

Append the returned message to your message list and send it back in your next chat request - no reshaping required.


Agent Mode

Enable autonomous tool execution with auto-approval

Open →

Tool Filtering

Control which tools are available per request

Open →