Skip to main content

Optimistic execution middleware for autonomous agents — let reads pass, intercept actions, approve at the end.

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

  1. Sign up at toolgate.dev
  2. Go to API Keys+ New Key
  3. 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

toolgate_ai-1.0.5.tar.gz (19.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

toolgate_ai-1.0.5-py3-none-any.whl (20.3 kB view details)

Uploaded Python 3

File details

Details for the file toolgate_ai-1.0.5.tar.gz.

File metadata

  • Download URL: toolgate_ai-1.0.5.tar.gz
  • Upload date:
  • Size: 19.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for toolgate_ai-1.0.5.tar.gz
Algorithm Hash digest
SHA256 07d2b02a70c2563c341a4ddb441d791770359fb4dd700525849eb2d748577886
MD5 5b339128f2604875b97c4cd79ae89c08
BLAKE2b-256 ec4499cbd3e8c7b0f7d8f6b122bbc1110a5ea4b5c832a90fe0d19fab3fa0d906

See more details on using hashes here.

File details

Details for the file toolgate_ai-1.0.5-py3-none-any.whl.

File metadata

  • Download URL: toolgate_ai-1.0.5-py3-none-any.whl
  • Upload date:
  • Size: 20.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for toolgate_ai-1.0.5-py3-none-any.whl
Algorithm Hash digest
SHA256 3e66970928b0e018650bed31b8e3ec1cf0c1686002a29af1efdd550ddff778c5
MD5 1191163f3a89d98c2ab741534b1476f9
BLAKE2b-256 edc6e7fee521b96d62a379b0197aa953868e084ec1020b99f575b4f0a72fb982

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page