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

# MCP Tool Governance

> Enforce trust, logging, and policy for MCP servers and tool registries in agentic AI systems

Model Context Protocol (MCP) gives agents a standardised interface to call external tools — file systems, databases, APIs, calendars, code executors. This is categorically different from an agent generating text: when an MCP tool runs, it has real-world side effects. A `delete_file` call deletes a file. A `send_email` call sends an email. An `execute_sql` call mutates a database. The risk model is not "could this response be harmful?" — it is "should this agent have been allowed to invoke this tool at all?"

Most observability platforms log LLM completions. None of them see inside MCP tool invocations unless you instrument the transport layer. Cognisafe closes this gap by intercepting the LLM calls that orchestrate tool selection, and by providing a proxy wrapper for MCP client transports that logs every tool invocation as a first-class event.

## Why MCP governance is different

| Dimension          | LLM call monitoring            | MCP tool governance                                          |
| ------------------ | ------------------------------ | ------------------------------------------------------------ |
| Reversibility      | Text completions are read-only | Tool calls may write, delete, or transmit data               |
| Blast radius       | Bounded by the model's output  | Unbounded — depends on the tool's permissions                |
| Audit requirement  | "What did the model say?"      | "What actions did the agent take, and were they authorised?" |
| Compliance mapping | LLM output review              | Change management, access control, SOC 2 CC6.6               |
| Detection latency  | After the fact (async scoring) | Ideally synchronous — block before execution                 |

## Architecture

```text theme={null}
┌──────────────────────────────────────────────────────────────────┐
│  Orchestrator / Agent                                             │
│  (LangChain, AutoGen, custom, etc.)                               │
│                                                                   │
│   1. Agent decides to call a tool (LLM completion)                │
│   2. MCP client invokes tool via transport                        │
└────────────────────┬─────────────────────────────────────────────┘
                     │  MCP tool/call request
                     ▼
┌──────────────────────────────────────────────────────────────────┐
│  Cognisafe MCP Intercept Layer                                    │
│  - Logs tool name + arguments → llm_requests                     │
│  - Tags: agent_name, session_id, tool_name                        │
│  - Runs synchronous policy checks (keyword_list scorer)           │
│  - Fires threat_detected webhook if policy violated               │
│  - Passes through (or blocks) to MCP server                       │
└────────────────────┬─────────────────────────────────────────────┘
                     │
                     ▼
┌──────────────────────────────────────────────────────────────────┐
│  MCP Server                                                       │
│  - filesystem, database, API, code executor, etc.                 │
│  - Executes tool; returns result                                  │
└────────────────────┬─────────────────────────────────────────────┘
                     │  Tool result
                     ▼
┌──────────────────────────────────────────────────────────────────┐
│  Cognisafe intercept (response path)                              │
│  - Logs tool result → response_body                               │
│  - Scores result for PII / sensitive data patterns                │
└──────────────────────────────────────────────────────────────────┘
```

Separately, every LLM call the orchestrator makes (tool selection, planning, summarisation) flows through the Cognisafe proxy as described in the [core architecture](/how-it-works). The MCP intercept layer is additive — it covers the tool execution path that the LLM proxy cannot see.

## Governance framework

