Skip to main content

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

  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.1.0.tar.gz (21.5 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.1.0-py3-none-any.whl (22.0 kB view details)

Uploaded Python 3

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

Hashes for toolgate_ai-1.1.0.tar.gz
Algorithm Hash digest
SHA256 323d0b00ac5dfd9137bbb6be82935db651e482338853f9d41be5c8c240f10376
MD5 415e255a11180a8a65a9fb69e6faccbd
BLAKE2b-256 e9f6d7cf977dac58e2a3d55259c1f9fbcb2e2628a415f8e3c0a87ef0d2be6d7c

See more details on using hashes here.

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

Hashes for toolgate_ai-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4c6e9997f102edaf1d694dfd8f9b6c5e281ecc2bde64c8437d36238747fa9b69
MD5 9a9c7a631f9fedeb2d5dca1c9e7530a7
BLAKE2b-256 e61e5b91c10d2aacb687813a61c2eecb01b07e31b60c65460ffd36ef8a02d007

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