# How Paperclip Manages Agent Governance and Approvals: A Complete Guide to MCP Access Control

> Discover how Paperclip enforces agent governance and approvals with its four-layer MCP Access Governance stack. Learn how to manage tool call approvals effectively.

- Repository: [Paperclip/paperclip](https://github.com/paperclipai/paperclip)
- Tags: how-to-guide
- Published: 2026-08-14

---

**Paperclip enforces agent governance through a four-layer MCP Access Governance stack—Catalog, Profiles, Policies, and Gateway—requiring explicit human approval for any tool call that matches a `require_approval` policy.**

Paperclip's **v1 MCP Access Governance** framework provides enterprise-grade control over what autonomous agents can execute against external services. The system sits between agents and any MCP-compatible endpoint—whether GitHub, Linear, or local fixtures—to enforce **default-deny security** with granular approval workflows. This article breaks down the exact mechanisms, source code locations, and API patterns that power Paperclip's agent governance and approval system.

## The Four Layers of MCP Access Governance

Paperclip's governance architecture consists of tightly coupled components that together determine whether an agent's tool call proceeds, pauses for approval, or gets blocked entirely.

| Layer | Purpose | Core Database Tables |
|-------|---------|-------------------|
| **Catalog** | Discovers and classifies tools by risk level (`read`, `write`, `destructive`) | `tool_catalog_entries` |
| **Profiles** | Allow/deny filters attached to actors via bindings | `tool_profiles`, `tool_profile_bindings` |
| **Policies** | Dynamic rules evaluated after profile selection | `tool_policies` |
| **Gateway & Audit** | Enforcement point with immutable logging | `tool_call_events`, action request tables |

According to the [MCP Access Governance documentation](https://github.com/paperclipai/paperclip/blob/master/doc/MCP-ACCESS-GOVERNANCE.md), new or unexpected write/destructive tools are **quarantined by default** until an operator explicitly reviews them.

## Catalog: Automatic Risk Classification

The **Catalog layer** runs discovery when a managed Connection is created. It parses the MCP schema and generates `tool_catalog_entries` with inferred risk levels based on operation semantics.

Risk levels follow this classification:
- `read` — non-mutating operations
- `write` — state changes without data loss
- `destructive` — irreversible operations (deletions, overwrites)

Unrecognized write or destructive tools enter a **quarantine state** that blocks execution regardless of profile or policy configuration.

### Refreshing the Catalog

```bash
curl -X POST "$PAPERCLIP_URL/api/tool-connections/$CONNECTION_ID/catalog/refresh" \
  -H "Authorization: Bearer $BOARD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}' | jq '{discoveredCount, quarantinedCount}'

```

This endpoint populates new catalog entries and surfaces any drift that requires operator attention. As noted in the source, "new write/destructive tools will appear as `quarantined`" until manually reviewed.

## Profiles: Scoped Permission Bundles

**Tool Profiles** are named allow/deny configurations that filter the catalog for specific actors. Profiles attach to targets through **Bindings**, with the narrowest binding taking precedence:

1. Company-wide defaults
2. Project-specific overrides
3. Agent-specific exceptions
4. Routine or issue-scoped rules

### Creating a Read-Only Profile

```bash

# Create the profile

curl -X POST "$PAPERCLIP_URL/api/companies/$COMPANY_ID/tools/profiles" \
  -H "Authorization: Bearer $BOARD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "profileKey": "engineering.read-only",
        "name": "Engineering Read-Only",
        "defaultAction": "deny",
        "entries": [{ "selectorType": "risk_level", "selectorValue": "read", "effect": "include" }]
      }' | jq '{id, name}'

# Bind to a project

curl -X POST "$PAPERCLIP_URL/api/companies/$COMPANY_ID/tools/profiles/$PROFILE_ID/bind" \
  -H "Authorization: Bearer $BOARD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "targetType": "project", "targetId": "'"$PROJECT_ID"'", "priority": 10 }' | jq .

```

The `defaultAction: "deny"` ensures that only explicitly included tools (those matching `risk_level: "read"`) become visible to agents in that project scope.

## Policies: Dynamic Enforcement Rules

While Profiles define *what tools are available*, **Policies** determine *how those tools may be used*. Policy types include:

| Policy Type | Behavior |
|-------------|----------|
| `allow` | Permit the matched call |
| `block` | Deny immediately (overrides `allow`) |
| `require_approval` | Generate an **Action Request** for human review |
| `rate_limit` | Enforce call frequency caps |
| `trust_rule` | Auto-allow based on previously approved argument patterns |

Policy evaluation follows priority order, with `block` always winning over `allow`, and `require_approval` taking precedence over default allows.

### Configuring Approval Requirements

```bash
curl -X POST "$PAPERCLIP_URL/api/companies/$COMPANY_ID/tools/policies" \
  -H "Authorization: Bearer $BOARD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "policyType": "require_approval",
        "selector": { "risk_level": "write" },
        "priority": 100,
        "config": { "expirySeconds": 86400 }
      }' | jq '{id, policyType}'

```

Any write-level call passing the profile check now generates an **Action Request** with a 24-hour expiry window.

## Gateway: Enforcement and Audit Point

The **Tool Gateway** ([`server/src/routes/tool-gateway.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/routes/tool-gateway.ts), lines 313-383) serves as the single entry point for all agent-initiated MCP calls. Located in [`server/src/services/tool-gateway.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/tool-gateway.ts), the gateway service implements the full policy engine.

### Agent Call Flow

When an agent calls `/api/tool-gateway/tools/call`, the gateway executes this sequence:

1. **Authentication** — Validate JWT-derived gateway token
2. **Profile resolution** — Determine effective profile from binding hierarchy
3. **Catalog check** — Verify tool exists and is not quarantined
4. **Policy evaluation** — Match against ordered policy list
5. **Action request creation** — If `require_approval` matches, persist request and return `409`
6. **Execution or denial** — Proceed only if all gates pass
7. **Audit logging** — Write immutable record to `tool_call_events`

### Requiring Approval: The 409 Response

```bash
curl -X POST "$PAPERCLIP_URL/api/tool-gateway/tools/call" \
  -H "X-Paperclip-Tool-Gateway-Token: $GATEWAY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "tool": "create_item",
        "parameters": { "title": "Urgent feature" }
      }' -i

```

The gateway returns **HTTP 409 Conflict** with a structured body:

```json
{
  "error": "approval_required",
  "actionRequestId": "req_abc123",
  "reasonCode": "approval_required",
  "message": "This call requires human approval before execution"
}

```

The call is **paused, not rejected**—the agent can retry once approval is granted.

## Human Approval Workflow

Approvers interact through the UI or direct API calls. Each approval records the **canonical hash** of the arguments, preventing argument tampering between request and resolution.

### Approving via API

```bash
curl -X POST "$PAPERCLIP_URL/api/tool-gateway/action-requests/$ACTION_REQUEST_ID/approve" \
  -H "Authorization: Bearer $BOARD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "companyId": "'"$COMPANY_ID"'" }' | jq '{id, status, resolvedByUserId}'

```

After verification, the gateway **resumes the original agent call** transparently.

## Trust Rules: Scaling Without Security Loss

Frequent approvals for identical operations create operational friction. **Trust rules** address this by promoting approved requests into auto-allow policies.

### Creating a Trust Rule

```bash
curl -X POST "$PAPERCLIP_URL/api/companies/$COMPANY_ID/tools/action-requests/$ACTION_REQUEST_ID/trust-rule" \
  -H "Authorization: Bearer $BOARD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "approvalThreshold": 2, "expiresAt": "2026-12-31T00:00:00Z" }' | jq '{id, policyType, config}'

```

Trust rules match on **exact argument shape** for the same actor scope. They invalidate automatically when:
- The underlying tool schema changes
- The expiration timestamp passes
- An operator manually revokes the rule

## Immutable Audit Trail

Every gateway decision—allow, block, or approval-required—writes to the append-only `tool_call_events` table. The audit endpoint supports filtered retrieval for compliance and debugging.

```bash
curl -H "Authorization: Bearer $BOARD_API_KEY" \
  "$PAPERCLIP_URL/api/tool-gateway/audit?companyId=$COMPANY_ID&limit=100" | \
  jq '[.[] | {createdAt, action, decision: .details.decision, tool: .details.tool, outcome: .details.outcome, reasonCode: .details.reasonCode}]'

```

Each record contains:
- Decision and outcome
- Matched policy IDs
- Redaction plan applied
- Full argument hash

## Default Security Posture

Per the [release notes](https://github.com/paperclipai/paperclip/blob/master/doc/RELEASE-NOTES-mcp-access-governance.md), Paperclip's default posture ensures **defense-in-depth**:

- Unknown tools → **deny**
- Catalog drift (new write/destructive) → **quarantine**
- Write tools without policy → **require_approval**
- Destructive tools → **deny** until explicit un-quarantine

Any privilege escalation requires **explicit operator action**—the system never silently expands agent capabilities.

## Quick Verification: The Smoke Test

Paperclip bundles a reference implementation for validating governance configuration:

```bash

# Install the safe read-only example

curl -X POST "$PAPERCLIP_URL/api/companies/$COMPANY_ID/tools/examples/safe-read-only-todo-kv/install" \
  -H "Authorization: Bearer $BOARD_API_KEY" \
  -H "Content-Type: application/json" -d '{}' | jq .

# Run verification

curl -X POST "$PAPERCLIP_URL/api/companies/$COMPANY_ID/tools/examples/safe-read-only-todo-kv/smoke" \
  -H "Authorization: Bearer $BOARD_API_KEY" \
  -H "Content-Type: application/json" -d '{}' | jq '{ok, checks}'

```

A properly configured stack reports `ok: true` with three green checks confirming read-allow, write-deny, and audit capture.

## Summary

- **Layered enforcement**: Paperclip combines Catalog (discovery), Profiles (scoped availability), Policies (dynamic rules), and Gateway (enforcement) into a unified governance stack
- **Human-in-the-loop**: The `require_approval` policy type pauses agent execution and surfaces Action Requests for explicit human review
- **Default-deny**: Unknown tools, quarantined discoveries, and destructive operations are blocked by default
- **Scalable trust**: Approved requests can become **trust rules** that auto-allow identical future calls without repeated human intervention
- **Complete audit**: Every decision is immutably logged with policy references, argument hashes, and redaction plans

## Frequently Asked Questions

### How does Paperclip prevent agents from accessing dangerous tools?

Paperclip **quarantines** any tool classified as `write` or `destructive` during catalog refresh. Quarantined tools are invisible to profile selection until an operator explicitly reviews and releases them. Additionally, the default posture denies destructive tools entirely until un-quarantined through a deliberate administrative action.

### What happens when an agent call requires approval?

The **Tool Gateway** returns **HTTP 409** with an `actionRequestId`. The original call is **paused**—not rejected—and will resume automatically once a human approver calls the approval endpoint. The gateway verifies that approved arguments match the canonical hash from the original request, preventing tampering during the approval window.

### Can approved actions be automated without repeated human review?

Yes. After approval, operators can promote the request to a **trust rule** using `POST /action-requests/{id}/trust-rule`. This creates a policy that auto-allows future calls with identical argument shapes for the same actor scope. Trust rules include expiration dates and invalidate automatically if the tool schema changes.

### Where is the policy engine implemented in the source code?

The core policy engine lives in [`server/src/services/tool-gateway.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/tool-gateway.ts), with public API routes defined in [`server/src/routes/tool-gateway.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/routes/tool-gateway.ts) (lines 313-383). The database schema supporting governance—including tables for profiles, policies, action requests, and audit events—is established in migration [`0203_interaction_resolver_governance.sql`](https://github.com/paperclipai/paperclip/blob/main/0203_interaction_resolver_governance.sql) within `packages/db/src/migrations/`.