> ## Documentation Index
> Fetch the complete documentation index at: https://docs.verifiedx.me/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP (Python)

> Wrap standalone MCP tool handlers with VerifiedX in Python.

**Best for:** teams implementing MCP servers or MCP tool handlers directly in Python and wanting tool-level boundary protection without changing their server design.

<Note>
  Run doctor in the repo before you wire anything:

  * Python: `verifiedx doctor`

  Protect one action free: [verifiedx.me](https://verifiedx.me/?vx_source=docs.mcp-python)

  If doctor shows a supported native adapter already owns the tool loop, use that page instead of the raw MCP handler path.
</Note>

## Install

```bash theme={null}
pip install verifiedx
```

<Note>
  This page is for standalone MCP tool handlers you own directly. If MCP is being surfaced through OpenAI Agents or another higher-level SDK, use that native adapter page instead.
</Note>

## Net-new VerifiedX code

This is the actual VerifiedX delta in an existing MCP server.

```python theme={null}
from verifiedx import init_verifiedx, wrap_tool_handler

verifiedx = init_verifiedx()

handlers = {
    "search_memories": wrap_tool_handler(
        "search_memories",
        search_memories,
        verifiedx=verifiedx,
    ),
    "add_memory": wrap_tool_handler(
        "add_memory",
        add_memory,
        verifiedx=verifiedx,
        policy_scope="memory_write",
    ),
}

# In your existing callTool path:
return handlers[tool_name](params)
```

<Note>
  That is the important part. The rest of your MCP server stays the same: your `listTools()` output, transport, server wiring, and request routing do not need to be redesigned.
</Note>

<Tip>
  If a tool is definitely a durable memory write, pass `policy_scope="memory_write"` instead of relying only on name heuristics.
</Tip>

<Note>
  `wrap_tool_handler(...)` is a thin adapter over VerifiedX core boundary wrappers. It uses the same preflight, decision-receipt, execution-report, and runtime-loopback path as the rest of the product.
</Note>

<Note>
  Your MCP tool surface is the config here. VerifiedX uses your existing tool names, descriptions, schemas, and params shape as the source of truth for what to preflight.
</Note>

## Optional: wrap tool definitions too

If your server returns tool definitions directly, you can also wrap them before returning them from `listTools()`:

```python theme={null}
from verifiedx import wrap_tool_definition

wrapped_definitions = [
    wrap_tool_definition(definition, verifiedx=verifiedx)
    for definition in tool_definitions
]
```

<Note>
  Boundary protection lives in `wrap_tool_handler(...)`. `wrap_tool_definition(...)` just enriches the tool definitions you expose from your MCP server.
</Note>

## Full example

```python theme={null}
from verifiedx import (
    init_verifiedx,
    wrap_tool_definition,
    wrap_tool_handler,
)

tool_definitions = [
    {
        "name": "search_memories",
        "description": "Search customer memory entries.",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string"},
            },
            "required": ["query"],
        },
    },
    {
        "name": "add_memory",
        "description": "Persist a customer memory entry.",
        "input_schema": {
            "type": "object",
            "properties": {
                "namespace": {"type": "string"},
                "key": {"type": "string"},
                "value": {},
            },
            "required": ["namespace", "key", "value"],
        },
    },
    {
        "name": "set_workflow_status",
        "description": "Update internal workflow status.",
        "input_schema": {
            "type": "object",
            "properties": {
                "workflow_id": {"type": "string"},
                "status": {"type": "string"},
                "reason": {"type": "string"},
            },
            "required": ["workflow_id", "status", "reason"],
        },
    },
]

definitions_by_name = {definition["name"]: definition for definition in tool_definitions}

verifiedx = init_verifiedx()

wrapped_tool_definitions = [
    wrap_tool_definition(definition, verifiedx=verifiedx)
    for definition in tool_definitions
]

def search_memories(params):
    return {
        "ok": True,
        "hits": [
            {"key": "cust_123.preference", "query": params["query"]},
        ],
    }

def add_memory(params):
    return {
        "ok": True,
        "saved": {
            "namespace": params["namespace"],
            "key": params["key"],
            "value": params["value"],
        },
    }

def set_workflow_status(params):
    return {
        "ok": True,
        "workflow_updated": {
            "workflow_id": params["workflow_id"],
            "status": params["status"],
            "reason": params["reason"],
        },
    }

handlers = {
    "search_memories": wrap_tool_handler(
        "search_memories",
        search_memories,
        verifiedx=verifiedx,
        definition=definitions_by_name["search_memories"],
    ),
    "add_memory": wrap_tool_handler(
        "add_memory",
        add_memory,
        verifiedx=verifiedx,
        policy_scope="memory_write",
        definition=definitions_by_name["add_memory"],
    ),
    "set_workflow_status": wrap_tool_handler(
        "set_workflow_status",
        set_workflow_status,
        verifiedx=verifiedx,
        definition=definitions_by_name["set_workflow_status"],
    ),
}

def list_tools():
    return wrapped_tool_definitions

def call_tool(tool_name, params):
    handler = handlers.get(tool_name)
    if handler is None:
        return {"ok": False, "error": f"Unknown tool: {tool_name}"}
    return handler(params)
```

<Note>
  Do not use raw `install_runtime(...)` for this path. The Python MCP surface is `wrap_tool_handler(...)`, with optional `wrap_tool_definition(...)` for `listTools()` metadata.
</Note>

## Composed systems

If this MCP tool call is part of a larger multi-agent or agent+human workflow, pass upstream context into VerifiedX so the current tool invocation has better system and situational awareness before it takes a high-impact action.

This is useful when a supervisor agent, parent workflow, or human reviewer already has context that the current MCP tool should use before taking action.

VerifiedX does not require a fixed schema for this. Pass the upstream context you already have in any JSON-serializable shape.

```python theme={null}
upstream = {
    "source": "workflow_supervisor",
    "workflow_id": "WF-2203",
    "approval_status": "approved_with_follow_up",
    "human_review": {
        "reviewer": "ops_lead",
        "result": "approved",
    },
    "prior_agent_output": {
        "summary": "Billing verification is complete.",
    },
}

with verifiedx.with_upstream_context(upstream):
    result = handlers["set_workflow_status"](
        {
            "workflow_id": "WF-2203",
            "status": "awaiting_human",
            "reason": "billing verification is missing",
        }
    )
```

<Note>
  Upstream context is supporting workflow context from outside the current tool invocation. It is not proof that this tool already executed any local action.
</Note>

## Async handlers

If your MCP handler is async, `wrap_tool_handler(...)` preserves that shape:

```python theme={null}
async def add_memory(params):
    return {"ok": True, "saved": params}

guarded_add_memory = wrap_tool_handler(
    "add_memory",
    add_memory,
    verifiedx=verifiedx,
    policy_scope="memory_write",
)

result = await guarded_add_memory(
    {"namespace": "crm", "key": "cust_123.preference", "value": "sms"}
)
```

## What the wrapper already does

Once wrapped, VerifiedX handles the MCP tool boundary directly.

That includes:

* Injecting `_meta.verifiedx` into request params and result payloads
* Recording retrieval-like tools into run history as support inputs
* Preflighting high-impact tools before the handler runs
* Observing tool-result ingress with `sourceUri: "mcp://<tool_name>"`
* Emitting MCP tool-result events with `sourceLineage: ["mcp_tool"]`

## What gets preflighted

VerifiedX infers the protected boundary from the MCP tool name and params.

That includes:

* `memory_write`
* `external_message_send`
* `record_mutation`
* `system_change`

Retrieval-like tools stay in run history as support inputs instead of being treated as mutations.

Examples from the actual Python MCP wrapper behavior:

* `search_memories` records retrieval history
* `add_memory` with `namespace`, `key`, and `value` preflights as `memory_write`
* `send_email` infers `external_message_send`
* `update_customer_record` infers `record_mutation`
* `set_workflow_status` infers `system_change`

<Note>
  The explicit override today is mainly `policy_scope="memory_write"` for durable memory writes. Other action classes are inferred from the MCP tool name and params shape.
</Note>

## What to expect at runtime

Protected MCP boundaries can return:

* `allow`
* `allow_with_warning`
* `replan_required`
* `goal_fail_terminal`

Every outcome includes a structured decision receipt.

If a tool is replanned, the side effect does not execute. The wrapped handler returns the normal VerifiedX blocked result shape, including:

* `ok: false`
* `blocked: true`
* `boundary_outcome`
* `safe_next_steps`
* `decision_receipt`

In a standalone MCP server, you will usually consume that blocked result locally and let the caller replan. When the same tool is composed inside a larger orchestration layer, the same receipt can signal either local replan or upstream replan.

## Validation coverage

The standalone Python MCP wrapper is directly covered in this repo.

That includes:

* Retrieval history carrying into a later memory-write preflight
* Durable memory writes preflighting as `memory_write`
* Internal workflow updates preflighting as `system_change`
* Blocked external sends returning the normal loopback result and suppressing the side effect

## Pricing note

One protected action check equals one real boundary preflight. Taint, event ingest, execution reports, and decision reads are all included at that price. The Free Sandbox includes every language, provider, framework, and adapter.

VerifiedX does not replace your MCP server or orchestration. It returns receipts your system can keep local, route downstream, or pass upstream.

***

For the full raw runtime reference, see the [Python SDK](/sdks/python). If your MCP tools are being used through OpenAI Agents, see the OpenAI Agents SDK page.
