Metadata-Version: 2.4
Name: toolgate-ai
Version: 1.0.5
Summary: Optimistic execution middleware for autonomous agents — let reads pass, intercept actions, approve at the end.
License-Expression: MIT
Project-URL: Homepage, https://toolgate.dev
Project-URL: Repository, https://github.com/GavinAlfaro/toolgate-python
Project-URL: Documentation, https://toolgate.dev
Keywords: agent,autonomous,tool-use,middleware,approval,guardrails,mcp,llm,ai-safety
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.24.0
Dynamic: license-file

# 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](https://www.npmjs.com/package/toolgate) for TypeScript/JavaScript.

## Install

```bash
pip install toolgate-ai
```

## Quick Start

```python
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](https://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:

```python
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:

```python
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:

```python
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:

```python
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

```python
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

```python
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
