Thoth SDK
sdk v0.1.6 / proxy v0.2.7

MCP Integration

Use Thoth as a security gateway for Model Context Protocol (MCP) tool calls — policy enforcement and behavioral monitoring for MCP-connected agents.

What is MCP?

The Model Context Protocol (MCP) is an open standard that enables AI models to connect to external tools and data sources through a unified interface. An MCP server exposes tools; an MCP client (typically an LLM host like Claude Desktop or a custom agent) calls those tools on behalf of the user.

Thoth adds a governance layer between the MCP client and the tools it calls — enforcing policy, requiring human approval for sensitive operations, and emitting tamper-evident behavioral events.


Architecture

Without Thoth, tool calls flow directly from the LLM to the MCP server. With Thoth, every call is intercepted before execution:

LLM / Agent
MCP Client
Thoth Governance
enforce + emit · <100ms
MCP Server
Tool executes

The LLM and MCP server are unaware of Thoth — governance is injected at the client execution layer. No changes to your MCP server or LLM prompt are required.


Python: OpenAI + MCP

import asyncio
import json
import os
import openai
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from thoth import instrument_openai
 
async def main():
    server_params = StdioServerParameters(
        command="npx",
        args=["-y", "@modelcontextprotocol/server-filesystem", "/tmp/workspace"],
    )
 
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as mcp_session:
            await mcp_session.initialize()
            tools_result = await mcp_session.list_tools()
 
            # Build raw execution callables for each MCP tool
            raw_tool_fns = {}
            for tool in tools_result.tools:
                async def make_executor(t_name):
                    async def execute(input_args: dict) -> str:
                        result = await mcp_session.call_tool(t_name, input_args)
                        return " ".join(c.text for c in result.content if hasattr(c, "text"))
                    return execute
                raw_tool_fns[tool.name] = await make_executor(tool.name)
 
            # Wrap with Thoth governance
            governed_fns = instrument_openai(
                raw_tool_fns,
                agent_id="mcp-filesystem-agent",
                approved_scope=["read_file", "list_directory"],
                tenant_id="acme-corp",
                enforcement="progressive",
            )
 
            # Standard OpenAI agentic loop — Thoth enforces policy on every call
            oai_client = openai.AsyncOpenAI()
            messages = [{"role": "user", "content": "List the files in /tmp/workspace"}]
 
            while True:
                response = await oai_client.chat.completions.create(
                    model="gpt-4o",
                    tools=[{"type": "function", "function": {"name": t.name, "parameters": t.inputSchema}}
                           for t in tools_result.tools],
                    messages=messages,
                )
                choice = response.choices[0]
                if choice.finish_reason == "stop":
                    print(choice.message.content)
                    break
 
                tool_results = []
                for tool_call in choice.message.tool_calls or []:
                    fn = governed_fns.get(tool_call.function.name)
                    if fn:
                        args = json.loads(tool_call.function.arguments)
                        result = await fn(args)  # Thoth enforcement here
                        tool_results.append({"role": "tool", "tool_call_id": tool_call.id, "content": str(result)})
                messages.extend(tool_results)
 
asyncio.run(main())

Python: Anthropic + MCP

import anthropic
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from thoth import instrument_anthropic
 
async def main():
    server_params = StdioServerParameters(
        command="npx",
        args=["-y", "@modelcontextprotocol/server-filesystem", "/tmp/workspace"],
    )
 
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as mcp_session:
            await mcp_session.initialize()
            tools_result = await mcp_session.list_tools()
 
            raw_tool_fns = {
                t.name: (lambda tn: lambda inp: mcp_session.call_tool(tn, inp))(t.name)
                for t in tools_result.tools
            }
 
            governed_fns = instrument_anthropic(
                raw_tool_fns,
                agent_id="mcp-filesystem-agent",
                approved_scope=["read_file", "list_directory"],
                tenant_id="acme-corp",
                enforcement="progressive",
            )
 
            client = anthropic.AsyncAnthropic()
            messages = [{"role": "user", "content": "List files in /tmp/workspace"}]
 
            while True:
                response = await client.messages.create(
                    model="claude-opus-4-5",
                    max_tokens=1024,
                    tools=[{"name": t.name, "description": t.description or "", "input_schema": t.inputSchema}
                           for t in tools_result.tools],
                    messages=messages,
                )
                if response.stop_reason == "end_turn":
                    break
 
                tool_results = []
                for block in response.content:
                    if block.type == "tool_use":
                        fn = governed_fns.get(block.name)
                        if fn:
                            result = await fn(block.input)
                            tool_results.append({"type": "tool_result", "tool_use_id": block.id, "content": str(result)})
                messages.append({"role": "assistant", "content": response.content})
                messages.append({"role": "user", "content": tool_results})

Go: WrapToolFunc() with MCP tools

package main
 
import (
    "context"
    "log"
    "os"
 
    "github.com/atensecurity/thoth-go"
)
 
func main() {
    client, err := thoth.NewClient(thoth.Config{
        APIKey:      os.Getenv("THOTH_API_KEY"),
        TenantID:    "acme-corp",
        AgentID:     "mcp-filesystem-agent",
        Enforcement: "progressive",
    })
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()
 
    rawReadFile := func(ctx context.Context, args map[string]any) (any, error) {
        return mcpSession.CallTool(ctx, "read_file", args)
    }
 
    govReadFile := client.WrapToolFunc("read_file", rawReadFile)
 
    ctx := context.Background()
    result, err := govReadFile(ctx, map[string]any{"path": "/tmp/workspace/report.txt"})
    if err != nil {
        log.Printf("error: %v", err)
        return
    }
    log.Printf("result: %v", result)
}

Enforcement scopes for MCP

ScenarioRecommended approved_scopeRecommended mode
Read-only file access["read_file", "list_directory"]progressive
Read + write["read_file", "write_file", "list_directory"]step_up for write operations
Database tools["query_db"] (exclude execute_sql, drop_table)block
Web browsing["fetch_url", "screenshot"]progressive

Behavioral events for MCP

Each governed MCP tool call emits two behavioral events (TOOL_CALL_PRE and TOOL_CALL_POST) with:

  • sourceType: "agent_tool_call" — identifies the event as coming from an agent via Thoth SDK
  • toolName — the MCP tool name
  • content — JSON-serialized MCP tool arguments (PRE) and result (POST)
  • sessionToolCalls — the list of tools already called in this session

These events are available through Thoth APIs and SIEM exports, filterable by agentId, sessionId, and toolName.


Native MCP Proxy

The thoth proxy binary intercepts MCP calls at the transport layer — no SDK, no code changes to your MCP server or agent prompt required. It runs as a stdio sidecar between Claude Desktop and any MCP server, enforcing policy on every tools/call before the upstream server executes.

Claude Desktop Setup →
Install thoth and govern your Claude Desktop MCP servers in under 5 minutes.
Enterprise Fleet Deployment →
Governed Claude Desktop config rollout workflow for managed fleets.

On this page