<Steps>
  <Step title="Inventory — know every tool your agents call">
    Before you can govern tool use, you need a complete inventory. Deploy the Cognisafe MCP interceptor (below) and run your agents in audit-only mode for one sprint. At the end, query:

    ```sql theme={null}
    SELECT
        agent_name,
        request_body->>'tool_name' AS tool_name,
        COUNT(*) AS invocation_count,
        MAX(created_at) AS last_seen
    FROM llm_requests
    WHERE request_body->>'type' = 'mcp_tool_call'
    GROUP BY agent_name, tool_name
    ORDER BY invocation_count DESC;
    ```

    This gives you the full tool call matrix: which agents call which tools, and how often. This is your baseline. Any tool appearing after this baseline was established will trigger a first-seen alert.
  </Step>

  <Step title="Trust levels — define agent-to-tool authorisation">
    Encode your authorisation policy as a Cognisafe custom scorer. The simplest form is a `keyword_list` scorer that fires when a restricted tool is called by any agent not on an allowlist.

    Create `evals/scorers.yaml` entries for each sensitivity tier:

    ```yaml theme={null}
    # Tier 1: Destructive operations — always alert
    - name: mcp_destructive_tool
      type: keyword_list
      severity: critical
      description: "Detects MCP tool calls with irreversible side effects"
      keywords:
        - delete_file
        - drop_table
        - purge_queue
        - terminate_process
        - execute_command
        - run_script
      match_on: request_body

    # Tier 2: External communications — alert and require review
    - name: mcp_external_comms
      type: keyword_list
      severity: high
      description: "Detects MCP tool calls that transmit data outside the system"
      keywords:
        - send_email
        - post_message
        - create_ticket
        - webhook_call
        - http_post
        - slack_send
      match_on: request_body

    # Tier 3: Data access — log and monitor for exfiltration patterns
    - name: mcp_data_access
      type: keyword_list
      severity: medium
      description: "Detects MCP tool calls that read potentially sensitive data"
      keywords:
        - query_database
        - read_secrets
        - get_credentials
        - export_data
        - list_users
        - fetch_pii
      match_on: request_body
    ```
  </Step>

  <Step title="Audit — tamper-evident log for every tool invocation">
    Every MCP tool call is written to `llm_requests` with:

    * `request_body`: `{"type": "mcp_tool_call", "tool_name": "send_email", "arguments": {...}}`
    * `response_body`: the tool result
    * `agent_name`: the calling agent
    * `created_at`: timestamp (TimescaleDB hypertable — immutable by partition)
    * `safety_scores`: any scorer results

    TimescaleDB's hypertable partitioning means historical data cannot be modified without access to the database host. For regulated environments, additionally configure PostgreSQL row-level security to make the `llm_requests` table append-only for the application role.

    ```sql theme={null}
    -- Append-only policy for the application database role
    CREATE POLICY llm_requests_insert_only ON llm_requests
        FOR ALL TO cognisafe_app
        USING (false)           -- no SELECT/UPDATE/DELETE via this policy
        WITH CHECK (true);      -- INSERT is allowed

    -- Grant explicit INSERT and SELECT (read) separately
    GRANT INSERT, SELECT ON llm_requests TO cognisafe_app;
    ```
  </Step>

  <Step title="Alerting — webhook on threat_detected">
    Configure a Cognisafe webhook for `threat_detected` events. Every time a `mcp_destructive_tool` or `mcp_external_comms` scorer fires, your security team receives a notification within seconds.

    ```bash theme={null}
    curl -X POST https://your-cognisafe-api/api/v1/webhooks \
      -H "Authorization: Bearer csk_..." \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://your-siem.internal/cognisafe-events",
        "events": ["threat_detected"],
        "filters": {
          "severity": ["high", "critical"],
          "scorer_name": ["mcp_destructive_tool", "mcp_external_comms"]
        }
      }'
    ```

    The webhook payload includes `agent_name`, `tool_name`, `arguments`, `session_id`, and a link to the full request in the Cognisafe dashboard.
  </Step>
</Steps>

## Python implementation

Wrap the MCP client's `call_tool` method to route invocations through Cognisafe before they reach the MCP server.

```python theme={null}
import time
import json
import httpx
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

COGNISAFE_API_URL = "http://localhost:8000"
COGNISAFE_API_KEY = "csk_..."
PROJECT_ID = "mcp-agent"
AGENT_NAME = "file-assistant"


async def log_tool_call(
    tool_name: str,
    arguments: dict,
    result: dict,
    duration_ms: float,
    session_id: str,
):
    """Fire-and-forget log to Cognisafe API."""
    payload = {
        "project_id": PROJECT_ID,
        "agent_name": AGENT_NAME,
        "session_id": session_id,
        "model": "mcp-tool",
        "request_body": {
            "type": "mcp_tool_call",
            "tool_name": tool_name,
            "arguments": arguments,
        },
        "response_body": result,
        "latency_ms": duration_ms,
    }
    async with httpx.AsyncClient() as client:
        await client.post(
            f"{COGNISAFE_API_URL}/internal/log",
            json=payload,
            headers={"Authorization": f"Bearer {COGNISAFE_API_KEY}"},
            timeout=2.0,
        )


class GovernedMCPSession:
    """
    Wraps an MCP ClientSession to log every tool invocation to Cognisafe
    and apply synchronous policy checks before forwarding to the MCP server.
    """

    BLOCKED_TOOLS = {"execute_command", "run_script", "drop_table"}

    def __init__(self, session: ClientSession, session_id: str):
        self._session = session
        self._session_id = session_id

    async def call_tool(self, tool_name: str, arguments: dict):
        # Synchronous policy gate — block before the tool runs
        if tool_name in self.BLOCKED_TOOLS:
            raise PermissionError(
                f"Tool '{tool_name}' is blocked by governance policy. "
                f"Session: {self._session_id}"
            )

        start = time.monotonic()
        result = await self._session.call_tool(tool_name, arguments)
        duration_ms = (time.monotonic() - start) * 1000

        # Non-blocking log — do not await in the hot path
        import asyncio
        asyncio.create_task(
            log_tool_call(
                tool_name=tool_name,
                arguments=arguments,
                result=result.model_dump() if hasattr(result, "model_dump") else result,
                duration_ms=duration_ms,
                session_id=self._session_id,
            )
        )

        return result


# Usage
async def main():
    server_params = StdioServerParameters(
        command="python",
        args=["mcp_server.py"],
    )

    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()

            governed = GovernedMCPSession(session, session_id="sess_abc123")

            # This call is logged to Cognisafe and checked against policy
            result = await governed.call_tool(
                "read_file",
                {"path": "/data/reports/q1.csv"},
            )
            print(result)
```

