mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-29 12:59:13 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Managed connections (Gmail, Google Workspace, GitHub, Asana, Slack manager) get and refresh their OAuth tokens through the Paperclip Cloud connector broker. > - When a provider refuses a refresh grant (the user revoked it, it expired, it was already rotated, or it no longer matches the profile), the broker sent `502 PROVIDER_OPERATION_FAILED`. That is the same answer it sends for a provider outage. > - Paperclip then kept the grant `active`. Each health check, catalog refresh, or catalog read failed again with a 502, and the server reported each failure as a crash. The user did not get a reconnect prompt. > - The broker now sends `409 PROVIDER_REAUTHORIZATION_REQUIRED` for a dead grant. The connector already maps any 409 to `REAUTHORIZATION_REQUIRED`. Thus the grant becomes `needs_reauthorization` and the caller gets a 422 reconnect response. > - This pull request adds the new reason to the diagnostic allowlist, pins the producer contract, and tests the reconnect path from end to end. > - The benefit is that a dead managed grant asks the user to reconnect. It is not reported as a server crash. Real provider outages keep their 502 response. ## Linked Issues or Issue Description **What happened?** A managed Paperclip Cloud connection refresh failed with `Paperclip Cloud connector rejected the request (operation=refresh, status=502, reason=PROVIDER_OPERATION_FAILED)`. The connection health-check and catalog routes wrap that error as an HTTP 502. The error handler reports every status of 500 or more, so the failure went to error monitoring. The grant stayed `active`, so the same failure can occur again on each use. **Expected behavior** If the provider refuses the refresh grant, Paperclip marks the grant `needs_reauthorization` and shows the reconnect path. Only a real provider or Cloud outage gives a 502. **Steps to reproduce** 1. Connect a managed Gmail connection through Paperclip Cloud. 2. Revoke the app access in the Google account, or let the refresh grant expire. 3. Wait until the access token needs a refresh. Then run a connection health check. 4. Before the broker change, the health check returns 502 and the server reports it. The grant stays `active`. **Additional context** The broker-side classification ships separately in Paperclip Cloud. This pull request is safe before or after that change, because the connector already maps any broker 409 to `REAUTHORIZATION_REQUIRED`. Without this pull request, the 409 still works, but diagnostics show `reason=UNKNOWN_BROKER_ERROR`. ## What Changed - `server/src/services/paperclip-cloud-connector.ts`: Add `PROVIDER_REAUTHORIZATION_REQUIRED` to the allowlist of broker reasons that errors can keep. - `server/src/services/fixtures/cloud-connector-refresh-reauthorization.json`: Add the producer HTTP contract (status 409 and the exact JSON error body). - `server/src/services/paperclip-cloud-connector.test.ts`: Test that a refresh with this contract gives `REAUTHORIZATION_REQUIRED`, status 409, the retained reason, and no refresh-token text. - `server/src/__tests__/tool-access-service.test.ts`: Test that `refreshOAuthGrantCredentials` changes a reauthorization refresh failure into a 422 `oauth_reauthorization_required` response and sets the grant to `needs_reauthorization`. - `doc/connections/GMAIL.md`: Add a troubleshooting row that tells the two refresh reasons apart. ## Verification - `cd server && npx vitest run src/services/paperclip-cloud-connector.test.ts`: 48 passed. Without the allowlist change, the new test fails (`reason=UNKNOWN_BROKER_ERROR`). - `cd server && npx vitest run src/__tests__/tool-access-service.test.ts -t "managed grant for reconnection|orders managed reconnect"`: 2 passed, against the embedded test database. - I did not run the full `pnpm -r typecheck`, `pnpm test:run`, and `pnpm build` locally. The sandbox has a 4 GB memory limit, and the server `tsc` process was stopped because it used too much memory. CI runs these checks. ## Risks - Low risk. The status mapping does not change: a 409 already meant `REAUTHORIZATION_REQUIRED`. This change only keeps one more fixed reason code in the error message. - The reason allowlist accepts fixed protocol codes only. The broker message, request data, and tokens stay out of errors and logs. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, in Claude Code agent mode with tool use (shell, file edit, code search, and a sub-agent for the code-path trace). ## 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 - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: devinfoley <139239+devinfoley@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
398 lines
20 KiB
Markdown
398 lines
20 KiB
Markdown
# Gmail connection
|
|
|
|
Paperclip connects to Google's hosted Gmail MCP server at
|
|
`https://gmailmcp.googleapis.com/mcp/v1`. Gmail authorization is separate from
|
|
Google sign-in:
|
|
|
|
- Google sign-in identifies a Paperclip ID user and requests only
|
|
`openid email profile`.
|
|
- Gmail authorization lets that user's agents search and read mail and create
|
|
drafts. It requests only `gmail.readonly` and `gmail.compose`.
|
|
|
|
Do not add Gmail scopes to the Google sign-in client. The existing Paperclip
|
|
Cloud application at `my.paperclip.app` hosts the public Gmail OAuth callback;
|
|
Paperclip ID remains identity-only. The originating Paperclip instance remains
|
|
the durable owner of the encrypted access and refresh tokens.
|
|
|
|
> Google Workspace MCP is a Developer Preview. Enroll the required Workspace
|
|
> organization and test accounts in Google's Developer Preview Program before
|
|
> relying on the service.
|
|
|
|
## Deployment layout
|
|
|
|
Use a separate Google Cloud project and OAuth web client for each environment:
|
|
|
|
| Environment | Suggested project id | OAuth client name | Authorized redirect URI |
|
|
| --- | --- | --- | --- |
|
|
| Development | `paperclip-gmail-dev` | `Paperclip Gmail Connection Dev` | Local Paperclip Cloud origin + `/v1/connector/oauth/google/callback` |
|
|
| Staging | `paperclip-gmail-staging` | `Paperclip Gmail Connection Staging` | `https://my-staging.paperclip.app/v1/connector/oauth/google/callback` |
|
|
| Production | `paperclip-gmail-prod` | `Paperclip Gmail Connection Production` | `https://my.paperclip.app/v1/connector/oauth/google/callback` |
|
|
|
|
Replace the development port if the local Paperclip Cloud application uses another
|
|
port. Do not register Tailscale, customer, or other self-hosted Paperclip
|
|
instance URLs with Google. The browser always returns to Paperclip Cloud first;
|
|
Cloud then sends an opaque, one-time claim identifier to the exact
|
|
originating instance URL that was enrolled before the flow began.
|
|
|
|
Keeping projects separate is a Paperclip release policy. It prevents a
|
|
development credential or consent-screen change from affecting production and
|
|
keeps restricted-scope Gmail verification independent of Google sign-in.
|
|
|
|
## Google Cloud setup
|
|
|
|
Repeat this procedure in development, staging, and production. Complete and
|
|
test development first, then staging. Do not enable production authorization
|
|
until Google verification and Paperclip Security review are complete.
|
|
|
|
### 1. Create the project
|
|
|
|
1. Open [Google Cloud project creation](https://console.cloud.google.com/projectcreate).
|
|
2. Select the Paperclip Cloud organization and billing account.
|
|
3. Create the environment-specific project from the table above.
|
|
4. Limit Owner and Editor access to the smallest operator group.
|
|
5. Add a monitored engineering or security contact.
|
|
6. Record the project id in the private environment runbook. Do not put a
|
|
client secret in the runbook or repository.
|
|
|
|
### 2. Enable Gmail and Gmail MCP
|
|
|
|
In **APIs & Services → Library**, enable:
|
|
|
|
- Gmail API: `gmail.googleapis.com`
|
|
- Gmail MCP API: `gmailmcp.googleapis.com`
|
|
|
|
The equivalent command is:
|
|
|
|
```sh
|
|
gcloud services enable \
|
|
gmail.googleapis.com \
|
|
gmailmcp.googleapis.com \
|
|
--project=PROJECT_ID
|
|
```
|
|
|
|
Do not enable Drive, Docs, Sheets, Calendar, Chat, or People for the Gmail-only
|
|
release.
|
|
|
|
### 3. Configure branding
|
|
|
|
Open **Google Auth Platform → Branding**. Set:
|
|
|
|
- App name: `Paperclip`
|
|
- User support email: a monitored support address
|
|
- Logo: the approved Paperclip logo
|
|
- Homepage: the public Paperclip product page
|
|
- Privacy policy: the public policy that describes Gmail data handling
|
|
- Terms of service: the public Paperclip terms
|
|
- Authorized domain: `paperclip.app`
|
|
- Developer contact: a monitored security or engineering group
|
|
|
|
The homepage, privacy policy, and terms must be live on the verified domain
|
|
before production verification. The privacy policy must explain that the
|
|
originating Paperclip instance stores Gmail credentials and that Paperclip Cloud
|
|
performs bounded OAuth exchange, refresh, and provider-supported revocation
|
|
without durable plaintext token storage.
|
|
|
|
### 4. Configure the audience
|
|
|
|
Open **Google Auth Platform → Audience**.
|
|
|
|
- Development: select **External**, keep the app in **Testing**, and add only
|
|
developer test accounts.
|
|
- Staging: select **External**, keep the app in **Testing**, and add only QA,
|
|
security-review, and verification accounts.
|
|
- Production: select **External** and move to **In production** only after the
|
|
required restricted-scope verification and security work is complete.
|
|
|
|
Google limits an external testing app to 100 test users. For non-basic scopes,
|
|
testing grants and their offline refresh tokens can expire after seven days.
|
|
Treat that expiry as expected test behavior.
|
|
|
|
### 5. Add the exact scopes
|
|
|
|
Open **Google Auth Platform → Data Access → Add or remove scopes → Manually add
|
|
scopes** and add only:
|
|
|
|
```text
|
|
https://www.googleapis.com/auth/gmail.readonly
|
|
https://www.googleapis.com/auth/gmail.compose
|
|
```
|
|
|
|
Do not add `mail.google.com`, `gmail.modify`, `gmail.send`, Drive, Calendar, or
|
|
profile/sign-in scopes. Gmail read and compose are restricted scopes. Public
|
|
production use therefore requires Google's restricted-scope verification and
|
|
may require an independent security assessment for server-side handling.
|
|
|
|
### 6. Create the OAuth client
|
|
|
|
Open **Google Auth Platform → Clients → Create Client**:
|
|
|
|
1. Select **Web application**.
|
|
2. Enter the environment-specific client name from the table above.
|
|
3. Add exactly the matching authorized redirect URI.
|
|
4. Leave **Authorized JavaScript origins** empty. This is a server-side flow.
|
|
5. Create the client.
|
|
6. Copy the client id and newly displayed secret directly into the matching
|
|
deployment secret manager.
|
|
|
|
Never paste either credential into an issue, document, chat, screenshot,
|
|
committed `.env`, build log, or browser-visible configuration. Step 7 lists the
|
|
deployment variables that receive them.
|
|
|
|
### 7. Configure the Paperclip Cloud broker deployment
|
|
|
|
Set these on the existing Paperclip Cloud application that owns the redirect URI above. This is
|
|
the broker half of the configuration; the originating Paperclip instance is
|
|
configured separately under [Configure each originating Paperclip
|
|
instance](#configure-each-originating-paperclip-instance).
|
|
|
|
| Variable | Development | Staging | Production |
|
|
| --- | --- | --- | --- |
|
|
| `CLOUD_HARNESS_CONNECTOR_GOOGLE_GMAIL_CLIENT_ID` | Dev client id | Staging client id | Production client id |
|
|
| `CLOUD_HARNESS_CONNECTOR_GOOGLE_GMAIL_CLIENT_SECRET_REF` | Dev secret-manager ref | Staging secret-manager ref | Production secret-manager ref |
|
|
| Fixed callback | Local Cloud origin + `/v1/connector/oauth/google/callback` | `https://my-staging.paperclip.app/v1/connector/oauth/google/callback` | `https://my.paperclip.app/v1/connector/oauth/google/callback` |
|
|
| `CLOUD_HARNESS_CONNECTOR_GOOGLE_ENABLED_PROFILES` | `gmail.read` during the first test | Add reviewed staging profiles | Add only approved production profiles |
|
|
| `CLOUD_HARNESS_CONNECTOR_ENVIRONMENT` | `development` | `staging` | `production` |
|
|
|
|
The client id and secret reference must both be present before a profile can be
|
|
used. The callback is derived from Paperclip Cloud's configured customer origin
|
|
and the provider's fixed in-code path; it is not accepted from a request or an
|
|
environment override. `CLOUD_HARNESS_CONNECTOR_GOOGLE_ENABLED_PROFILES` is the
|
|
profile kill switch. An omitted profile is advertised as disabled and every
|
|
authorization, refresh, and revocation request for it fails closed.
|
|
|
|
Set `CLOUD_HARNESS_CONNECTOR_ENVIRONMENT` explicitly in every environment. Every signed
|
|
connector request declares its own environment, and the broker accepts the
|
|
request only when that value matches both this deployment's environment and the
|
|
environment recorded on the enrolled instance. That three-way match is what
|
|
makes a leaked staging instance key inert against production, so it must equal
|
|
the instance's `PAPERCLIP_CLOUD_CONNECTOR_ENVIRONMENT`.
|
|
|
|
Paperclip Cloud derives a safe development, staging, or production fallback
|
|
from its own customer origin, but the explicit value makes environment
|
|
isolation reviewable and avoids a custom hostname being treated as development.
|
|
The value is never derived from `NODE_ENV`.
|
|
|
|
## Connector request requirements
|
|
|
|
The Gmail authorization request must use:
|
|
|
|
- the Gmail connector client, not the Google sign-in client;
|
|
- `/v1/connector/oauth/google/callback` on Paperclip Cloud;
|
|
- `response_type=code`;
|
|
- the two exact Gmail scopes above;
|
|
- `access_type=offline`;
|
|
- `prompt=consent` for every connect and explicit reconnect;
|
|
- a random, short-lived, single-use state value; and
|
|
- PKCE S256.
|
|
|
|
Do not send `include_granted_scopes`. After token exchange, compare the granted
|
|
scope set with the two required scopes. If either is missing, leave that
|
|
personal connection grant inactive and let the user retry deliberately.
|
|
|
|
No access token, refresh token, Google authorization code, client secret, or
|
|
token fragment may appear in a browser URL. The browser return from Paperclip
|
|
Cloud to the originating instance contains only an opaque one-time claim id and
|
|
the instance's local state.
|
|
|
|
## Token custody and instance enrollment
|
|
|
|
The expected flow is:
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
actor U as User browser
|
|
participant P as Originating Paperclip instance
|
|
participant C as Paperclip Cloud connector
|
|
participant G as Google OAuth
|
|
participant V as Instance encrypted vault
|
|
|
|
U->>P: Apps → Gmail → Connect
|
|
P->>C: Signed, environment-bound authorization session
|
|
C-->>U: Existing Cloud login and destination confirmation
|
|
C-->>U: Google authorization URL with state and PKCE
|
|
U->>G: Grant Gmail read and draft access
|
|
G-->>C: Authorization code at the fixed Cloud callback
|
|
C->>G: Exchange with the Gmail client secret
|
|
C-->>U: Opaque one-time claim for the enrolled instance
|
|
U->>P: Return to exact enrolled instance URL
|
|
P->>C: Signed one-time claim
|
|
C-->>P: Instance-encrypted token response
|
|
P->>V: Encrypt tokens and bind them to the chosen user or organization grant
|
|
```
|
|
|
|
Before an instance can create a session:
|
|
|
|
1. The instance generates an Ed25519 signing key and a separate X25519 seal
|
|
key. Both private keys stay local; Ed25519 authenticates requests and
|
|
X25519 lets Paperclip Cloud encrypt token responses that only the instance can
|
|
open.
|
|
2. An instance administrator signs in to Paperclip Cloud through its existing
|
|
Paperclip ID OIDC login and enrolls the instance. Enrollment is
|
|
instance-global: ordinary company membership cannot start it, and the
|
|
initiating administrator must complete the return callback.
|
|
3. Paperclip Cloud binds the account, opaque instance id, both public keys,
|
|
deployment environment, and exact allowed browser return origins.
|
|
4. On authenticated private instances, the setup request supplies its verified
|
|
same-origin HTTPS address and enrollment binds it automatically. This makes a
|
|
Tailscale HTTPS setup config-free while still rejecting a bare or mismatched
|
|
`Host` header. Loopback HTTP is development-only; other plaintext origins are
|
|
rejected.
|
|
5. Create, claim, refresh, and supported revoke requests are signed, audience-bound,
|
|
timestamped, and protected by a one-time `jti` replay cache.
|
|
|
|
Paperclip Cloud may retain instance-encrypted initial-token ciphertext for at most
|
|
five minutes. It binds the first claim to a stable local redemption id and only
|
|
returns the same ciphertext to that redemption id during the retry window. It
|
|
deletes the ciphertext on expiry and excludes it from long-term backups. Refresh
|
|
and supported revoke operations handle plaintext only in memory for one bounded
|
|
request.
|
|
|
|
Removing one managed Google profile revokes only the local Paperclip grant.
|
|
Paperclip does not call Google's token revocation endpoint for that action.
|
|
Google treats revocation as client-wide for the user, so a provider-side revoke
|
|
could also invalidate the user's other managed Gmail, Drive, and Calendar
|
|
profiles. A future provider-level disconnect must present that all-profiles
|
|
effect explicitly.
|
|
|
|
### Configure each originating Paperclip instance
|
|
|
|
Generate the two long-lived instance keys once. PEM-encoded PKCS#8 keys work
|
|
directly with Paperclip:
|
|
|
|
```sh
|
|
openssl genpkey -algorithm ED25519 -out paperclip-cloud-signing.pem
|
|
openssl genpkey -algorithm X25519 -out paperclip-cloud-sealing.pem
|
|
openssl pkey -in paperclip-cloud-signing.pem -pubout -out paperclip-cloud-signing.pub.pem
|
|
openssl pkey -in paperclip-cloud-sealing.pem -pubout -out paperclip-cloud-sealing.pub.pem
|
|
```
|
|
|
|
Keep both private files in the instance secret manager. Enroll only the public
|
|
files with Paperclip Cloud, together with the instance id, the matching environment,
|
|
and every exact browser return origin. Then configure the originating Paperclip
|
|
deployment:
|
|
|
|
| Variable | Development | Staging | Production |
|
|
| --- | --- | --- | --- |
|
|
| `PAPERCLIP_CLOUD_CONNECTOR_BASE_URL` | Local Paperclip Cloud URL | `https://my-staging.paperclip.app` | `https://my.paperclip.app` |
|
|
| `PAPERCLIP_CLOUD_CONNECTOR_ENVIRONMENT` | `development` | `staging` | `production` |
|
|
| `PAPERCLIP_CLOUD_CONNECTOR_INSTANCE_ID` | Enrolled development instance id | Enrolled staging instance id | Enrolled production instance id |
|
|
| `PAPERCLIP_CLOUD_CONNECTOR_SIGN_PRIVATE_KEY` | Development Ed25519 private key | Staging Ed25519 private key | Production Ed25519 private key |
|
|
| `PAPERCLIP_CLOUD_CONNECTOR_SEAL_PRIVATE_KEY` | Development X25519 private key | Staging X25519 private key | Production X25519 private key |
|
|
|
|
Use separate keypairs and instance enrollments across environments. The
|
|
connector is unavailable unless all four identity/key variables are present.
|
|
HTTP is accepted only for a loopback Paperclip Cloud URL; staging and production
|
|
must use HTTPS.
|
|
|
|
Cloud-hosted stacks receive these values automatically through the existing
|
|
per-stack secret-reference delivery path. Self-hosted instances normally use
|
|
the Apps enrollment action instead of running the OpenSSL commands manually;
|
|
it generates the keys and writes them to the ignored instance secret directory
|
|
with owner-only permissions.
|
|
|
|
`PAPERCLIP_ID_CONNECTOR_*` values are not aliases for this protocol. Paperclip
|
|
ID used different endpoints, signing metadata, envelope purposes, and Google
|
|
client credentials. An instance with only those legacy values fails with
|
|
`CONNECTOR_MIGRATION_REQUIRED`. Enroll it with Paperclip Cloud and reconnect
|
|
each legacy Google grant. Cloud-hosted fleets must deliver the new enrollment
|
|
keys before they deploy a binary that enables the Cloud connector.
|
|
|
|
## Paperclip access defaults
|
|
|
|
Gmail uses the same credential ownership choice as the rest of the Apps setup:
|
|
|
|
- **Just me** stores the Gmail credential on the connecting user's grant.
|
|
- **Any human in the organization** stores it on the default organization grant
|
|
so a deliberately shared mailbox or Workspace account can back
|
|
organization-wide use.
|
|
- The disclosure states that Gmail access can search/read mail and create
|
|
drafts. Sending mail is not enabled.
|
|
- A user grant does not automatically authorize an agent. The user must also
|
|
install the connection for that agent, select an access profile, and grant
|
|
standing delegation before autonomous use.
|
|
- Read, search, get, and list tools may be enabled after explicit profile
|
|
review.
|
|
- Draft creation and label changes require **Ask first**.
|
|
- Trash, spam, destructive label changes, newly discovered tools, nested
|
|
execution, and any future send tool remain blocked until separately reviewed.
|
|
|
|
## Broker rejection diagnostics
|
|
|
|
A rejected connector request includes its operation, HTTP status, and an
|
|
allowlisted broker reason in the server error. For example,
|
|
`RETURN_ORIGIN_NOT_ENROLLED` means the callback origin is not enrolled for that
|
|
instance. Compare the current page/configured origin with the instance's enrolled
|
|
origins; do not bypass origin checks or copy credentials to a different instance.
|
|
|
|
Unknown codes, malformed bodies, oversized responses, and stalled diagnostic
|
|
reads produce `UNKNOWN_BROKER_ERROR`. This is not proof of any specific rejection
|
|
cause. Diagnostics read at most 4 KiB within 500 ms and retain no raw response
|
|
messages, URLs, tokens, authorization state, or instance/customer identifiers.
|
|
The existing error code, status, authorization, and retry behavior are unchanged.
|
|
|
|
## Verification checklist
|
|
|
|
### Development
|
|
|
|
1. Enable the connector only in development.
|
|
2. Confirm the broker's `CLOUD_HARNESS_CONNECTOR_ENVIRONMENT` and the instance's
|
|
`PAPERCLIP_CLOUD_CONNECTOR_ENVIRONMENT` both read `development`. A mismatch
|
|
fails every signed request with an environment error before Google is ever
|
|
contacted, which looks nothing like a Google misconfiguration.
|
|
3. Use an isolated Gmail test mailbox.
|
|
4. Connect from localhost and one explicitly enrolled Tailscale HTTPS origin.
|
|
5. From the board Test panel, run `list_labels` and a bounded
|
|
`search_threads` query.
|
|
6. Install the reviewed profile on one test agent and repeat one read-only call
|
|
in a fresh agent run.
|
|
7. Create a draft through an Ask-first approval and verify no send action is
|
|
exposed.
|
|
8. Force access-token expiry and verify refresh changes only the originating
|
|
instance's encrypted secret version.
|
|
9. Revoke the grant and verify the next call fails closed.
|
|
10. Confirm sanitized logs, activity, API payloads, agent context, and browser
|
|
history contain no credential or authorization code.
|
|
|
|
### Staging
|
|
|
|
Repeat development verification, then add negative tests for replayed state,
|
|
wrong origin, wrong instance, wrong company, wrong user, wrong environment,
|
|
expired claim, missing scope, inactive membership, connector outage, and the
|
|
seven-day testing-token expiry.
|
|
|
|
### Production
|
|
|
|
1. Complete Developer Preview enrollment, restricted-scope verification, any
|
|
required security assessment, and Paperclip Security review.
|
|
2. Configure only the production project credentials in production secrets.
|
|
3. Start with an internal allowlist and read tools.
|
|
4. Enable Ask-first draft and label tools only after production telemetry is
|
|
clean.
|
|
5. Keep destructive and send-email capabilities blocked.
|
|
6. Keep the environment-specific connector kill switch available. When it is
|
|
off, new authorization and refresh fail with an actionable error and never
|
|
fall back to Google sign-in or another user's grant.
|
|
|
|
## Troubleshooting
|
|
|
|
| Symptom | Check |
|
|
| --- | --- |
|
|
| `redirect_uri_mismatch` | The client contains the exact environment callback, including scheme, host, port, path, and no extra slash. |
|
|
| Test user cannot consent | The account is listed under the environment project's Audience test users and is enrolled in Workspace Developer Preview. |
|
|
| Refresh fails after seven days | The external app is still in Testing. Reauthorize the test user; do not treat this as token-rotation failure. |
|
|
| One required capability is missing | Inspect the returned granted scope set. Keep the grant inactive if either exact required scope is absent. |
|
|
| Local or Tailscale return is rejected | Enroll the exact origin on Paperclip Cloud. Only loopback HTTP is allowed; Tailscale must use HTTPS. |
|
|
| Every signed request fails on environment | `CLOUD_HARNESS_CONNECTOR_ENVIRONMENT`, the enrollment record, and `PAPERCLIP_CLOUD_CONNECTOR_ENVIRONMENT` must agree. |
|
|
| The managed method is unavailable | Confirm the exact profile is in `CLOUD_HARNESS_CONNECTOR_GOOGLE_ENABLED_PROFILES` and its client id and secret reference are configured. |
|
|
| Login starts asking for Gmail | Stop the rollout. The login and Gmail clients or route namespaces have been mixed. |
|
|
| Connector is unavailable | Keep the grant in `needs_reauthorization` or an actionable unavailable state. Never use a login token or another environment's client. |
|
|
| Refresh reports `reason=PROVIDER_REAUTHORIZATION_REQUIRED` | The provider revoked, expired, or already rotated the grant, or the grant no longer matches the profile. Paperclip marks it `needs_reauthorization`; the user reconnects. `reason=PROVIDER_OPERATION_FAILED` is a provider or Cloud outage; retry later. |
|
|
|
|
## References
|
|
|
|
- [Configure Google Workspace MCP servers](https://developers.google.com/workspace/guides/configure-mcp-servers)
|
|
- [OAuth 2.0 for web server applications](https://developers.google.com/identity/protocols/oauth2/web-server)
|
|
- [Google OAuth 2.0 policies](https://developers.google.com/identity/protocols/oauth2/policies)
|
|
- [Choose Gmail API scopes](https://developers.google.com/workspace/gmail/api/auth/scopes)
|
|
- [Google Workspace API user data and developer policy](https://developers.google.com/workspace/workspace-api-user-data-developer-policy)
|