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

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, 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

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


# 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

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, lines 313-383) serves as the single entry point for all agent-initiated MCP calls. Located in 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

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:

{
  "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

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

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.

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, 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:


# 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, with public API routes defined in 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 within packages/db/src/migrations/.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →