Agent Mode
Enable autonomous tool execution with an eligibility allow-list
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 any supported Virtual Key carrier:
-H "Authorization: Bearer sk-ds-your-virtual-key"# or-H "x-deepintshield-vk: sk-ds-your-virtual-key"# also supported by compatible clients: x-api-key and x-goog-api-keyFor details on how virtual keys work and how to scope them, see Authentication and Virtual Keys.
When Agentic-New GAF is enabled, the key must be associated with an enabled
governed agent in the active workspace. Send X-Agent-Subject when one key is
associated with multiple agents. If that agent profile uses an identity
provider, also send its verified child workload credential in X-Agent-Token.
The gateway revalidates the exact current or rotation-grace key at decision
time; /mcp does not parse the raw key a second time.
With GAF enabled, every actual call—JSON-RPC /mcp, this endpoint, autonomous
agent mode, and nested code-mode tools—uses the same server-side canonical
decision. The decision intersects the current Virtual-Key/MCP binding, Registry
action metadata, exact-workspace OpenFGA relationships, optional context policy, and
Command Authority approval. Any successful plugin short circuit must
pass the same boundary. The legacy direct MCP result cache is disabled for both
reads and writes on this canonical path because its key does not contain the
complete GAF subject, delegated identity, client generation, and policy
decision. The older Agentic Tool Integrity and behavior-grant mechanisms are
legacy capabilities, not additional legs in this canonical decision.
For POST /v1/mcp/tool/execute, Deny returns 403, Require-approval returns 202,
and a dependency or audit failure returns 503 rather than executing the tool.
Their stable response codes are mcp_tool_authorization_denied,
mcp_tool_approval_required, and mcp_tool_authorization_unavailable.
JSON-RPC /mcp expresses the same outcome as an MCP tool result with
isError=true and a safe code, verdict, reason, decision ID, and approval ID;
it does not translate a tool-level protocol result into those HTTP statuses.
The Python SDK keeps the DeepIntShield-specific layer small and delegates the protocol to the official MCP Python SDK.
pip install 'deepintshield[mcp]'import asyncio
from deepintshield import DeepintShield, DeepintShieldErrorfrom mcp.types import TextContent
shield = DeepintShield( virtual_key="sk-ds-...", base_url="https://app.deepintshield.com",)
async def main() -> None: try: # This is an initialized official mcp.ClientSession. async with shield.mcp.connect() as session: tools = await session.list_tools() print([tool.name for tool in tools.tools])
result = await session.call_tool( "DeepWiki-ask_question", arguments={ "repoName": "facebook/react", "question": "What is Suspense?", }, ) for part in result.content: if isinstance(part, TextContent): print(part.text) except DeepintShieldError as exc: if exc.code == "mcp_tool_approval_required": print("The MCP action is waiting for approval.") elif exc.code == "mcp_tool_authorization_denied": print("The MCP action was denied by policy.") elif exc.code == "mcp_tool_authorization_unavailable": print("MCP authorization is temporarily unavailable.") else: print(f"DeepIntShield error [{exc.code}]: {exc.description}")
asyncio.run(main())curl -X POST https://app.deepintshield.com/v1/chat/completions \ -H "Authorization: Bearer sk-ds-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-ds-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-ds-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. Execute each through the official session, validate each result, and append all successful results before continuing the conversation:
async with shield.mcp.connect() as session: results = [] for call in requested_calls: result = await session.call_tool(call.name, arguments=call.arguments) results.append(result)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 the Virtual-Key tool allow-list, DeepIntShield authorizes each MCP call
against the exact workspace’s relationship graph backed by
OpenFGA. The canonical intersection checks the
governed agent’s permission on the derived resource, its can_use relationship
to the Registry tool, and its can_execute relationship to the server-resolved
named action. The current MCP adapter submits the governed agent as a direct
caller; it does not populate the GAF user or delegation fields from
X-MCP-Subject-Token. That header is only an upstream OAuth transport
credential. Explicit control-plane decisions that supply a user additionally
require acts_for, an active scoped delegation, and the user’s permission, but
that must not be inferred from the MCP subject-token header.
The server owns tool/action classification. It canonicalizes the arguments to an approval digest and never trusts a caller-supplied action class. Write-like or unknown action classes require a live approval for that exact agent, tool, action, target, permission, and digest (plus user when an explicit decision actually supplies one). Every OpenFGA check uses the higher-consistency mode; workspace stores and model pins are durable and replica-coordinated.
Manage relationships from Agentic → Policy & Access, Registry actions from Agentic → Assets → Tools / Actions, and live server connections from Agentic → MCP Registry (page title: MCP Connections).
Tool execution can fail for several reasons:
tools_to_execute)The Python SDK exposes DeepIntShield failures through one exception and one
stable string code. A session opened by shield.mcp.connect() checks every
tool result automatically because JSON-RPC /mcp represents a blocked tool as
a successful protocol response with isError=true:
from deepintshield import DeepintShieldError
try: async with shield.mcp.connect() as session: result = await session.call_tool(tool_name, arguments=arguments)except DeepintShieldError as exc: if exc.code == "mcp_tool_authorization_denied": stop_without_retrying() elif exc.code == "mcp_tool_approval_required": show_pending_approval() elif exc.code == "mcp_tool_authorization_unavailable": retry_later_only_if_replay_is_safe() else: report_code(exc.code)Do not place a failed tool result in model context before this check. Provider
and framework libraries may additionally raise their own native exceptions;
DeepIntShield-specific branching should use DeepintShieldError.code. If an
external adapter creates its own session and exposes a raw CallToolResult,
call shield.mcp.raise_for_result(result) in its result interceptor before
model consumption.
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.