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 operationswrite— state changes without data lossdestructive— 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:
- Company-wide defaults
- Project-specific overrides
- Agent-specific exceptions
- 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:
- Authentication — Validate JWT-derived gateway token
- Profile resolution — Determine effective profile from binding hierarchy
- Catalog check — Verify tool exists and is not quarantined
- Policy evaluation — Match against ordered policy list
- Action request creation — If
require_approvalmatches, persist request and return409 - Execution or denial — Proceed only if all gates pass
- 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_approvalpolicy 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →