mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-22 00:30:19 +02:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Agents use remote MCP tools through a governed gateway. > - The gateway lists tools only while their shared connection is healthy. > - One failed call currently marks the whole connection as unhealthy. > - Later calls to healthy sibling tools then fail with `tool_not_found`. > - This pull request keeps non-authentication call failures on their invocation. > - Agents can continue their work while credential and access gates remain in force. ## Linked Issues or Issue Description Refs #10147. Related work: #15462 fixed oversized and malformed response handling. #15448 covers timeout and JSON-RPC health handling together with separate SSE and catalog discovery changes. Its patch still marks fetch failures and HTTP failures as connection errors. #15325 proposed a transport failure counter; it has merge conflicts and has not changed since October 6. #11910 is an older timeout fix with merge conflicts. This PR covers the remaining fetch and HTTP failure paths on current master, with the same narrow call-health rule for timeouts and JSON-RPC failures. It does not include the SSE or discovery changes from #15448. The timeout session-reset check follows the recovery case discussed in #15448. **What happened?** A shared remote MCP connection works until one call has a network failure, timeout, HTTP 503, or JSON-RPC error. Every later call on that connection returns `tool_not_found`, including healthy sibling tools. Connection setup then asks for user attention even though the failure did not prove that credentials need repair. **Expected behavior** The original invocation retains its failure. Authorized sibling tools remain callable. The gateway does not replay an ambiguous action. HTTP 401/403, missing credentials, and explicit insufficient scope keep their access gates. **Steps to reproduce** 1. Give an agent access to two read tools on one remote MCP connection. 2. Make one tool fail with a network error or HTTP 503. 3. Call the other tool in the same gateway session. 4. Before this fix, the second call fails with `tool_not_found` before provider dispatch. **Paperclip version or commit** `master` at `e6dcd1e8e9`. The regression runs against source with synthetic data. **Deployment mode** Any deployment that uses a shared remote MCP connection. This is a core gateway issue. ## What Changed - Preserve connection health after a network failure, timeout, non-authentication HTTP error, or JSON-RPC error from one tool call. - Retain HTTP 401/403 and insufficient-scope health gates. - Retain HTTP 401/403 gates during MCP initialization and its initialized notification, with HTTP 503 controls for both stages. - Discard only the upstream session used by a network failure or timeout. Let the next explicit call initialize again. - Add sibling-tool regression cases, authentication controls, and real HTTP header/body timeout checks. - Update runtime recovery guidance and the feature and access maps. ## Verification - Red: the four new sibling-tool cases failed on the original implementation. Each second call returned HTTP 404 with `tool_not_found`. HTTP 401/403 control cases passed. - Green: the same six-case selection passed after the production change: `pnpm --filter @paperclipai/server exec vitest run src/__tests__/tool-gateway-service.test.ts -t 'keeps sibling MCP tools usable|keeps the connection blocked' --maxWorkers=1`. - The regression checks the actual gateway and embedded PostgreSQL. The injected provider fault preserves the call boundary. It checks failure recording, no automatic replay, catalog retention, and successful sibling dispatch. - The real local HTTP fixture checks cancellation before headers and during a body, then a successful explicit call with a fresh MCP session. - `node scripts/check-maps.mjs` and `git diff --check` pass. - Complete nearby suites pass: 166 tests across `tool-gateway-service.test.ts`, `tool-gateway.test.ts`, `remote-mcp-protocol.test.ts`, and `mcp-http.test.ts`, with one test worker. - `pnpm --filter @paperclipai/server exec tsc --noEmit` passes after refreshing Runner TypeScript declarations. The first package typecheck used declarations from before the rebase and failed two unchanged native-session call sites; rebuilding that dependency resolved both errors. - The rebased head also passes all 11 selected sibling/authentication/session regressions. - Review regression red: setup-stage HTTP 401/403 controls failed because connection health stayed `ok`; the two setup-stage HTTP 503 controls passed. Green: all six setup controls and the complete 166-test nearby run pass. The fix separately handles typed initialization authentication failures and preserves the existing caller error. The server compiler also passes after this change. - Full repository CI passes on `256a65ccc302c1c31a80e87f1f91411d955028de`: typecheck, build, test shards, Runner checks, browser checks, Canary dry run, and aggregate gates. All checks are terminal: 55 successful, 2 expected skips, and the separate Paperclip Review check is neutral. Greptile is 5/5 on this exact head, with no open findings. - Full repository checks were run in CI. Local verification in this worktree used the focused suites and server compiler to keep parallel work bounded. - This change has no live provider or staging deployment proof. ## Risks - A provider that remains unavailable can stay listed until a health check detects the outage. Each call still fails with its original controlled reason code. - No automatic retry occurs because a failed remote action may already have run. - Already unhealthy connections still need a successful health check after an upgrade. - No schema, credential policy, or permission contract changes. ## Model Used - OpenAI Codex, GPT-6 family, with tool use and code execution. The runtime does not expose the exact serving model ID or context window. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
211 lines
12 KiB
Markdown
211 lines
12 KiB
Markdown
# MCP Runtime Operations
|
|
|
|
This runbook covers Paperclip Tools & Access runtime slots for MCP connections. It is written for board and CloudOps operators responding to stuck local stdio slots, degraded remote HTTP connections, capacity deferrals, restart storms, and secret-resolution failures.
|
|
|
|
Do not print raw bearer tokens, gateway session tokens, credential headers, environment variables, or secret values while following this runbook. The APIs below return redacted state and audit metadata; keep shell tracing disabled when exporting credentials.
|
|
|
|
Tool action approvals require `PAPERCLIP_TOOL_ACTION_SIGNING_SECRET` to be set independently from auth/JWT secrets. `paperclipai onboard` generates it for local instances, and worktree setup propagates or generates an independent value in the worktree `.env`; operator-managed deployments must set it explicitly. Rotate it deliberately: changing it invalidates outstanding signed tool-action approvals, so drain or reject pending approvals before rotation.
|
|
|
|
## Support Matrix
|
|
|
|
| Transport | Local trusted | Hosted cloud / public authenticated | Notes |
|
|
| --- | --- | --- | --- |
|
|
| `remote_http` | Supported | Supported | Preferred production path. Paperclip proxies calls through the gateway with policy, audit, timeout, and redaction controls. |
|
|
| `local_stdio` | Supported through approved templates and supervised runtime slots | Supported only when an explicitly trusted MCP runtime worker/host is configured | Set `PAPERCLIP_TRUSTED_MCP_RUNTIME_HOST` or `PAPERCLIP_TOOL_RUNTIME_TRUSTED_HOST` only for a worker that is allowed to supervise local processes. Do not enable arbitrary agent-supplied commands. |
|
|
|
|
## Native runtime gateway ownership
|
|
|
|
Paperclip Runner creates an immutable MCP assignment for one agent. Its gateway
|
|
stores the owner in `agentId` and the agent context scope. Authentication checks
|
|
that the run belongs to that owner and that the gateway and profile metadata
|
|
refer to the same assignment. Invalid ownership or assignment metadata rejects
|
|
the token with `gateway_token_run_context_invalid`.
|
|
|
|
The reserved `native:` profile key also identifies a native assignment if its
|
|
gateway metadata is cleared. Clearing metadata cannot turn the assignment into
|
|
a shared gateway. Invalid JSON metadata, including JSON `null`, returns the same
|
|
authentication rejection. Use an ordinary profile for an explicit shared gateway.
|
|
|
|
Older native gateways can have a null `agentId`. Authentication still checks
|
|
their metadata owner. When the owner reuses the assignment, Paperclip validates
|
|
the gateway and profile before it binds the gateway to that agent. A conflicting
|
|
owner is never overwritten.
|
|
|
|
Native assignments enter the run configuration once through the runtime MCP
|
|
delivery path. Managed gateway discovery excludes historical native assignments.
|
|
Their stored rows remain available for existing runs. Explicit company gateways
|
|
continue to use their configured scopes.
|
|
|
|
## Client-side tool-call deadline
|
|
|
|
The gateway bounds each provider call, but the agent's MCP client also needs a
|
|
bound. Without one, a gateway request that never answers holds the agent turn
|
|
until the adapter execution timeout ends the run. Claude's default MCP tool
|
|
timeout is far longer than a run, and ACP `mcpServers` entries have no
|
|
per-server timeout field. So when an ACP Claude run receives runtime MCP
|
|
servers, the ACPX engine sets `MCP_TOOL_TIMEOUT` to 15 minutes. A call that
|
|
passes this deadline returns an MCP error to the model, and the turn
|
|
continues. An `MCP_TOOL_TIMEOUT` value in the adapter `env` configuration
|
|
takes priority.
|
|
|
|
## Metrics
|
|
|
|
The board runtime health API summarizes one-hour event windows plus current durable slot state:
|
|
|
|
```sh
|
|
curl -fsS \
|
|
-H "Authorization: Bearer $BOARD_API_KEY" \
|
|
"$PAPERCLIP_URL/api/companies/$COMPANY_ID/tools/runtime-health" | jq .
|
|
```
|
|
|
|
Metrics surfaced there include:
|
|
|
|
- Current slot counts: active, starting, running, idle, failed, stopped.
|
|
- Stuck slot counts: starting/running slots without progress for 5 minutes.
|
|
- Runtime events: capacity deferrals, restart attempts, restart suppression, idle evictions.
|
|
- Tool-call health: call count, timeout count/rate, failure count/rate, average latency, p95 latency.
|
|
- Connection health: active, disabled, degraded, `remote_http`, and `local_stdio` connection counts.
|
|
- Secret failures: missing-secret failures in the last hour.
|
|
- Audit write failures: durable `audit_write_failed` counter increments whenever MCP audit-event persistence fails.
|
|
|
|
## Alerts
|
|
|
|
| Alert | Severity | Suggested threshold | First responder action |
|
|
| --- | --- | --- | --- |
|
|
| `mcp_runtime_stuck_starting_slot` | Critical | Any starting slot older than 5 minutes | Inspect slot health/logs, stop the slot, restart it once, then disable the connection if it sticks again. |
|
|
| `mcp_runtime_stuck_running_slot` | Critical | Any running slot with no progress for 5 minutes | Inspect recent audit events and active calls; restart only after confirming no healthy call is still in progress. |
|
|
| `mcp_runtime_high_timeout_rate` | Warning/Critical | Warning at >=3 timeouts and >=10% in 1 hour; critical at >=10 timeouts or >=25% | Check upstream MCP health, runtime capacity, and gateway audit failures before retrying workloads. |
|
|
| `mcp_runtime_high_error_rate` | Warning/Critical | Warning at >=5 failures and >=10% in 1 hour; critical at >=10 failures or >=25% | Group audit failures by `reasonCode`, then fix credentials/config or disable the affected connection. |
|
|
| `mcp_runtime_capacity_deferrals_repeated` | Warning/Critical | Warning at >=3 capacity deferrals in 1 hour; critical at >=10 | Stop idle/stale slots, reduce noisy workloads, or raise slot caps only after confirming host capacity. |
|
|
| `mcp_runtime_restart_storm` | Warning/Critical | Warning at >=3 restarts in 1 hour; critical on any restart suppression | Stop the slot, inspect stderr/audit reason codes, and keep the connection disabled until the template/upstream is fixed. |
|
|
| `mcp_runtime_connection_health_degraded` | Warning/Critical | Any active enabled connection with degraded/failed/missing-secret health, or any disabled enabled-path connection | Run health check, refresh catalog after recovery, or keep the connection disabled and route agents to alternatives. |
|
|
| `mcp_runtime_missing_secret_failures` | Warning/Critical | Warning on any missing-secret failure; critical at >=3 in 1 hour | Check secret bindings and provider health without revealing secret values; rotate or rebind missing secrets. |
|
|
| `mcp_runtime_audit_write_failures` | Critical | Any audit write failure | Treat as a control-plane incident; restore DB/audit durability before retrying tool workloads. |
|
|
|
|
## Diagnose A Stuck Slot
|
|
|
|
1. Read the health summary and note firing alert names:
|
|
|
|
```sh
|
|
curl -fsS \
|
|
-H "Authorization: Bearer $BOARD_API_KEY" \
|
|
"$PAPERCLIP_URL/api/companies/$COMPANY_ID/tools/runtime-health" | jq '{status, metrics, alerts}'
|
|
```
|
|
|
|
2. List durable runtime slots:
|
|
|
|
```sh
|
|
curl -fsS \
|
|
-H "Authorization: Bearer $BOARD_API_KEY" \
|
|
"$PAPERCLIP_URL/api/companies/$COMPANY_ID/tools/runtime-slots" | jq .
|
|
```
|
|
|
|
3. Inspect recent gateway audit events without printing secrets:
|
|
|
|
```sh
|
|
curl -fsS \
|
|
-H "Authorization: Bearer $BOARD_API_KEY" \
|
|
"$PAPERCLIP_URL/api/tool-gateway/audit?companyId=$COMPANY_ID&limit=100" \
|
|
| jq '[.[] | {createdAt, action, entityType, entityId, reasonCode: .details.reasonCode, tool: .details.tool, durationMs: .details.durationMs}]'
|
|
```
|
|
|
|
4. Identify the affected `slotId`, `connectionId`, `reasonCode`, and whether the slot is `starting`, `running`, `idle`, `failed`, or `stopped`.
|
|
|
|
## Clear A Stuck Slot
|
|
|
|
Stop the slot first when it is stale, idle, failed, or confirmed not to be serving a healthy active call:
|
|
|
|
```sh
|
|
curl -fsS -X POST \
|
|
-H "Authorization: Bearer $BOARD_API_KEY" \
|
|
-H "Content-Type: application/json" \
|
|
"$PAPERCLIP_URL/api/companies/$COMPANY_ID/tools/runtime-slots/$SLOT_ID/stop" \
|
|
-d '{}' | jq .
|
|
```
|
|
|
|
Restart once when the template/config is expected to recover:
|
|
|
|
```sh
|
|
curl -fsS -X POST \
|
|
-H "Authorization: Bearer $BOARD_API_KEY" \
|
|
-H "Content-Type: application/json" \
|
|
"$PAPERCLIP_URL/api/companies/$COMPANY_ID/tools/runtime-slots/$SLOT_ID/restart" \
|
|
-d '{}' | jq .
|
|
```
|
|
|
|
If restart suppression fires, do not keep retrying. Disable the connection:
|
|
|
|
```sh
|
|
curl -fsS -X PATCH \
|
|
-H "Authorization: Bearer $BOARD_API_KEY" \
|
|
-H "Content-Type: application/json" \
|
|
"$PAPERCLIP_URL/api/tool-connections/$CONNECTION_ID" \
|
|
-d '{"enabled":false,"status":"disabled"}' | jq '{id, name, enabled, status, healthStatus}'
|
|
```
|
|
|
|
## Verify Recovery
|
|
|
|
An individual remote MCP tool-call failure stays on its invocation. A network
|
|
failure, timeout, non-authentication HTTP error, or JSON-RPC error does not change
|
|
the connection's shared health or hide its other tools. HTTP 401/403 responses,
|
|
missing credentials, and explicit insufficient-scope errors retain their access
|
|
gates. Health checks and catalog refreshes still assess connection health.
|
|
|
|
After a network failure or timeout, the next explicit call can start a fresh MCP
|
|
session. The gateway never automatically replays the failed action: the provider
|
|
may already have performed it. Check the outcome before retrying a write. If a
|
|
connection was already marked unhealthy before upgrading, use the health check
|
|
below to restore access after verifying the endpoint.
|
|
|
|
1. Run the connection health check:
|
|
|
|
```sh
|
|
curl -fsS -X POST \
|
|
-H "Authorization: Bearer $BOARD_API_KEY" \
|
|
-H "Content-Type: application/json" \
|
|
"$PAPERCLIP_URL/api/tool-connections/$CONNECTION_ID/health-check" \
|
|
-d '{}' | jq '{connection: {id: .connection.id, healthStatus: .connection.healthStatus, healthMessage: .connection.healthMessage}, runtimeSlot}'
|
|
```
|
|
|
|
2. Refresh the catalog after a remote endpoint or stdio template recovers:
|
|
|
|
```sh
|
|
curl -fsS -X POST \
|
|
-H "Authorization: Bearer $BOARD_API_KEY" \
|
|
-H "Content-Type: application/json" \
|
|
"$PAPERCLIP_URL/api/tool-connections/$CONNECTION_ID/catalog/refresh" \
|
|
-d '{}' | jq '{discoveredCount, quarantinedCount}'
|
|
```
|
|
|
|
3. Re-read runtime health:
|
|
|
|
```sh
|
|
curl -fsS \
|
|
-H "Authorization: Bearer $BOARD_API_KEY" \
|
|
"$PAPERCLIP_URL/api/companies/$COMPANY_ID/tools/runtime-health" | jq '{status, metrics, alerts}'
|
|
```
|
|
|
|
Recovery is complete when stuck-slot alerts clear, timeout/error rates return below threshold, the connection is healthy or intentionally disabled, and audit events show no new restart suppression or capacity deferrals.
|
|
|
|
### Catalog publication
|
|
|
|
Discovery keeps the last descriptor when pages repeat a tool name. Catalog rows
|
|
are written in transactions of at most 500 tools. Refresh preserves existing row
|
|
IDs, first-seen timestamps, operator-disabled actions, and unchanged pending
|
|
reviews. Publication locks the application before the connection, as setup
|
|
finalization and removal do. Provider discovery runs before these locks.
|
|
|
|
Each completed chunk remains committed if a later chunk fails. Default-profile
|
|
synchronization runs in a separate transaction after catalog publication. A
|
|
failure there rolls back the profile changes without removing discovered tools.
|
|
Retry the catalog refresh after correcting the failure.
|
|
|
|
## Verification Coverage
|
|
|
|
Automated coverage includes:
|
|
|
|
- A synthetic degraded runtime-health scenario in `server/src/__tests__/tool-access-service.test.ts` that creates a stale running slot, degraded connection, timeout event, capacity deferral, and restart suppression.
|
|
- A durable audit-write failure scenario in `server/src/__tests__/tool-access-service.test.ts` that verifies `mcp_runtime_audit_write_failures` fires from the counter path.
|
|
- A gateway runtime recovery scenario in `server/src/__tests__/tool-gateway.test.ts` that recovers a stuck local stdio slot before reuse.
|
|
- PostgreSQL catalog publication tests in `server/src/__tests__/tool-catalog-publication.test.ts` cover 250 and 2,500 tools, overlapping pages, preserved choices, partial failure, and publication lock contention.
|