Files
PaperClipAI/doc/connections/GENERIC-REMOTE-MCP.md
Dotta a560b48d6d feat(apps): refine Postman and Shopify setup (#12357)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Apps give those agents governed access to external tools.
> - Provider catalogs must match each provider's current protocol and
credential contract.
> - Postman method labels and API-key placement were outdated.
> - Shopify now offers a UCP commerce endpoint that needs a managed
agent-profile argument.
> - This pull request updates both providers and documents the complete
connection-authoring workflow.
> - The benefit is accurate setup, safer runtime defaults, and a
repeatable provider review process.

## Linked Issues or Issue Description

Refs #11965

This is stack 10 of 11. It depends on stack 9 and preserves the final
catalog work recovered from #11965.

Related: #5904 covers Shopify skill routing. This pull request covers
the Apps connection contract instead.

## What Changed

- Update Postman hosted MCP methods, capability choices, default
selection, and bearer-token placement.
- Add Shopify UCP commerce and Storefront compatibility methods with
public-store prerequisites.
- Inject the reviewed Shopify UCP agent profile at runtime and remove
that managed field from user input schemas.
- Classify Shopify checkout completion and cancellation as destructive
actions.
- Expand the connection authoring runbook from provider research through
verification and pull request handoff.
- Add focused shared, server, and UI coverage.
- Make the approved-execution waiter phase-aware so slow preparation
cannot consume the provider execution timeout and grace period.
- Settle legacy pre-execute-on-approve requests and invocations as
failed, clear their stale idempotency key, and allow a fresh governed
approval instead of leaving work stuck in `executing`.

## Verification

- `pnpm exec vitest run packages/shared/src/app-definitions.test.ts
server/src/__tests__/tool-access-service.test.ts
ui/src/pages/apps/AppsConnect.test.tsx -t
"Postman|Shopify|normalizeConnectionMethodConfig|classifyRisk"` (16
passed)
- `pnpm exec vitest run
server/src/services/approved-execution-wait.test.ts` (4 passed)
- `pnpm exec vitest run server/src/__tests__/tool-gateway.test.ts -t
"enforces policy, approvals, retries, rate limits, and company
boundaries for connected remote MCP calls"` (1 passed)
- `pnpm exec vitest run
server/src/__tests__/tool-gateway-service.test.ts` (21 passed; includes
legacy approval settlement and fresh-approval recovery)
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm check:token-gates`
- `pnpm -r typecheck`
- `pnpm build`

## Risks

- Shopify UCP calls now include a Paperclip-managed agent profile that
overrides caller input at the same path.
- Postman EU credentials now use the hosted MCP server's bearer-token
contract instead of the general REST API header.
- The catalog generator and checked-in definitions change together to
prevent regeneration drift.
- Approved execution preparation has an explicit two-minute bound;
provider execution retains its own 65-second timeout and persistence
grace starting from durable provider start.
- Legacy approvals created before execute-on-approve are intentionally
terminalized and must be requested again under the current signed
contract.

> I checked `ROADMAP.md`. This provider update does not duplicate
planned core work. The related open Shopify PR addresses skill routing,
not Apps connections.

## Model Used

OpenAI Codex, GPT-5. The runtime exact model ID and context window were
not exposed. The model used reasoning, tool use, and code execution.

## 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 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
2026-08-29 12:08:35 -05:00

12 KiB

Connecting any remote MCP server

Paperclip can connect a standards-compliant remote HTTP MCP server without a Paperclip code change. A curated AppDefinition is a convenience layer — branding, tailored fields, scoped defaults, support copy — not a prerequisite.

This is the documented baseline for connecting anything. Read Connection authoring runbook when you want to add the branded convenience layer on top for a vendor Paperclip should promote.

Accepted in the generic remote MCP plan, implemented in PAP-17087.

The two routes

Route Where Use it when
Guided URL Apps → Connect an app → Connect your own MCP server You have the server's address. Paperclip probes it and walks you through whatever it needs.
Paste a config Advanced → Paste a config A README gave you an mcpServers snippet, or the server needs headers with names Paperclip could not guess.

Both routes normalize through the same backend contract, so auth discovery, secret handling, catalog refresh and review cannot diverge between them.

Advanced → Run your own is a separate, higher-trust path for local stdio commands and is deliberately not covered here.

Don't know the address or the headers? The question-mark control beside Paste a config gives you a request you can hand to an agent: it asks the agent to consult the vendor's current documentation and reply with one paste-ready mcpServers JSON object using credential placeholders, plus notes on how to obtain each credential. Paste only the JSON block back into Paperclip; Paperclip reads the header names from it and asks you for the values, which it stores as Paperclip secrets.

What the guided URL flow does

After you paste an address and press Check link, Paperclip probes the endpoint and branches:

Endpoint says You get
Nothing needed Discovered actions, straight to review.
Needs authorization, and publishes discoverable OAuth metadata Sign in to continue — a browser sign-in at the provider.
Needs authorization, but no discoverable sign-in A prompt to add the key or headers its docs list, under Advanced authentication.
Needs a client you registered yourself A prompt for a client ID and secret. The draft connection is kept — you don't start over.
Not a valid address / private network / unreachable The specific problem and which field to change.

An unknown server is labelled Unverified server with its host shown, at every step through review, access and install. Reads are enabled for review; state-changing actions start off; newly discovered actions are quarantined until reviewed. That is the same treatment a curated connection gets.

Advanced authentication

Collapsed by default. Open it when the server's docs are specific:

  • No sign-in needed — the server is open to anyone with the address.
  • Key or token — sent as an Authorization header.
  • Custom headers — for servers that name their own headers.
  • Browser sign-in — optionally with a client ID and secret you registered yourself, for providers that require preregistration.

Every value you enter becomes a Paperclip secret. Values are write-only: they never appear in stored config JSON, logs, activity details, API responses after write, or UI readback. Only header names are shown in review and diagnostics.

Paperclip refuses to send header names it manages or that belong to the transport — Host, Cookie, Content-Length, Transfer-Encoding, hop-by-hop headers, and anything under Proxy-* or Sec-* — and rejects values containing line breaks or control characters. This is enforced in shared code (packages/shared/src/mcp-remote-headers.ts), checked at the API boundary, and re-checked in the service immediately before the header is projected onto a real request.

How sign-in gets a client

You never choose this; Paperclip resolves it and the wizard shows none of it. Recorded here for security review and diagnostics. In preference order:

  1. Deployment-preconfigured client. PAPERCLIP_TOOL_OAUTH_<PROVIDER>_CLIENT_ID / _SECRET, or the unsuffixed PAPERCLIP_TOOL_OAUTH_CLIENT_ID / _SECRET. Always wins when set.
  2. Client ID Metadata Document (CIMD). When the authorization server advertises client_id_metadata_document_supported, Paperclip presents the URL of its own published metadata document as the client_id. Nothing is registered. Requires a public HTTPS base URL (PAPERCLIP_PUBLIC_URL): the authorization server has to fetch that document server-to-server, so loopback and plain-HTTP deployments fall through to the next tier. The document is served unauthenticated at /api/tools/oauth/client-metadata and contains only this deployment's callback and the grant/response/auth methods Paperclip uses — no company, connection or secret data.
  3. Dynamic client registration (RFC 7591). When the authorization server advertises a registration_endpoint. Paperclip registers a public client (token_endpoint_auth_method: none, application_type: web, PKCE S256).
  4. Manual preregistered client. The client ID and secret you paste under Advanced authentication → Browser sign-in.

A generic connection may register (tiers 2 and 3) only after validated protected-resource and authorization-server discovery actually produced a metadata document, and only on an explicit operator connect action. An endpoint that merely returns a 401 does not earn a registration.

Client binding

Client material is bound to the authorization-server issuer, the MCP resource URL, the callback URI, and the company. If any of those change:

  • a Paperclip-minted client (CIMD or DCR) is re-registered;
  • a client you supplied yourself is not — Paperclip stops and asks you to re-enter it, because it cannot register on your behalf in a console it does not control.

Credentials are never reused across issuers or across companies.

Endpoint addresses are validated before they are used

Every OAuth endpoint address is chosen by the remote server — in discovered metadata, in a WWW-Authenticate hint, in a pasted config, or in a gallery default — and the authorization endpoint additionally becomes a top-level browser navigation. All of them are parsed by one shared validator (checkOAuthEndpointUrl in @paperclipai/shared) and must be:

  • https:. Plain http: is refused, except for a loopback host under the local-development policy (the same policy that allows private remote endpoints), and except for this deployment's own origin.
  • Free of embedded credentials. https://accounts.google.com@evil.test/… reads as the wrong site to a human, so Paperclip refuses it.
  • Free of a fragment, and a well-formed absolute URL.

javascript:, data:, file: and friends are therefore refused before they can reach window.location. The board applies the same validator to the address it receives, so an unsafe value cannot pass the API boundary and then execute at the navigation boundary. A refusal is reported as oauth_<kind>_endpoint_rejected (422) and the unsafe value is never persisted on the connection.

An address that passes is still only an address: a valid HTTPS authorization page can be a phishing page. The redirect screen names the host you are being sent to, and the Unverified server label stays visible for an endpoint with no curated definition.

Protocol conformance

  • RFC 8707 resource on authorization, token, and refresh requests, naming the canonical MCP endpoint (origin + path, no query or fragment), so the authorization server can audience-restrict the token to that server.
  • RFC 9728 protected-resource discovery, path-aware first (/.well-known/oauth-protected-resource<path>) then origin.
  • RFC 8414 authorization-server discovery for issuers with a path, in the spec's insertion form (/.well-known/oauth-authorization-server<path>) and the widely deployed OIDC suffix form (<path>/.well-known/...). A metadata document whose issuer disagrees with the issuer used to build the discovery URL is discarded.
  • RFC 9207 iss validated against the persisted expected issuer when the authorization server returns it. A mismatch refuses the code rather than exchanging it. An absent iss is tolerated — it is optional and widely omitted.
  • PKCE S256, exact redirect/state binding, and SSRF/private-network and redirect limits are unchanged from the curated path.

The discovered auth kind, issuer, and resource are persisted on the connection, so refresh, reconnect, revoke and diagnostics all use the generic path instead of falling back to authKind: none semantics.

Curated definitions remain optional

A curated definition matching a pasted endpoint is offered as a branded shortcut beside the generic form — never instead of it. A definition adds labels, logos, field validation, scoped defaults and support copy. It does not unlock a separate execution capability, and it must not create a second connection or change ownership.

The bespoke PostHog connection is the worked example: it is the polished route for most users, and PostHog is also connectable generically through this page with either a key or browser sign-in.

Verifying

Deterministic coverage lives in server/src/__tests__/generic-mcp-connection.test.ts, which stands up an in-process MCP server plus authorization server. It needs no network and no vendor credentials, and every case connects by URL without naming a gallery app. A credentialed vendor smoke (for example live PostHog OAuth) may be recorded by QA but is not required for deterministic verification.

Opt-in generic Notion live smoke

pnpm smoke:notion-generic-live exercises the generic Advanced → Paste a config route against https://mcp.notion.com/mcp. It is intentionally outside the normal unit, browser, and CI-required suites. Run it only against an already-running, browser-reachable HTTPS Paperclip instance with these bindings provided by the execution environment:

  • PAPERCLIP_E2E_BASE_URL, PAPERCLIP_E2E_EMAIL, and PAPERCLIP_DEV_LOGIN_PASSWORD for the target instance;
  • PAPERCLIP_API_URL, PAPERCLIP_API_KEY, PAPERCLIP_RUN_ID, and PAPERCLIP_TASK_ID for the control plane;
  • the approved on-demand secret binding access.notion_generic_flow_test_account, delivered by the agent secret API under its normalized key generic-flow-test-account, for the existing Notion test account.

If that account requires an emailed one-time code, also set AGENTMAIL_API_KEY and NOTION_AGENTMAIL_INBOX_ID for the already-configured forwarding inbox. The smoke loads the AgentMail SDK only after Notion presents the code challenge, accepts only a fresh authenticated Notion message, fills the code once in memory, and never records the message, address, or code.

Check the URL, health endpoint, and binding metadata without retrieving the credential value or opening a browser:

pnpm smoke:notion-generic-live -- --dry-run

The live command retrieves the credential only after the safe preflight and Paperclip login succeed. It disables trace, video, and HAR capture, takes only post-callback screenshots, enables and invokes only notion-get-self, proves notion-create-pages remains locally denied, and removes its uniquely named connection in a finally cleanup. Its summary.json and PNG files contain sanitized IDs, decisions, outcomes, and endpoint origins/paths only; they default to PAPERCLIP_RUN_SCRATCH_DIR, or to NOTION_EVIDENCE_DIR when set.

Run the credential-free harness checks with:

node --test scripts/smoke/notion-generic-live.test.mjs