## Dangerous tool patterns

These are the tool name patterns that most commonly appear in security incidents involving agentic AI systems. Add them to your custom scorers as your inventory grows.

<CardGroup cols={2}>
  <Card title="File system operations" icon="folder-open">
    `write_file`, `delete_file`, `move_file`, `execute_command`, `run_script`, `create_symlink`

    Risk: data destruction, code execution, privilege escalation via symlink
  </Card>

  <Card title="External communications" icon="paper-plane">
    `send_email`, `post_message`, `create_ticket`, `http_post`, `slack_send`, `teams_notify`

    Risk: data exfiltration, social engineering at scale, shadow communication channels
  </Card>

  <Card title="Data access and export" icon="database">
    `query_database`, `export_data`, `read_secrets`, `get_credentials`, `list_users`, `dump_table`

    Risk: credential theft, PII exfiltration, bulk data exposure
  </Card>

  <Card title="Infrastructure control" icon="server">
    `restart_service`, `scale_deployment`, `modify_config`, `update_dns`, `revoke_certificate`

    Risk: availability impact, lateral movement, configuration drift
  </Card>
</CardGroup>

## Policy patterns

**Block on PII in tool arguments (regex scorer)**

```yaml theme={null}
- name: mcp_pii_in_args
  type: regex
  severity: high
  description: "Detects PII patterns in MCP tool call arguments"
  patterns:
    - '\b\d{3}-\d{2}-\d{4}\b'          # SSN
    - '\b[A-Z]{2}\d{6}[A-Z]\b'         # UK passport
    - '\b\d{13,16}\b'                    # Payment card
    - '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'  # Email
  match_on: request_body
```

**Alert on first-seen tool names**

Enable the `usage_alert` webhook for `new_tool_detected` events. When an agent calls a tool name that has never appeared in your `llm_requests` table for that `project_id`, Cognisafe fires the webhook. Your operations team reviews and approves or blocks the tool before it becomes routine.

**Require human-in-the-loop for financial tools**

For tools that trigger financial transactions (`create_payment`, `issue_refund`, `transfer_funds`), implement a HITL gate in your orchestrator that pauses execution and awaits approval:

```python theme={null}
HITL_REQUIRED_TOOLS = {"create_payment", "issue_refund", "wire_transfer"}

async def governed_call(governed_session, tool_name, arguments):
    if tool_name in HITL_REQUIRED_TOOLS:
        approval = await request_human_approval(
            tool_name=tool_name,
            arguments=arguments,
            approver_channel="slack://ops-approvals",
        )
        if not approval.approved:
            raise PermissionError(f"Human declined {tool_name} invocation")

    return await governed_session.call_tool(tool_name, arguments)
```

## Compliance evidence

The MCP tool invocation log in Cognisafe directly satisfies the following SOC 2 controls:

| Control                                     | Evidence from Cognisafe                                                                                                                           |
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| CC6.6 — Network security and logical access | Every tool invocation has `agent_name`, `session_id`, timestamp, and arguments. Demonstrates that only authorised agents invoked sensitive tools. |
| CC7.2 — System monitoring                   | Real-time alerting on `threat_detected` events. Dashboard shows invocation trends, anomalies, and scorer results.                                 |
| CC7.3 — Incident response                   | Webhook events feed into SIEM/incident management. Full request/response payload available for forensics.                                         |
| CC9.2 — Vendor and third-party risk         | MCP tools that call external APIs are logged; data transmitted to third parties is visible in `response_body`.                                    |

For ISO 27001 (A.12.4 — Logging and monitoring), export the `llm_requests` table (filtered to `request_body->>'type' = 'mcp_tool_call'`) to your evidence repository at each audit cycle.

<Note>
  MCP is a rapidly evolving specification. Tool name conventions vary between MCP server implementations. Build your keyword\_list scorers based on your own inventory (Step 1) rather than assuming standard tool names across all MCP servers.
</Note>

<Warning>
  The `GovernedMCPSession` wrapper above logs tool calls asynchronously to avoid blocking the agent. In high-sensitivity environments (financial, medical), consider making the log synchronous — verify the write succeeded before allowing the tool result to be returned to the orchestrator. This ensures the audit log cannot be lost if the process crashes between tool execution and log delivery.
</Warning>
