Optimistic execution middleware for autonomous agents — let reads pass, intercept actions, approve at the end. Supports MCP servers and custom local/hosted tools.
Project description
ToolGate (Python)
MCP-first execution middleware for autonomous agents.
Intercepts write/delete/send/execute tool calls, returns phantom responses so the AI keeps running, and queues them for human approval. Approved actions execute for real — against the actual MCP server — even when the AI is offline.
Agent calls MCP tool ──▶ ToolGate classifies ──▶ READ? ──▶ Execute immediately
│
└──▶ ACTION? ──▶ Return phantom result
Store params + MCP URL
┊
Agent finishes ──▶ Session appears in dashboard
┊
From anywhere ──▶ Approve & Execute
(calls real MCP server)
Also available as an npm package for TypeScript/JavaScript.
Install
pip install toolgate-ai
Quick Start
import asyncio
from toolgate import mcp_tool_gate, auto_config_from_mcp
async def main():
# 1. Auto-discover tools from your MCP servers
config = await auto_config_from_mcp([
{"name": "gmail", "url": "https://gmail-mcp.example.com"},
{"name": "github", "url": "https://github-mcp.example.com"},
])
# 2. Create a gate
gate = mcp_tool_gate(
mcp_executor,
api_key="tg_live_...",
mcp_servers=config.mcp_servers,
)
# 3. Give gate.proxy to your agent
await agent.run(execute=gate.proxy)
# 4. Finalize — session appears in dashboard, actions wait for approval
await gate.finalize()
asyncio.run(main())
Getting Your API Key
- Sign up at toolgate.dev
- Go to API Keys → + New Key
- Copy the key — that's all you need
No database setup, no Supabase credentials, no extra config.
MCP Server Auth
If your MCP servers require authentication, pass headers per server. They're stored alongside each intercepted action (RLS-protected) and sent automatically at execution time:
from toolgate import mcp_tool_gate, MCPServerDef
gate = mcp_tool_gate(
mcp_executor,
api_key="tg_live_...",
mcp_servers=[
MCPServerDef(
name="gmail",
url="https://gmail-mcp.example.com",
headers={"Authorization": "Bearer ya29.xxx"},
),
MCPServerDef(
name="github",
url="https://github-mcp.example.com",
headers={"X-API-Key": "ghp_xxx"},
),
MCPServerDef(
name="weather",
url="https://weather-mcp.example.com",
# no auth needed — omit headers
),
],
)
If your MCP provider embeds auth in the URL (Composio, Zapier MCP, etc.) you don't need headers — it works automatically.
Auto-Discovery
auto_config_from_mcp calls tools/list on each server and builds the config:
from toolgate import auto_config_from_mcp, mcp_tool_gate
config = await auto_config_from_mcp([
{"name": "gmail", "url": "https://gmail-mcp.example.com", "headers": {"Authorization": "Bearer ..."}},
{"name": "github", "url": "https://github-mcp.example.com", "headers": {"X-API-Key": "ghp_xxx"}},
])
gate = mcp_tool_gate(mcp_executor, api_key="tg_live_...", mcp_servers=config.mcp_servers)
Manual Tool Definitions
If you already know the tool list or can't call the server at startup:
from toolgate import mcp_tool_gate, MCPServerDef, MCPToolDef
gate = mcp_tool_gate(
mcp_executor,
api_key="tg_live_...",
mcp_servers=[
MCPServerDef(
name="gmail",
url="https://gmail-mcp.example.com",
headers={"Authorization": "Bearer ya29.xxx"},
tools=[
MCPToolDef(name="gmail_listMessages", description="List messages in the inbox"),
MCPToolDef(name="gmail_sendMessage", description="Send a new email message"),
MCPToolDef(name="gmail_trashMessage", description="Move a message to trash"),
],
),
],
)
How Hosted Execution Works
When ToolGate intercepts an action it stores three things in Supabase:
- The tool name and params the agent wanted to call
- The MCP server URL that owns the tool
- Any auth headers for that server
When you click Approve & Execute in the dashboard (from any device, any time), it calls the MCP server directly using those stored values. No agent process needs to be running.
Classification
ToolGate automatically classifies every MCP tool as a read (execute immediately) or action (intercept) by analyzing the tool name and description:
list*,get*,search*,fetch*,read*→ read (passthrough)send*,create*,delete*,update*,post*,write*→ action (intercepted)- Unknown → intercepted (safe default)
Programmatic Approval (local agent mode)
If the agent is still running and you want to approve in-process:
gate = mcp_tool_gate(mcp_executor, api_key="tg_live_...")
await agent.run(execute=gate.proxy)
await gate.finalize()
# Approve everything and execute for real
await gate.approve_all()
# Or reject everything
await gate.reject_all()
Inspection
gate.reads # all reads that executed
gate.pending # all intercepted actions (with mcp_server_url, params)
gate.current_session # full session snapshot
gate.summary() # pretty-printed CLI summary
Full Example
import asyncio
from toolgate import mcp_tool_gate, MCPServerDef, MCPToolDef
async def mcp_executor(tool: str, params: dict):
# call your MCP client library here
...
async def main():
gate = mcp_tool_gate(
mcp_executor,
api_key="tg_live_...",
mcp_servers=[
MCPServerDef(
name="gmail",
url="https://gmail-mcp.example.com",
headers={"Authorization": "Bearer ya29.xxx"},
tools=[
MCPToolDef(name="gmail_listMessages", description="List messages in the inbox"),
MCPToolDef(name="gmail_sendMessage", description="Send a new email message"),
],
),
],
)
gate.describe("Read inbox and draft replies to urgent emails")
# Reads pass through, writes are intercepted
emails = await gate.proxy("gmail_listMessages", {"maxResults": 10}) # ✅ READ
detail = await gate.proxy("gmail_getMessage", {"id": emails[0]["id"]}) # ✅ READ
draft = await gate.proxy("gmail_sendMessage", { # 🛑 SEND — intercepted
"to": "boss@co.com",
"subject": "Re: Q3 Report",
"body": "Reviewed — numbers look solid.",
})
await gate.finalize()
# Session appears in dashboard. Click "Approve & Execute" to send the real email.
asyncio.run(main())
API Reference
mcp_tool_gate(executor, *, api_key, mcp_servers, agent_name)
| Param | Type | Description |
|---|---|---|
api_key |
str |
Your ToolGate API key — required for dashboard sync |
agent_name |
str |
Display name shown in dashboard |
mcp_servers |
list[MCPServerDef] |
MCP server definitions |
MCPServerDef
| Field | Type | Description |
|---|---|---|
name |
str |
Display name |
url |
str |
HTTP endpoint for the MCP server |
tools |
list[MCPToolDef] |
Tool list (omit to use auto_config_from_mcp) |
headers |
dict[str, str] |
Auth headers sent on every call (stored securely) |
auto_config_from_mcp(servers)
Calls tools/list on each server and returns a ToolGateConfig. Accepts the same headers per server entry.
Instance methods
| Method | Description |
|---|---|
.proxy(name, params) |
The executor to hand to your agent |
.describe(text) |
Set a human-readable task description |
.finalize() |
End session, persist to dashboard |
.approve_all() |
Approve + execute all pending actions locally |
.reject_all() |
Reject all pending actions |
.execute_approval(result) |
Execute with per-action decisions |
.summary() |
Pretty-printed session summary for CLI logging |
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file toolgate_ai-1.1.0.tar.gz.
File metadata
- Download URL: toolgate_ai-1.1.0.tar.gz
- Upload date:
- Size: 21.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
323d0b00ac5dfd9137bbb6be82935db651e482338853f9d41be5c8c240f10376
|
|
| MD5 |
415e255a11180a8a65a9fb69e6faccbd
|
|
| BLAKE2b-256 |
e9f6d7cf977dac58e2a3d55259c1f9fbcb2e2628a415f8e3c0a87ef0d2be6d7c
|
File details
Details for the file toolgate_ai-1.1.0-py3-none-any.whl.
File metadata
- Download URL: toolgate_ai-1.1.0-py3-none-any.whl
- Upload date:
- Size: 22.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4c6e9997f102edaf1d694dfd8f9b6c5e281ecc2bde64c8437d36238747fa9b69
|
|
| MD5 |
9a9c7a631f9fedeb2d5dca1c9e7530a7
|
|
| BLAKE2b-256 |
e61e5b91c10d2aacb687813a61c2eecb01b07e31b60c65460ffd36ef8a02d007
|