@zackbart/connecta 0.33.0 → 0.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +100 -128
  3. package/SECURITY.md +1 -1
  4. package/bin/version.mjs +1 -1
  5. package/dist/activity.d.ts +3 -0
  6. package/dist/branding.d.ts +7 -0
  7. package/dist/branding.js +76 -9
  8. package/dist/catalog-drift.d.ts +30 -26
  9. package/dist/catalog-drift.js +169 -84
  10. package/dist/catalog-service.js +16 -2
  11. package/dist/config-value-sources.js +6 -1
  12. package/dist/config.d.ts +13 -2
  13. package/dist/config.js +36 -2
  14. package/dist/connector-caller.js +2 -1
  15. package/dist/connectors/api-connector.d.ts +3 -0
  16. package/dist/connectors/api-connector.js +5 -1
  17. package/dist/connectors/option-shapes.js +1 -1
  18. package/dist/connectors/remote-mcp.d.ts +2 -0
  19. package/dist/connectors/remote-mcp.js +5 -1
  20. package/dist/describe-config.d.ts +5 -1
  21. package/dist/describe-config.js +2 -0
  22. package/dist/execute.js +1 -11
  23. package/dist/fix-prompt.js +2 -2
  24. package/dist/index.js +1 -0
  25. package/dist/invocation.js +3 -2
  26. package/dist/membership.d.ts +2 -0
  27. package/dist/meta-tools.js +3 -2
  28. package/dist/oauth-callback-outcome.js +2 -2
  29. package/dist/operator-ui/agents.d.ts +1 -1
  30. package/dist/operator-ui/agents.js +16 -16
  31. package/dist/operator-ui/config-summary.js +565 -0
  32. package/dist/operator-ui/connection-map.js +376 -0
  33. package/dist/operator-ui/endpoints.js +45 -16
  34. package/dist/operator-ui/fix-prompts.js +1 -1
  35. package/dist/operator-ui/generated.js +4 -4
  36. package/dist/operator-ui/health.js +52 -0
  37. package/dist/operator-ui/member-page.js +13 -28
  38. package/dist/operator-ui/overview.js +123 -0
  39. package/dist/operator-ui/view.d.ts +26 -22
  40. package/dist/operator-ui/view.js +130 -96
  41. package/dist/page-styles.js +5 -3
  42. package/dist/provider.d.ts +6 -1
  43. package/dist/provider.js +18 -1
  44. package/dist/providers/_shared/google/workspace.d.ts +9 -4
  45. package/dist/providers/_shared/google/workspace.js +7 -3
  46. package/dist/providers/basecamp/index.d.ts +2 -0
  47. package/dist/providers/breeze/index.d.ts +2 -0
  48. package/dist/providers/ccb/index.d.ts +2 -0
  49. package/dist/providers/ccb/index.js +2 -2
  50. package/dist/providers/cloudflare/index.d.ts +2 -0
  51. package/dist/providers/cloudflare/index.js +5 -1
  52. package/dist/providers/cloudflare/openapi.generated.js +3296 -3206
  53. package/dist/providers/cloudflare/rest.js +19 -0
  54. package/dist/providers/cloudflare/skill.generated.js +2 -2
  55. package/dist/providers/cloudflare/value-safety.js +51 -18
  56. package/dist/providers/docs/index.js +1 -1
  57. package/dist/providers/docs/skill.generated.js +2 -2
  58. package/dist/providers/drive/index.js +1 -1
  59. package/dist/providers/drive/skill.generated.js +2 -2
  60. package/dist/providers/forms/index.js +1 -1
  61. package/dist/providers/forms/skill.generated.js +2 -2
  62. package/dist/providers/gmail/index.js +1 -1
  63. package/dist/providers/gmail/skill.generated.js +2 -2
  64. package/dist/providers/intercom/index.d.ts +24 -0
  65. package/dist/providers/intercom/index.js +121 -0
  66. package/dist/providers/intercom/skill.generated.js +8 -0
  67. package/dist/providers/linear/index.js +204 -19
  68. package/dist/providers/linear/skill.generated.js +2 -2
  69. package/dist/providers/mixpanel/index.d.ts +2 -0
  70. package/dist/providers/mixpanel/index.js +21 -17
  71. package/dist/providers/mixpanel/skill.generated.js +3 -3
  72. package/dist/providers/notion/index.d.ts +2 -0
  73. package/dist/providers/notion/index.js +1 -1
  74. package/dist/providers/notion/skill.generated.js +1 -1
  75. package/dist/providers/overflow/index.d.ts +2 -0
  76. package/dist/providers/overflow/index.js +1 -1
  77. package/dist/providers/overflow/skill.generated.js +2 -2
  78. package/dist/providers/planning-center/index.d.ts +2 -0
  79. package/dist/providers/planning-center/index.js +1 -1
  80. package/dist/providers/planning-center/skill.generated.js +4 -4
  81. package/dist/providers/revenuecat/index.d.ts +5 -3
  82. package/dist/providers/revenuecat/index.js +147 -7
  83. package/dist/providers/revenuecat/skill.generated.js +3 -3
  84. package/dist/providers/sheets/index.js +1 -1
  85. package/dist/providers/sheets/skill.generated.js +2 -2
  86. package/dist/providers/slides/index.js +1 -1
  87. package/dist/providers/slides/skill.generated.js +2 -2
  88. package/dist/providers/stripe/index.d.ts +2 -0
  89. package/dist/providers/stripe/index.js +50 -30
  90. package/dist/providers/stripe/skill.generated.js +2 -1
  91. package/dist/providers/tithely/index.d.ts +2 -0
  92. package/dist/providers/tithely/index.js +1 -1
  93. package/dist/providers/vercel/index.d.ts +2 -0
  94. package/dist/providers/vercel/index.js +2 -2
  95. package/dist/providers/vercel/openapi.generated.js +391 -374
  96. package/dist/providers/vercel/skill.generated.js +2 -2
  97. package/dist/providers/vercel/value-safety.js +14 -4
  98. package/dist/registry.d.ts +10 -14
  99. package/dist/registry.js +77 -62
  100. package/dist/routes/mcp.js +20 -13
  101. package/dist/routes/oauth.js +2 -2
  102. package/dist/routes/ui-data.js +8 -2
  103. package/dist/skills.js +18 -2
  104. package/dist/storage/sql.js +5 -1
  105. package/dist/types.d.ts +36 -11
  106. package/dist/ui.js +9 -4
  107. package/dist/usage-guide.js +1 -1
  108. package/dist/version.d.ts +1 -1
  109. package/dist/version.js +1 -1
  110. package/documentation/README.md +2 -2
  111. package/documentation/architecture.md +66 -18
  112. package/documentation/auth.md +183 -67
  113. package/documentation/code-mode.md +30 -10
  114. package/documentation/deploying.md +5 -1
  115. package/documentation/integrating.md +64 -7
  116. package/documentation/meta-tools.md +38 -24
  117. package/documentation/migration-0.33.md +21 -13
  118. package/documentation/migration-0.34.md +67 -0
  119. package/documentation/operating.md +1 -1
  120. package/documentation/operator-tests.md +52 -20
  121. package/documentation/operator-ui.md +341 -104
  122. package/documentation/provider-auth-migration.md +22 -0
  123. package/examples/worker/AGENTS.md +15 -9
  124. package/examples/worker/README.md +41 -14
  125. package/examples/worker/src/connecta.config.ts +5 -4
  126. package/examples/worker/wrangler.jsonc +1 -1
  127. package/package.json +23 -19
  128. package/templates/node/AGENTS.md +13 -7
  129. package/templates/node/README.md +14 -11
  130. package/templates/node/package.json +1 -1
  131. package/templates/node/src/connecta.config.ts +2 -2
@@ -30,6 +30,16 @@ const READ_CAP_MS = 60_000;
30
30
  * (`CLOUDFLARE_VALUE_SAFETY`).
31
31
  */
32
32
  const READ_POSTS = [
33
+ [
34
+ "POST",
35
+ "/accounts/{account_id}/resource-library/applications/lookup",
36
+ "Looks up application names in a sentence; changes no application or rule.",
37
+ ],
38
+ [
39
+ "POST",
40
+ "/accounts/{account_id}/data-security/posture/finding_types/validate",
41
+ "Checks a candidate finding definition without creating or reserving it.",
42
+ ],
33
43
  ["POST", "/accounts/{account_id}/analytics/query/{dataset}/summary", "Analytics query; aggregates only."],
34
44
  ["POST", "/accounts/{account_id}/analytics/query/{dataset}/timeseries", "Analytics query; aggregates only."],
35
45
  ["POST", "/accounts/{account_id}/analytics/query/{dataset}/top-n", "Analytics query; aggregates only."],
@@ -145,6 +155,15 @@ export const PIN_SAFE_UNSCOPED = new Set([
145
155
  "POST /zones",
146
156
  "GET /memberships",
147
157
  "GET /ips",
158
+ // CWS category definitions and filter-builder schema are global catalogs,
159
+ // not account policy, credential, or deployment records.
160
+ "GET /cws/categories/apis",
161
+ "GET /cws/categories/applications",
162
+ "GET /cws/categories/applications/{application_key}/versions",
163
+ "GET /cws/categories/applications/{application_key}/versions/latest",
164
+ "GET /cws/categories/applications/{application_key}/versions/{version_tag}",
165
+ "GET /cws/categories/operations",
166
+ "GET /cws/filter-schema",
148
167
  "POST /graphql",
149
168
  ]);
150
169
  /** Why each family of unscoped operations is refused under a pin; `test` checks every one is covered. */
@@ -3,10 +3,10 @@ export const skill = {
3
3
  "content": "- Resolve account and zone ids before acting; never guess one. Ids are 32-character hex strings, and a zone is found by its domain name. Defaults and pins route a connector; Cloudflare's permissions still decide what the credential may do.\n- Read state before replacing, rolling back, or purging. For DNS, PATCH changes the fields you send and PUT replaces the whole record, so read the record first and send its type, name, and content with the change. Prefer a targeted cache purge (files, tags, hosts, or prefixes) over purging everything.\n- Cloudflare reports failures in `errors` with numeric codes and returns `success: false`; read the message instead of retrying the same call.\n- Honor rate-limit waits. Cloudflare documents 1,200 requests per five minutes per user, counted across the dashboard and every token; Connecta's per-runtime window approximates it, and a limited user is blocked for the rest of the window.\n- Treat every create, update, delete, purge, deploy, and upload as a write. For guest calls, classification, and routing, fetch `skills({ name: \"usage\" })`.\n",
4
4
  "instructionsHeading": "Account instructions",
5
5
  "fragments": {
6
- "oauth": "\n\n- The catalog is Cloudflare's own `search` and `execute`. `search` runs code against Cloudflare's OpenAPI document; `execute` runs code that may call any Cloudflare API endpoint the OAuth grant allows.\n- Connecta classifies `execute` as a write, even for a program that only reads, because one program can mix GET, POST, PUT, PATCH, and DELETE and no schema proves otherwise. A read-only endpoint therefore gets only `search` here. Deployments that need API reads from a read-only endpoint configure an API-token connector, whose `cloudflare_api_read` and named reads are reads.\n- Use `search` to find the exact method, path, and fields before writing an execute program. Do not guess an endpoint from product naming.\n- Keep returned values small. Filter and project inside the Cloudflare program, then reduce again inside Connecta's `execute_code` when several calls must be joined.\n- This connection has none of the REST connector's tools: no `cloudflare_api_*` validation against a pinned index, no `graphql_query`, no `cloudflare_api_upload`, and no account or zone pin. OAuth permissions are the boundary.\n",
6
+ "oauth": "\n\n- The catalog is Cloudflare's own `docs`, `search`, and `execute`. `docs` searches Cloudflare developer documentation. `search` runs code against Cloudflare's OpenAPI document; `execute` runs code that may call any Cloudflare API endpoint the OAuth grant allows.\n- Connecta classifies `execute` as a write, even for a program that only reads, because one program can mix GET, POST, PUT, PATCH, and DELETE and no schema proves otherwise. A read-only endpoint therefore gets `docs` and `search` here. Deployments that need API reads from a read-only endpoint configure an API-token connector, whose `cloudflare_api_read` and named reads are reads.\n- Use `search` to find the exact method, path, and fields before writing an execute program. Do not guess an endpoint from product naming.\n- Keep returned values small. Filter and project inside the Cloudflare program, then reduce again inside Connecta's `execute_code` when several calls must be joined.\n- This connection has none of the REST connector's tools: no `cloudflare_api_*` validation against a pinned index, no `graphql_query`, no `cloudflare_api_upload`, and no account or zone pin. OAuth permissions are the boundary.\n",
7
7
  "oauth_tail": "- An `auth_required` failure means this connector's Cloudflare authorization is missing or expired: run `authorize_connector` for this connector id, then retry the same call unchanged.\n",
8
8
  "global_warning": "\n\nThis connector holds a Global API Key. It acts as its Cloudflare user with every permission that user has, in every account and zone the user belongs to, and nothing on Cloudflare's side narrows it. A mistaken write can reach production domains, billing, and members across all of them. Prefer reads, state the account and zone of every write, and stop when a request is ambiguous. Writes under `/user` and `/memberships` are refused. R2 accepts API tokens only, so R2 operations fail with this key.\n",
9
- "key": "\n\n- This is Connecta's REST connector to the Cloudflare v4 API, holding one operator-managed credential. `cloudflare_api_search` finds API operations in a pinned index (not records) and names the tool that calls each; `cloudflare_api_details` returns one operation's parameters and body with types, enums, and required names. Neither sends a request.\n- `cloudflare_api_read` calls GET operations plus reviewed read-only POSTs: analytics queries, Analytics Engine and SQL API queries, Log Explorer SQL, Vectorize queries, KV bulk get, telemetry queries, and validation checks. `cloudflare_api_write` calls every other POST, PUT, PATCH, and DELETE with `method` and `path` stated. D1 `…/query` and `…/raw`, Durable Object queries, Workers AI `…/ai/run`, AI Search and AutoRAG search (query rewriting runs billed models), monetization checks, and flag evaluation are writes: they can change data, cost money, or activate something.\n- Paths are concrete: substitute ids into the template search returned (`/zones/<zone id>/dns_records/<record id>`). Pass `query` and `body` as JSON named as details lists them; text operations (Analytics Engine SQL, Log Explorer SQL, Worker scripts) take a string body, and Vectorize inserts a list of objects sent as NDJSON. R2 calls accept `headers` with `cf-r2-jurisdiction` or `cf-r2-storage-class`. Encode a `/` inside an R2 object key or KV key name as `%2F`.\n- Every call is checked against the pinned index before it is sent. An unknown path returns the nearest operations; an unknown or missing parameter returns `validation.issues` with the accepted names. Nothing reached Cloudflare, so fix the call from that answer.\n- Under an account pin, each uncached zone-ownership check also spends the connector's call budget. Checks stay capped at three per call and 60 per five minutes per connector; an exhausted budget stops the check and the requested operation.\n- Results arrive as `{ status, data, page? }` with Cloudflare's envelope unwrapped: `data` is the `result`. On a list, pass `page.next` as the `page.param` query argument (`page` for numbered lists, `cursor` for KV, R2, and other cursor lists). Lists belong inside `execute_code`; pass `select` (dot paths such as `id` and `name`) to keep only the fields the question needs.\n- `list_zones` and `list_accounts` resolve names to ids; `verify_credential` separates a bad credential from a missing permission. `graphql_query` runs read-only GraphQL Analytics queries (mutations are refused before sending); filter every `zones` or `accounts` field by `zoneTag` or `accountTag`. A pinned connector sends one query per document, with every fragment it defines used.\n- Multipart, binary, and file bodies go through `cloudflare_api_upload`: Worker scripts and versions, KV values, R2 objects, Pages deployments, Images, Stream, DNS zone-file import, and Snippets. It takes `textBody`, `base64Body`, or multipart `parts`, never a local file.\n- Refused by design: every operation that mints, rotates, or hands back a credential (API tokens, Access service tokens, R2 temporary credentials, tunnel and Mesh node tokens, signing keys, registry credentials, direct-upload URLs, deploy hooks, preview and participant tokens), payment client secrets, URL Scanner captures (results, HAR, DOM, raw responses: they hold the scanned session's cookies and headers; use the scan search for verdicts), and GETs that act (an email unsubscribe, a PagerDuty connect). Stored and embedded secrets (Turnstile, TSIG, webhook and identity-provider secrets, Stream keys, Logpush destination credentials, probe and custom-provider headers, BGP keys, Zaraz variables, Hyperdrive database passwords, RealtimeKit storage credentials, transform-rule header values) and every environment or plain-text binding value (Pages, Containers, Workers, Builds; names and types stay) come back as `\"[redacted]\"` on every method. Notification, RealtimeKit, and data security webhook destinations keep only their scheme and host. Workers KV values are application data and come back as stored. Reviewed operations, and operations that take credentials, report Cloudflare's error codes without its error text. Worker script content is readable: it is code, and secret bindings are not part of it.\n- An `auth_required` failure means the credential is missing, revoked, or malformed: an operator must replace it in this connection in the operator UI, and `authorize_connector` returns that handoff. `provider_permission_denied` means the credential works but lacks this permission or resource, or the call is outside the pin; ask rather than retry.\n- Embedded URL safety uses reviewed paths plus global signing parameters. Generic credential-named query parameters (`code`, `key`, `token`, `sig`, etc.) and credential-looking fragments withhold the destination's entire query/fragment only in Workers build `lines`, Pages log `data.line`, and telemetry `$metadata.message`/`error` and raw string/object `source` payloads in both `events.events` and `invocations.*` on the reviewed log operations and vendor error text. Ordinary `code`/`key` links can therefore be false positives in these diagnostics. DNS TXT content, KV values, Access/Waiting Room custom HTML and other operator content keep those ordinary URL parameters byte for byte. Literal AWS SigV4/Google V4 `X-Amz-*`/`X-Goog-*` Signature, Credential and Security-Token values are withheld in every decoded string before projection. A withheld value is not the original: do not write it back. KV bulk writes also inspect decoded `value` fields with `base64: true` (up to Cloudflare's 25 MiB decoded value limit; malformed or oversized base64 is refused, and the host's inbound body cap still applies). All writes, including named writes and supported raw/base64/multipart uploads, reject URL destinations whose own query/fragment contains the exact `[redacted]` placeholder, plain or encoded, with `invalid_args` before dispatch. Admission iteratively checks every JSON key and value within the host's accepted request size, without separate depth or node-count ceilings; cyclic direct arguments are refused. A marker outside the destination remains ordinary text. Split strings, encoded host/signing names, bare hosts without `//`, and opaque bearer paths remain outside this scan. Binary responses represented as base64, including Worker JavaScript served as `application/javascript`, are returned unchanged and are not decoded/scanned. An envelope cursor that would change under URL sanitizing is omitted rather than echoed or reused.\n",
9
+ "key": "\n\n- This is Connecta's REST connector to the Cloudflare v4 API, holding one operator-managed credential. `cloudflare_api_search` finds API operations in a pinned index (not records) and names the tool that calls each; `cloudflare_api_details` returns one operation's parameters and body with types, enums, and required names. Neither sends a request.\n- `cloudflare_api_read` calls GET operations plus reviewed read-only POSTs: analytics queries, Analytics Engine and SQL API queries, Log Explorer SQL, Vectorize queries, KV bulk get, telemetry queries, application-name lookup, and validation checks (including custom posture finding types). `cloudflare_api_write` calls every other POST, PUT, PATCH, and DELETE with `method` and `path` stated. D1 `…/query` and `…/raw`, Durable Object queries, Workers AI `…/ai/run`, AI Search and AutoRAG search (query rewriting runs billed models), monetization checks, and flag evaluation are writes: they can change data, cost money, or activate something.\n- Paths are concrete: substitute ids into the template search returned (`/zones/<zone id>/dns_records/<record id>`). Pass `query` and `body` as JSON named as details lists them; text operations (Analytics Engine SQL, Log Explorer SQL, Worker scripts) take a string body, and Vectorize inserts a list of objects sent as NDJSON. R2 calls accept `headers` with `cf-r2-jurisdiction` or `cf-r2-storage-class`. Encode a `/` inside an R2 object key or KV key name as `%2F`.\n- Every call is checked against the pinned index before it is sent. An unknown path returns the nearest operations; an unknown or missing parameter returns `validation.issues` with the accepted names. Nothing reached Cloudflare, so fix the call from that answer.\n- Under an account pin, each uncached zone-ownership check also spends the connector's call budget. Checks stay capped at three per call and 60 per five minutes per connector; an exhausted budget stops the check and the requested operation.\n- Results arrive as `{ status, data, page? }` with Cloudflare's envelope unwrapped: `data` is the `result`. On a list, pass `page.next` as the `page.param` query argument (`page` for numbered lists, `cursor` for KV, R2, and other cursor lists). Lists belong inside `execute_code`; pass `select` (dot paths such as `id` and `name`) to keep only the fields the question needs.\n- `list_zones` and `list_accounts` resolve names to ids; `verify_credential` separates a bad credential from a missing permission. `graphql_query` runs read-only GraphQL Analytics queries (mutations are refused before sending); filter every `zones` or `accounts` field by `zoneTag` or `accountTag`. A pinned connector sends one query per document, with every fragment it defines used.\n- Multipart, binary, and file bodies go through `cloudflare_api_upload`: Worker scripts and versions, KV values, R2 objects, Pages deployments, Images, Stream, DNS zone-file import, and Snippets. It takes `textBody`, `base64Body`, or multipart `parts`, never a local file.\n- Refused by design: every operation that mints, rotates, or hands back a credential (API tokens, Access service tokens, R2 temporary credentials, tunnel and Mesh node tokens, signing keys, registry credentials, direct-upload URLs, deploy hooks, preview and participant tokens), payment client secrets (including AI Gateway top-up configuration), Cloudforce One priority-intelligence match reads and updates (their response schemas are unreviewed), URL Scanner captures (results, HAR, DOM, raw responses: they hold the scanned session's cookies and headers; use the scan search for verdicts), and GETs that act (an email unsubscribe, a PagerDuty connect). Stored and embedded secrets (Turnstile, TSIG, webhook and identity-provider secrets, Stream keys, Logpush destination credentials, probe, custom-provider, and AI Search crawler headers (every `include_headers` map value; names stay), BGP keys, Zaraz variables, Hyperdrive database passwords, RealtimeKit storage credentials, transform-rule header values) and application setup form defaults and every environment or plain-text binding value (Pages, Containers, Workers, Builds; names and types stay) come back as `\"[redacted]\"` on every method. Notification, RealtimeKit, and data security webhook destinations keep only their scheme and host. Workers KV values are application data and come back as stored. Reviewed operations, and operations that take credentials, report Cloudflare's error codes without its error text. Worker script content is readable: it is code, and secret bindings are not part of it.\n- An `auth_required` failure means the credential is missing, revoked, or malformed: an operator must replace it in this connection in the operator UI, and `authorize_connector` returns that handoff. `provider_permission_denied` means the credential works but lacks this permission or resource, or the call is outside the pin; ask rather than retry.\n- Embedded URL safety uses reviewed paths plus global signing parameters. Generic credential-named query parameters (`code`, `key`, `token`, `sig`, etc.) and credential-looking fragments withhold the destination's entire query/fragment only in Workers build `lines`, Pages log `data.line`, and telemetry `$metadata.message`/`error` and raw string/object `source` payloads in both `events.events` and `invocations.*` on the reviewed log operations and vendor error text. Ordinary `code`/`key` links can therefore be false positives in these diagnostics. DNS TXT content, KV values, Access/Waiting Room custom HTML and other operator content keep those ordinary URL parameters byte for byte. Literal AWS SigV4/Google V4 `X-Amz-*`/`X-Goog-*` Signature, Credential and Security-Token values are withheld in every decoded string before projection. A withheld value is not the original: do not write it back. KV bulk writes also inspect decoded `value` fields with `base64: true` (up to Cloudflare's 25 MiB decoded value limit; malformed or oversized base64 is refused, and the host's inbound body cap still applies). All writes, including named writes and supported raw/base64/multipart uploads, reject URL destinations whose own query/fragment contains the exact `[redacted]` placeholder, plain or encoded, with `invalid_args` before dispatch. Admission iteratively checks every JSON key and value within the host's accepted request size, without separate depth or node-count ceilings; cyclic direct arguments are refused. A marker outside the destination remains ordinary text. Split strings, encoded host/signing names, bare hosts without `//`, and opaque bearer paths remain outside this scan. Binary responses represented as base64, including Worker JavaScript served as `application/javascript`, are returned unchanged and are not decoded/scanned. An envelope cursor that would change under URL sanitizing is omitted rather than echoed or reused.\n",
10
10
  "shared": "- Resolve account and zone ids before acting; never guess one. Ids are 32-character hex strings, and a zone is found by its domain name. Defaults and pins route a connector; Cloudflare's permissions still decide what the credential may do.\n- Read state before replacing, rolling back, or purging. For DNS, PATCH changes the fields you send and PUT replaces the whole record, so read the record first and send its type, name, and content with the change. Prefer a targeted cache purge (files, tags, hosts, or prefixes) over purging everything.\n- Cloudflare reports failures in `errors` with numeric codes and returns `success: false`; read the message instead of retrying the same call.\n- Honor rate-limit waits. Cloudflare documents 1,200 requests per five minutes per user, counted across the dashboard and every token; Connecta's per-runtime window approximates it, and a limited user is blocked for the rest of the window.\n- Treat every create, update, delete, purge, deploy, and upload as a write. For guest calls, classification, and routing, fetch `skills({ name: \"usage\" })`.\n",
11
11
  "key_tail": "- Cloudflare answers a rate limit with HTTP 429; the failure carries `retryAfterMs`. Failed authentication is limited separately (code 10502), so diagnose a credential once with `verify_credential` rather than retrying calls.\n"
12
12
  }
@@ -4,7 +4,8 @@
4
4
  // `value-safety.node.test.ts` runs the shared harness over it.
5
5
  //
6
6
  // - `refuse`: the operation exists to mint, rotate, or hand back a credential
7
- // (or move money through a payment secret); nothing is sent.
7
+ // (or move money through a payment secret), or lacks a reviewed response
8
+ // contract; nothing is sent.
8
9
  // - `redact`: the operation is useful, and these field paths carry a
9
10
  // credential or stored secret; they are replaced before the result is
10
11
  // returned, on every method. A path ending in `#url` keeps only the URL's
@@ -16,7 +17,7 @@
16
17
  // or the whole body when it is not an envelope); lists are traversed
17
18
  // implicitly and `*` matches every key of an object.
18
19
  // Embedded URL verdict (#812): reviewed paths + global signing parameters.
19
- // Evidence: digest-verified schema revision 3fad73eb27c381031c09ccdfa28ef9352029cd5a:
20
+ // Evidence: digest-verified schema revision 843eae2fbf4fe774ef9c91021dd34017ac9c970a:
20
21
  // builds_BuildLogsResponse.lines (timestamp/log-message tuples),
21
22
  // pages_deployment_log.data.line and workers-observability_query_results
22
23
  // events.events and invocations.* -> telemetry_event.$metadata.message/error
@@ -30,7 +31,10 @@
30
31
  // JavaScript served as application/javascript) are not decoded or scanned.
31
32
  // Recording/download/upload capabilities retain their structured-field rules.
32
33
  // The review found no documented Cloudflare-minted links embedded in other
33
- // free text and justifies no Cloudflare/R2/S3-wide file-host policy.
34
+ // free text, including AI Search item/job logs, and justifies no
35
+ // Cloudflare/R2/S3-wide file-host policy. The new Cloudforce match responses
36
+ // omit schemas; both operations are refused until their response contracts
37
+ // can be reviewed, including their error bodies.
34
38
  import { redact as reviewed, refuse, safe as reviewedSafe, } from "../_shared/rest/value-safety.js";
35
39
  const MINT = "Connecta does not create, rotate, or return credentials; an operator manages this credential in the Cloudflare dashboard.";
36
40
  const API_TOKENS = "Connecta does not create, roll, edit, or delete API tokens; an operator manages tokens in the Cloudflare dashboard.";
@@ -39,6 +43,7 @@ const BEARER = "This returns a URL or upload token that grants access without Cl
39
43
  const ACTS = "This GET acts rather than reads (it completes a connection or an unsubscribe), so it is not a read.";
40
44
  const INTERNAL = "An internal Cloudflare test route; it is not part of the supported API.";
41
45
  const CAPTURE = "A URL scan's captured session (cookies, request and response headers, response bodies, DOM) can carry credentials, so Connecta does not return it. Read scan verdicts with GET /accounts/{account_id}/urlscanner/v2/search.";
46
+ const UNREVIEWED_MATCHES = "Cloudforce match responses have no reviewed response schema; Connecta refuses this operation until its success and error values can be reviewed.";
42
47
  const METADATA = "Returns names, ids, status, and timestamps; no credential value.";
43
48
  const REVOKES = "Revokes or deletes; returns no credential value.";
44
49
  const CALLER_SUPPLIED = "The caller supplies the secret; the response returns only its name and metadata.";
@@ -152,6 +157,33 @@ const DESCRIBED_ONLY = "The spec mentions credentials in this field's descriptio
152
157
  const A = "/accounts/{account_id}";
153
158
  const Z = "/zones/{zone_id}";
154
159
  const OPERATIONS = {
160
+ [`POST ${A}/ai-gateway/billing/topup/config`]: refuse("Returns a payment client secret; complete billing setup in the dashboard."),
161
+ ...Object.fromEntries([
162
+ `GET ${A}/cws/basic-auth-credentials`,
163
+ `POST ${A}/cws/basic-auth-credentials`,
164
+ `GET ${A}/cws/basic-auth-credentials/{credential_id}`,
165
+ `DELETE ${A}/cws/basic-auth-credentials/{credential_id}`,
166
+ `GET ${A}/cws/basic-auth-credentials/{credential_id}/secrets`,
167
+ `POST ${A}/cws/basic-auth-credentials/{credential_id}/secrets`,
168
+ `GET ${A}/cws/basic-auth-credentials/{credential_id}/secrets/{secret_id}`,
169
+ `DELETE ${A}/cws/basic-auth-credentials/{credential_id}/secrets/{secret_id}`,
170
+ ].map((key) => [
171
+ key,
172
+ safe("CWS returns only username, ids, and creation/expiry metadata; password input and hashes are never returned."),
173
+ ])),
174
+ ...Object.fromEntries([
175
+ `GET ${A}/devices/policies`,
176
+ `GET ${A}/devices/policy`,
177
+ `POST ${A}/devices/policy`,
178
+ `PATCH ${A}/devices/policy`,
179
+ `GET ${A}/devices/policy/{policy_id}`,
180
+ `PATCH ${A}/devices/policy/{policy_id}`,
181
+ `DELETE ${A}/devices/policy/{policy_id}`,
182
+ ].map((key) => [
183
+ key,
184
+ safe("Device credential authentication is a boolean reauthentication policy, not a credential.", "enable_device_credential_authentication"),
185
+ ])),
186
+ "GET /cws/filter-schema": safe("Filter-builder fields and advisory operator labels; no credentials.", "policy_fields.operators", "workload_fields.operators"),
155
187
  // Access
156
188
  [`GET ${A}/access/apps`]: ACCESS_APP,
157
189
  [`POST ${A}/access/apps`]: ACCESS_APP,
@@ -291,6 +323,8 @@ const OPERATIONS = {
291
323
  [`GET ${A}/cfd_tunnel/{tunnel_id}/token`]: refuse("A tunnel token lets anyone run this tunnel; Connecta does not return it. Read the tunnel itself instead."),
292
324
  [`GET ${A}/warp_connector/{tunnel_id}/token`]: refuse("A Mesh node token lets anyone run this connector; Connecta does not return it."),
293
325
  // Cloudforce One
326
+ [`GET ${A}/cloudforce-one/v2/priority-intelligence/{priority_id}/matches`]: refuse(UNREVIEWED_MATCHES),
327
+ [`PATCH ${A}/cloudforce-one/v2/priority-intelligence/{priority_id}/matches`]: refuse(UNREVIEWED_MATCHES),
294
328
  [`GET ${A}/cloudforce-one/v2/credential-monitor/domains`]: safe(METADATA),
295
329
  [`POST ${A}/cloudforce-one/v2/credential-monitor/domains`]: safe(METADATA),
296
330
  [`DELETE ${A}/cloudforce-one/v2/credential-monitor/domains/{id}`]: safe(REVOKES),
@@ -656,6 +690,18 @@ const OPERATIONS = {
656
690
  [`GET ${A}/urlscanner/v2/har/{scan_id}`]: refuse(CAPTURE),
657
691
  [`GET ${A}/urlscanner/v2/dom/{scan_id}`]: refuse(CAPTURE),
658
692
  [`GET ${A}/urlscanner/v2/responses/{response_id}`]: refuse(CAPTURE),
693
+ // Custom crawler headers may authenticate under any name. Withhold every
694
+ // value in stored or echoed instance configuration, preserving header names.
695
+ ...Object.fromEntries([
696
+ `GET ${A}/ai-search/namespaces/{namespace}/instances`,
697
+ `POST ${A}/ai-search/namespaces/{namespace}/instances`,
698
+ `GET ${A}/ai-search/namespaces/{namespace}/instances/{id}`,
699
+ `PUT ${A}/ai-search/namespaces/{namespace}/instances/{id}`,
700
+ `DELETE ${A}/ai-search/namespaces/{namespace}/instances/{id}`,
701
+ ].map((key) => [
702
+ key,
703
+ reviewed("Crawler include_headers stores custom request header values that can authenticate under arbitrary names.", ["source_params.web_crawler.parse_options.include_headers{}"]),
704
+ ])),
659
705
  // Reviewed safe: the flagged field is metadata, a header name list, a count, or third-party data the operation exists to return.
660
706
  ...Object.fromEntries([
661
707
  [`GET ${A}/access/ai-controls/mcp/portals`, "servers.auth_config_summary"],
@@ -668,20 +714,6 @@ const OPERATIONS = {
668
714
  [`PUT ${A}/access/ai-controls/mcp/servers/{id}`, "auth_config_summary"],
669
715
  [`DELETE ${A}/access/ai-controls/mcp/servers/{id}`, "auth_config_summary"],
670
716
  [`GET ${A}/access/users/{user_id}/last_seen_identity`, "passkeys"],
671
- [`GET ${A}/ai-search/namespaces/{name}/instances`, "source_params.web_crawler.parse_options.include_headers"],
672
- [`POST ${A}/ai-search/namespaces/{name}/instances`, "source_params.web_crawler.parse_options.include_headers"],
673
- [
674
- `GET ${A}/ai-search/namespaces/{name}/instances/{id}`,
675
- "source_params.web_crawler.parse_options.include_headers",
676
- ],
677
- [
678
- `PUT ${A}/ai-search/namespaces/{name}/instances/{id}`,
679
- "source_params.web_crawler.parse_options.include_headers",
680
- ],
681
- [
682
- `DELETE ${A}/ai-search/namespaces/{name}/instances/{id}`,
683
- "source_params.web_crawler.parse_options.include_headers",
684
- ],
685
717
  [`POST ${A}/ai/tomarkdown`, "tokens"],
686
718
  [`GET ${A}/basin-catalog/{bucket_name}/maintenance-configs`, ""],
687
719
  [`GET ${A}/billable/usage/billable-metrics`, "DimensionKeys"],
@@ -1039,7 +1071,7 @@ const OPERATIONS = {
1039
1071
  key,
1040
1072
  safe("OAuth client registrations: ids, URIs, and whether a secret was rotated; the secret itself is refused at creation."),
1041
1073
  ])),
1042
- [`GET ${A}/one/applications/{application_id}/setup-flows`]: safe("Setup flows describe the OAuth authorization URL and form fields to fill, not credentials.", "steps.form_fields.name", "steps.form_fields.type"),
1074
+ [`GET ${A}/one/applications/{application_id}/setup-flows`]: reviewed("Setup form defaults may carry credential values; withhold defaults while retaining field names and input types.", ["steps.form_fields.default_value"], ["steps", "steps.form_fields.name", "steps.form_fields.type"]),
1043
1075
  ...Object.fromEntries([
1044
1076
  `GET ${A}/realtime/kit/{app_id}/meetings`,
1045
1077
  `POST ${A}/realtime/kit/{app_id}/meetings`,
@@ -1223,6 +1255,7 @@ export const CLOUDFLARE_VALUE_SAFETY = {
1223
1255
  ["amr_matching_session_duration", "A session duration."],
1224
1256
  ["doh_jwt_duration", "A token lifetime setting."],
1225
1257
  ["token_endpoint_auth_method", "An OAuth client authentication method name."],
1258
+ ["leak_reporter", "The secret-scanning partner name, not the leaked token."],
1226
1259
  ["has_rotated_secret", "Whether a client secret was rotated; a flag."],
1227
1260
  ["secret_preview", "A masked preview of a caller-supplied key."],
1228
1261
  ["token_pricing", "Per-token model pricing."],
@@ -21,7 +21,7 @@ import { skill } from "./skill.generated.js";
21
21
  *
22
22
  * Drift. Google publishes a credential-free Discovery document per API.
23
23
  * `src/providers/docs/drift.json` records the three methods the tools
24
- * call, and `npm run providers:check -- --provider docs` reports a touched
24
+ * call, and `pnpm run providers:check --provider docs` reports a touched
25
25
  * contract that moved or a method that stopped accepting the scope below.
26
26
  */
27
27
  import { apiConnector as api, defined } from "../../connectors/api-connector.js";
@@ -1,9 +1,9 @@
1
1
  // Generated by scripts/generate-providers.mjs; do not edit.
2
2
  export const skill = {
3
- "content": "# Google Docs usage\n\nReads and edits Google Docs as the signed-in person through Workspace delegation; it cannot search or list documents.\n\nConnection purpose: \n\n## Whose documents\n\nEvery call acts as the Workspace account deployment config maps the caller\nto, and reaches exactly the documents Drive shares with that person; an\nagent acts as the owner of the pool it came through, unless the deployment fixes one shared account that every caller, agent or person, acts as. No argument names an\naccount. A call with none mapped, or from an agent whose pool has no owner,\nfails `auth_required`; only an operator can change either.\n\n## Finding a document\n\nThere is no search or list tool. Take the id from a\ndocs.google.com URL (the part after `/d/`), from the person, or from a\nGoogle Drive connection's file search. A failure on an id may mean it does\nnot exist or is not shared with this person; Google does not say which.\n\n## Reading\n\n- `get_document` renders every tab as markdown-ish text: headings, lists,\n links, tables as pipe rows, footnotes after the body, images and other\n embeds as bracketed markers. Headers, footers, floating images, and\n comments are not rendered; a tab's `notRendered` names any it holds, and\n `suggestion_marks` there means the text mixes unmarked suggested\n insertions and deletions in (read with `suggestions` to preview). Text past `maxChars` (shared across tabs) ends with a marker;\n pass `tabId` to read one tab.\n- Before an index-based edit, read with `withIndexes: true` and target an\n element's `startIndex`/`endIndex`. A long document's rows page with\n `page.nextCursor`; send `maxChars: 0` on later pages, and start over if\n `revisionId` changed between them. Indexes are UTF-16 code units and\n shift after every edit. Keep `revisionId` and pass it as\n `requiredRevisionId`, so an edit against a document someone else has\n changed fails `conflict` with nothing applied; re-read and recompute. Indexes\n from a suggestions preview (`accepted`/`rejected`) are not valid for\n edits.\n- `raw: true` returns Google's document resource for the tabs instead of\n the rendering — styles, headers, footers, named ranges, list and segment ids\n that `batch_update_document` requests may need. It is large: pass\n `tabId`, and call it directly rather than inside `execute_code`,\n which carries at most 256 KiB per result.\n- The same holds for a `maxChars` above the default: a long rendering\n can pass here and still be refused inside `execute_code`, even when the\n program would only measure it. Read it with a direct `call_tool`, paged\n with `connecta.result`, or read one tab at a time.\n\n## Editing\n\n- `create_document` makes a new document in My Drive's root. Never create\n again after a failure that may have left one behind; the message says\n which. When the document exists but its starting text failed, the message\n names its id: append the text with `append_text` straight away only when\n Google refused it; otherwise read the document first and append only what\n is missing.\n- An edit sent with no answer back, answered with a server error, or\n redirected says its outcome is unknown: re-read before repeating it, or\n repeat it with the same `requiredRevisionId`, which Google refuses if the\n first attempt landed. One Google answered 2xx whose reply was unreadable\n says it was applied: do not repeat it.\n- `append_text` joins the last paragraph; begin with `\\n` for a new one.\n- `replace_all_text` changes every match at once; read first and make\n `find` specific.\n- `batch_update_document` takes raw Docs API requests (`deleteContentRange`,\n `updateTextStyle`, `insertTable`, `createParagraphBullets`, …) for what\n the named tools cannot do. It is always destructive, and requires the\n `requiredRevisionId` from the read it was planned against: up to 100\n requests, each exactly one generally available kind (the Developer Preview\n comment and suggestion kinds are refused). Several requests in one\n call apply atomically, and each sees the index shifts of those before it;\n order deletions from the end of the document backwards.\n",
3
+ "content": "# Google Docs usage\n\nReads and edits Google Docs as the signed-in person through Workspace delegation; it cannot search or list documents.\n\nConnection purpose: \n\n## Whose documents\n\nEvery call acts as the Workspace account deployment config maps the caller\nto, and reaches exactly the documents Drive shares with that person; an\nagent acts as the owner of the pool it came through, unless the deployment fixes one shared account that every caller, agent or person, acts as. No argument names an\naccount. A call with none mapped fails `auth_required`; only an operator can change\nthat.\n\n## Finding a document\n\nThere is no search or list tool. Take the id from a\ndocs.google.com URL (the part after `/d/`), from the person, or from a\nGoogle Drive connection's file search. A failure on an id may mean it does\nnot exist or is not shared with this person; Google does not say which.\n\n## Reading\n\n- `get_document` renders every tab as markdown-ish text: headings, lists,\n links, tables as pipe rows, footnotes after the body, images and other\n embeds as bracketed markers. Headers, footers, floating images, and\n comments are not rendered; a tab's `notRendered` names any it holds, and\n `suggestion_marks` there means the text mixes unmarked suggested\n insertions and deletions in (read with `suggestions` to preview). Text past `maxChars` (shared across tabs) ends with a marker;\n pass `tabId` to read one tab.\n- Before an index-based edit, read with `withIndexes: true` and target an\n element's `startIndex`/`endIndex`. A long document's rows page with\n `page.nextCursor`; send `maxChars: 0` on later pages, and start over if\n `revisionId` changed between them. Indexes are UTF-16 code units and\n shift after every edit. Keep `revisionId` and pass it as\n `requiredRevisionId`, so an edit against a document someone else has\n changed fails `conflict` with nothing applied; re-read and recompute. Indexes\n from a suggestions preview (`accepted`/`rejected`) are not valid for\n edits.\n- `raw: true` returns Google's document resource for the tabs instead of\n the rendering — styles, headers, footers, named ranges, list and segment ids\n that `batch_update_document` requests may need. It is large: pass\n `tabId`, and call it directly rather than inside `execute_code`,\n which carries at most 256 KiB per result.\n- The same holds for a `maxChars` above the default: a long rendering\n can pass here and still be refused inside `execute_code`, even when the\n program would only measure it. Read it with a direct `call_tool`, paged\n with `connecta.result`, or read one tab at a time.\n\n## Editing\n\n- `create_document` makes a new document in My Drive's root. Never create\n again after a failure that may have left one behind; the message says\n which. When the document exists but its starting text failed, the message\n names its id: append the text with `append_text` straight away only when\n Google refused it; otherwise read the document first and append only what\n is missing.\n- An edit sent with no answer back, answered with a server error, or\n redirected says its outcome is unknown: re-read before repeating it, or\n repeat it with the same `requiredRevisionId`, which Google refuses if the\n first attempt landed. One Google answered 2xx whose reply was unreadable\n says it was applied: do not repeat it.\n- `append_text` joins the last paragraph; begin with `\\n` for a new one.\n- `replace_all_text` changes every match at once; read first and make\n `find` specific.\n- `batch_update_document` takes raw Docs API requests (`deleteContentRange`,\n `updateTextStyle`, `insertTable`, `createParagraphBullets`, …) for what\n the named tools cannot do. It is always destructive, and requires the\n `requiredRevisionId` from the read it was planned against: up to 100\n requests, each exactly one generally available kind (the Developer Preview\n comment and suggestion kinds are refused). Several requests in one\n call apply atomically, and each sees the index shifts of those before it;\n order deletions from the end of the document backwards.\n",
4
4
  "instructionsHeading": "Connection instructions",
5
5
  "fragments": {
6
6
  "guide_0": "# Google Docs usage\n\nReads and edits Google Docs as the signed-in person through Workspace delegation; it cannot search or list documents.\n\nConnection purpose: ",
7
- "guide_1": "\n\n## Whose documents\n\nEvery call acts as the Workspace account deployment config maps the caller\nto, and reaches exactly the documents Drive shares with that person; an\nagent acts as the owner of the pool it came through, unless the deployment fixes one shared account that every caller, agent or person, acts as. No argument names an\naccount. A call with none mapped, or from an agent whose pool has no owner,\nfails `auth_required`; only an operator can change either.\n\n## Finding a document\n\nThere is no search or list tool. Take the id from a\ndocs.google.com URL (the part after `/d/`), from the person, or from a\nGoogle Drive connection's file search. A failure on an id may mean it does\nnot exist or is not shared with this person; Google does not say which.\n\n## Reading\n\n- `get_document` renders every tab as markdown-ish text: headings, lists,\n links, tables as pipe rows, footnotes after the body, images and other\n embeds as bracketed markers. Headers, footers, floating images, and\n comments are not rendered; a tab's `notRendered` names any it holds, and\n `suggestion_marks` there means the text mixes unmarked suggested\n insertions and deletions in (read with `suggestions` to preview). Text past `maxChars` (shared across tabs) ends with a marker;\n pass `tabId` to read one tab.\n- Before an index-based edit, read with `withIndexes: true` and target an\n element's `startIndex`/`endIndex`. A long document's rows page with\n `page.nextCursor`; send `maxChars: 0` on later pages, and start over if\n `revisionId` changed between them. Indexes are UTF-16 code units and\n shift after every edit. Keep `revisionId` and pass it as\n `requiredRevisionId`, so an edit against a document someone else has\n changed fails `conflict` with nothing applied; re-read and recompute. Indexes\n from a suggestions preview (`accepted`/`rejected`) are not valid for\n edits.\n- `raw: true` returns Google's document resource for the tabs instead of\n the rendering — styles, headers, footers, named ranges, list and segment ids\n that `batch_update_document` requests may need. It is large: pass\n `tabId`, and call it directly rather than inside `execute_code`,\n which carries at most 256 KiB per result.\n- The same holds for a `maxChars` above the default: a long rendering\n can pass here and still be refused inside `execute_code`, even when the\n program would only measure it. Read it with a direct `call_tool`, paged\n with `connecta.result`, or read one tab at a time.\n\n## Editing\n\n- `create_document` makes a new document in My Drive's root. Never create\n again after a failure that may have left one behind; the message says\n which. When the document exists but its starting text failed, the message\n names its id: append the text with `append_text` straight away only when\n Google refused it; otherwise read the document first and append only what\n is missing.\n- An edit sent with no answer back, answered with a server error, or\n redirected says its outcome is unknown: re-read before repeating it, or\n repeat it with the same `requiredRevisionId`, which Google refuses if the\n first attempt landed. One Google answered 2xx whose reply was unreadable\n says it was applied: do not repeat it.\n- `append_text` joins the last paragraph; begin with `\\n` for a new one.\n- `replace_all_text` changes every match at once; read first and make\n `find` specific.\n- `batch_update_document` takes raw Docs API requests (`deleteContentRange`,\n `updateTextStyle`, `insertTable`, `createParagraphBullets`, …) for what\n the named tools cannot do. It is always destructive, and requires the\n `requiredRevisionId` from the read it was planned against: up to 100\n requests, each exactly one generally available kind (the Developer Preview\n comment and suggestion kinds are refused). Several requests in one\n call apply atomically, and each sees the index shifts of those before it;\n order deletions from the end of the document backwards.\n"
7
+ "guide_1": "\n\n## Whose documents\n\nEvery call acts as the Workspace account deployment config maps the caller\nto, and reaches exactly the documents Drive shares with that person; an\nagent acts as the owner of the pool it came through, unless the deployment fixes one shared account that every caller, agent or person, acts as. No argument names an\naccount. A call with none mapped fails `auth_required`; only an operator can change\nthat.\n\n## Finding a document\n\nThere is no search or list tool. Take the id from a\ndocs.google.com URL (the part after `/d/`), from the person, or from a\nGoogle Drive connection's file search. A failure on an id may mean it does\nnot exist or is not shared with this person; Google does not say which.\n\n## Reading\n\n- `get_document` renders every tab as markdown-ish text: headings, lists,\n links, tables as pipe rows, footnotes after the body, images and other\n embeds as bracketed markers. Headers, footers, floating images, and\n comments are not rendered; a tab's `notRendered` names any it holds, and\n `suggestion_marks` there means the text mixes unmarked suggested\n insertions and deletions in (read with `suggestions` to preview). Text past `maxChars` (shared across tabs) ends with a marker;\n pass `tabId` to read one tab.\n- Before an index-based edit, read with `withIndexes: true` and target an\n element's `startIndex`/`endIndex`. A long document's rows page with\n `page.nextCursor`; send `maxChars: 0` on later pages, and start over if\n `revisionId` changed between them. Indexes are UTF-16 code units and\n shift after every edit. Keep `revisionId` and pass it as\n `requiredRevisionId`, so an edit against a document someone else has\n changed fails `conflict` with nothing applied; re-read and recompute. Indexes\n from a suggestions preview (`accepted`/`rejected`) are not valid for\n edits.\n- `raw: true` returns Google's document resource for the tabs instead of\n the rendering — styles, headers, footers, named ranges, list and segment ids\n that `batch_update_document` requests may need. It is large: pass\n `tabId`, and call it directly rather than inside `execute_code`,\n which carries at most 256 KiB per result.\n- The same holds for a `maxChars` above the default: a long rendering\n can pass here and still be refused inside `execute_code`, even when the\n program would only measure it. Read it with a direct `call_tool`, paged\n with `connecta.result`, or read one tab at a time.\n\n## Editing\n\n- `create_document` makes a new document in My Drive's root. Never create\n again after a failure that may have left one behind; the message says\n which. When the document exists but its starting text failed, the message\n names its id: append the text with `append_text` straight away only when\n Google refused it; otherwise read the document first and append only what\n is missing.\n- An edit sent with no answer back, answered with a server error, or\n redirected says its outcome is unknown: re-read before repeating it, or\n repeat it with the same `requiredRevisionId`, which Google refuses if the\n first attempt landed. One Google answered 2xx whose reply was unreadable\n says it was applied: do not repeat it.\n- `append_text` joins the last paragraph; begin with `\\n` for a new one.\n- `replace_all_text` changes every match at once; read first and make\n `find` specific.\n- `batch_update_document` takes raw Docs API requests (`deleteContentRange`,\n `updateTextStyle`, `insertTable`, `createParagraphBullets`, …) for what\n the named tools cannot do. It is always destructive, and requires the\n `requiredRevisionId` from the read it was planned against: up to 100\n requests, each exactly one generally available kind (the Developer Preview\n comment and suggestion kinds are refused). Several requests in one\n call apply atomically, and each sees the index shifts of those before it;\n order deletions from the end of the document backwards.\n"
8
8
  }
9
9
  };
@@ -26,7 +26,7 @@ import { skill } from "./skill.generated.js";
26
26
  * changes sharing is destructive and crosses the host's approval prompt.
27
27
  *
28
28
  * Drift. `src/providers/drive/drift.json` records the eleven methods the
29
- * tools call, and `npm run providers:check -- --provider drive` reports a
29
+ * tools call, and `pnpm run providers:check --provider drive` reports a
30
30
  * touched contract that moved or a method that stopped accepting the scope
31
31
  * below.
32
32
  */
@@ -1,9 +1,9 @@
1
1
  // Generated by scripts/generate-providers.mjs; do not edit.
2
2
  export const skill = {
3
- "content": "# Google Drive usage\n\nActs as the signed-in person's own Google Drive through Workspace delegation: what they can open, and nothing they cannot.\n\nConnection purpose: \n\n## Whose Drive\n\nEvery call acts as the Workspace address deployment config maps the caller\nto, and reaches exactly the files that person can open, shared drives\nincluded; an agent acts as the owner of the pool it came through, unless the deployment fixes one shared account that every caller, agent or person, acts as. No\nargument names an account. A call with none mapped, or from an agent whose\npool has no owner, fails `auth_required`; only an operator can change either. A missing file\nand one this person cannot see fail alike — Google does not distinguish them.\n\n## Finding files\n\n- `search_files` takes Drive query syntax: `name contains 'x'`,\n `fullText contains 'x'`, `mimeType = '…'`, `'<folderId>' in parents`,\n `modifiedTime > '2026-01-01T00:00:00'`. Quote values with single quotes.\n Trashed files are left out unless `includeTrashed`. `incompleteSearch:\n true` means Drive gave up before searching everything; narrow the query or\n the corpus. Shared drives: `list_shared_drives`, then `corpora: \"drive\"`\n with its `driveId`.\n- Page with `page.nextCursor` and the same arguments; a cursor from another\n tool or other arguments is refused. A page can stop short of `limit` to\n stay under `maxBytes`; the cursor resumes at the first row left out, and\n fails `conflict` if that page changed meanwhile — start over then.\n- Ids come from these reads or a Drive URL (`/d/<id>/`, `/folders/<id>`);\n never guess one.\n\n## Reading content\n\n- `get_file_content` exports Docs as Markdown, Sheets as CSV of the\n **first sheet only**, and Slides as plain text; reads text files as text;\n and returns other files as base64 when the encoded file fits the result. Anything else — a\n larger binary, a drawing, a form, a folder, a shortcut — comes back as\n `format: \"unavailable\"` with a `note`, never as an empty success.\n- Text is capped by `maxChars`; a cut ends with a truncation marker and\n `contentTruncated: true`. Drive exports at most 10 MB.\n- Every read stays under `maxBytes` of JSON. The default is what one result\n can carry into `execute_code`, so a program always receives it. A larger\n `maxBytes` (up to 4 MiB — a binary of up to 1 MiB) reaches only a direct\n `call_tool`, which pages it with `connecta.result`.\n\n## Writing\n\n- `create_folder` and `restore_file` add without losing or exposing\n anything.\n- A new file takes its folder's sharing, not its source's: `create_file`\n and `copy_file` into a shared folder disclose that content to everyone\n the folder is shared with, so both are approved like any destructive write.\n Check the destination with `list_permissions` first. `convertTo`\n imports content as a Google Doc, Sheet, or Slides file.\n- `update_file_content` replaces the whole content; send all of it.\n `update_file` renames; `move_file` changes the folder and with it who\n inherits access.\n- `trash_file` is recoverable for 30 days with `restore_file`. There is no\n permanent delete and no ownership transfer.\n- `share_file` emails no one unless `sendNotificationEmail: true`. Check\n `list_permissions` before and after changing access, and confirm with the\n person before sharing with `anyone` or a whole domain.\n",
3
+ "content": "# Google Drive usage\n\nActs as the signed-in person's own Google Drive through Workspace delegation: what they can open, and nothing they cannot.\n\nConnection purpose: \n\n## Whose Drive\n\nEvery call acts as the Workspace address deployment config maps the caller\nto, and reaches exactly the files that person can open, shared drives\nincluded; an agent acts as the owner of the pool it came through, unless the deployment fixes one shared account that every caller, agent or person, acts as. No\nargument names an account. A call with none mapped fails `auth_required`; only an operator can change\nthat. A missing file\nand one this person cannot see fail alike — Google does not distinguish them.\n\n## Finding files\n\n- `search_files` takes Drive query syntax: `name contains 'x'`,\n `fullText contains 'x'`, `mimeType = '…'`, `'<folderId>' in parents`,\n `modifiedTime > '2026-01-01T00:00:00'`. Quote values with single quotes.\n Trashed files are left out unless `includeTrashed`. `incompleteSearch:\n true` means Drive gave up before searching everything; narrow the query or\n the corpus. Shared drives: `list_shared_drives`, then `corpora: \"drive\"`\n with its `driveId`.\n- Page with `page.nextCursor` and the same arguments; a cursor from another\n tool or other arguments is refused. A page can stop short of `limit` to\n stay under `maxBytes`; the cursor resumes at the first row left out, and\n fails `conflict` if that page changed meanwhile — start over then.\n- Ids come from these reads or a Drive URL (`/d/<id>/`, `/folders/<id>`);\n never guess one.\n\n## Reading content\n\n- `get_file_content` exports Docs as Markdown, Sheets as CSV of the\n **first sheet only**, and Slides as plain text; reads text files as text;\n and returns other files as base64 when the encoded file fits the result. Anything else — a\n larger binary, a drawing, a form, a folder, a shortcut — comes back as\n `format: \"unavailable\"` with a `note`, never as an empty success.\n- Text is capped by `maxChars`; a cut ends with a truncation marker and\n `contentTruncated: true`. Drive exports at most 10 MB.\n- Every read stays under `maxBytes` of JSON. The default is what one result\n can carry into `execute_code`, so a program always receives it. A larger\n `maxBytes` (up to 4 MiB — a binary of up to 1 MiB) reaches only a direct\n `call_tool`, which pages it with `connecta.result`.\n\n## Writing\n\n- `create_folder` and `restore_file` add without losing or exposing\n anything.\n- A new file takes its folder's sharing, not its source's: `create_file`\n and `copy_file` into a shared folder disclose that content to everyone\n the folder is shared with, so both are approved like any destructive write.\n Check the destination with `list_permissions` first. `convertTo`\n imports content as a Google Doc, Sheet, or Slides file.\n- `update_file_content` replaces the whole content; send all of it.\n `update_file` renames; `move_file` changes the folder and with it who\n inherits access.\n- `trash_file` is recoverable for 30 days with `restore_file`. There is no\n permanent delete and no ownership transfer.\n- `share_file` emails no one unless `sendNotificationEmail: true`. Check\n `list_permissions` before and after changing access, and confirm with the\n person before sharing with `anyone` or a whole domain.\n",
4
4
  "instructionsHeading": "Connection instructions",
5
5
  "fragments": {
6
6
  "guide_0": "# Google Drive usage\n\nActs as the signed-in person's own Google Drive through Workspace delegation: what they can open, and nothing they cannot.\n\nConnection purpose: ",
7
- "guide_1": "\n\n## Whose Drive\n\nEvery call acts as the Workspace address deployment config maps the caller\nto, and reaches exactly the files that person can open, shared drives\nincluded; an agent acts as the owner of the pool it came through, unless the deployment fixes one shared account that every caller, agent or person, acts as. No\nargument names an account. A call with none mapped, or from an agent whose\npool has no owner, fails `auth_required`; only an operator can change either. A missing file\nand one this person cannot see fail alike — Google does not distinguish them.\n\n## Finding files\n\n- `search_files` takes Drive query syntax: `name contains 'x'`,\n `fullText contains 'x'`, `mimeType = '…'`, `'<folderId>' in parents`,\n `modifiedTime > '2026-01-01T00:00:00'`. Quote values with single quotes.\n Trashed files are left out unless `includeTrashed`. `incompleteSearch:\n true` means Drive gave up before searching everything; narrow the query or\n the corpus. Shared drives: `list_shared_drives`, then `corpora: \"drive\"`\n with its `driveId`.\n- Page with `page.nextCursor` and the same arguments; a cursor from another\n tool or other arguments is refused. A page can stop short of `limit` to\n stay under `maxBytes`; the cursor resumes at the first row left out, and\n fails `conflict` if that page changed meanwhile — start over then.\n- Ids come from these reads or a Drive URL (`/d/<id>/`, `/folders/<id>`);\n never guess one.\n\n## Reading content\n\n- `get_file_content` exports Docs as Markdown, Sheets as CSV of the\n **first sheet only**, and Slides as plain text; reads text files as text;\n and returns other files as base64 when the encoded file fits the result. Anything else — a\n larger binary, a drawing, a form, a folder, a shortcut — comes back as\n `format: \"unavailable\"` with a `note`, never as an empty success.\n- Text is capped by `maxChars`; a cut ends with a truncation marker and\n `contentTruncated: true`. Drive exports at most 10 MB.\n- Every read stays under `maxBytes` of JSON. The default is what one result\n can carry into `execute_code`, so a program always receives it. A larger\n `maxBytes` (up to 4 MiB — a binary of up to 1 MiB) reaches only a direct\n `call_tool`, which pages it with `connecta.result`.\n\n## Writing\n\n- `create_folder` and `restore_file` add without losing or exposing\n anything.\n- A new file takes its folder's sharing, not its source's: `create_file`\n and `copy_file` into a shared folder disclose that content to everyone\n the folder is shared with, so both are approved like any destructive write.\n Check the destination with `list_permissions` first. `convertTo`\n imports content as a Google Doc, Sheet, or Slides file.\n- `update_file_content` replaces the whole content; send all of it.\n `update_file` renames; `move_file` changes the folder and with it who\n inherits access.\n- `trash_file` is recoverable for 30 days with `restore_file`. There is no\n permanent delete and no ownership transfer.\n- `share_file` emails no one unless `sendNotificationEmail: true`. Check\n `list_permissions` before and after changing access, and confirm with the\n person before sharing with `anyone` or a whole domain.\n"
7
+ "guide_1": "\n\n## Whose Drive\n\nEvery call acts as the Workspace address deployment config maps the caller\nto, and reaches exactly the files that person can open, shared drives\nincluded; an agent acts as the owner of the pool it came through, unless the deployment fixes one shared account that every caller, agent or person, acts as. No\nargument names an account. A call with none mapped fails `auth_required`; only an operator can change\nthat. A missing file\nand one this person cannot see fail alike — Google does not distinguish them.\n\n## Finding files\n\n- `search_files` takes Drive query syntax: `name contains 'x'`,\n `fullText contains 'x'`, `mimeType = '…'`, `'<folderId>' in parents`,\n `modifiedTime > '2026-01-01T00:00:00'`. Quote values with single quotes.\n Trashed files are left out unless `includeTrashed`. `incompleteSearch:\n true` means Drive gave up before searching everything; narrow the query or\n the corpus. Shared drives: `list_shared_drives`, then `corpora: \"drive\"`\n with its `driveId`.\n- Page with `page.nextCursor` and the same arguments; a cursor from another\n tool or other arguments is refused. A page can stop short of `limit` to\n stay under `maxBytes`; the cursor resumes at the first row left out, and\n fails `conflict` if that page changed meanwhile — start over then.\n- Ids come from these reads or a Drive URL (`/d/<id>/`, `/folders/<id>`);\n never guess one.\n\n## Reading content\n\n- `get_file_content` exports Docs as Markdown, Sheets as CSV of the\n **first sheet only**, and Slides as plain text; reads text files as text;\n and returns other files as base64 when the encoded file fits the result. Anything else — a\n larger binary, a drawing, a form, a folder, a shortcut — comes back as\n `format: \"unavailable\"` with a `note`, never as an empty success.\n- Text is capped by `maxChars`; a cut ends with a truncation marker and\n `contentTruncated: true`. Drive exports at most 10 MB.\n- Every read stays under `maxBytes` of JSON. The default is what one result\n can carry into `execute_code`, so a program always receives it. A larger\n `maxBytes` (up to 4 MiB — a binary of up to 1 MiB) reaches only a direct\n `call_tool`, which pages it with `connecta.result`.\n\n## Writing\n\n- `create_folder` and `restore_file` add without losing or exposing\n anything.\n- A new file takes its folder's sharing, not its source's: `create_file`\n and `copy_file` into a shared folder disclose that content to everyone\n the folder is shared with, so both are approved like any destructive write.\n Check the destination with `list_permissions` first. `convertTo`\n imports content as a Google Doc, Sheet, or Slides file.\n- `update_file_content` replaces the whole content; send all of it.\n `update_file` renames; `move_file` changes the folder and with it who\n inherits access.\n- `trash_file` is recoverable for 30 days with `restore_file`. There is no\n permanent delete and no ownership transfer.\n- `share_file` emails no one unless `sendNotificationEmail: true`. Check\n `list_permissions` before and after changing access, and confirm with the\n person before sharing with `anyone` or a whole domain.\n"
8
8
  }
9
9
  };
@@ -22,7 +22,7 @@ import { skill } from "./skill.generated.js";
22
22
  *
23
23
  * Drift. Google publishes a credential-free Discovery document per API.
24
24
  * `src/providers/forms/drift.json` records the five methods the tools
25
- * call, and `npm run providers:check -- --provider forms` reports a touched
25
+ * call, and `pnpm run providers:check --provider forms` reports a touched
26
26
  * contract that moved or a method that stopped accepting the scopes below.
27
27
  */
28
28
  import { apiConnector as api, defined } from "../../connectors/api-connector.js";
@@ -1,10 +1,10 @@
1
1
  // Generated by scripts/generate-providers.mjs; do not edit.
2
2
  export const skill = {
3
- "content": "# Google Forms usage\n\nActs as the signed-in person in Google Forms through Workspace delegation: read and edit their forms, read responses, never submit or delete.\n\nConnection purpose: \n\n## Whose forms\n\nEvery call reaches only the forms the Workspace user deployment config maps\nthe caller to can open; an agent acts as the owner of the pool it came through, unless the deployment fixes one shared account that every caller, agent or person, acts as. No argument names a user. A call with none mapped, or from an agent\nwhose pool has no owner, fails `auth_required`; only an operator can change\neither. A form id\nthat does not exist and one not shared with that user fail alike, as\n`connector_call_failed`: Google does not say which.\n\n## Finding forms\n\nThere is no list or search here: the Forms API has none. Find a form in\nDrive, or take its id from an edit URL (`/forms/d/<formId>/edit`). The\n`/forms/d/e/…` responder link is not the form id.\n\n## Reading\n\n- Every result stays inside KiB, so it crosses into\n `execute_code` whole. A page that would outgrow that ends early with\n `page.hasMore`; follow `page.nextCursor` rather than raising `limit`.\n- `get_form` lists items with their `questions`: one for a question,\n one per row for a grid, none for sections and media. Answers are keyed by\n those `questionId`s. A large form pages its items (`itemCount` is the\n total), and a grid too large for one page continues on the next: the item\n repeats with `questionsFrom`, the index of its first row there, and\n `moreQuestions` counts rows still to come. A cursor from a form that has\n since changed fails `conflict`.\n Long text is cut with a marker; `raw: true` returns Google's whole Form\n resource, or refuses one too large for a result.\n- `list_responses` labels each answer with its question's title, in form\n order; an answer to a since-deleted question has no title. Answers longer\n than characters end with a truncation marker; `get_response`\n reads one whole. Page with `page.nextCursor` and the same\n `submittedAfter`. To read only new responses, pass the newest\n `lastSubmittedTime` already seen as `submittedAfter`. A cursor\n continues after the last response it returned; if Google's page has\n changed since (new, deleted, or reordered responses), it fails\n `conflict` — start again, or use `submittedAfter`.\n- A response too large for one result comes back cut, with the answers left\n out named in `omittedQuestionIds`; pass them to `get_response` as\n `questionIds`.\n\n## Editing\n\n- `create_form` makes an empty form: Google takes only its title and Drive\n file name at creation. Add a description and questions afterwards.\n- `update_form_info` replaces the title or description.\n- `batch_update_form` is the escape hatch for everything else, and always\n needs approval. It takes Google's own Request objects, applied all or none,\n with the form's current `revisionId` as `requiredRevisionId`: a form\n someone changed since you read it is refused as `conflict`, so re-read it\n and rebuild the edit. Its reply carries the new `revisionId` and each created item's id.\n A reply too large to return whole comes back with `truncated: true`: the\n batch still applied; read the rest with `get_form`. An `updateItem` needs an `updateMask`; read `raw: true` first so the\n item it replaces keeps its grading, images, and navigation.\n- Publishing, sharing, deleting, and watches are not in this connection; the\n owner does those in Forms or Drive. Responses are read-only.\n",
3
+ "content": "# Google Forms usage\n\nActs as the signed-in person in Google Forms through Workspace delegation: read and edit their forms, read responses, never submit or delete.\n\nConnection purpose: \n\n## Whose forms\n\nEvery call reaches only the forms the Workspace user deployment config maps\nthe caller to can open; an agent acts as the owner of the pool it came through, unless the deployment fixes one shared account that every caller, agent or person, acts as. No argument names a user. A call with none mapped fails `auth_required`; only an operator can change\nthat. A form id\nthat does not exist and one not shared with that user fail alike, as\n`connector_call_failed`: Google does not say which.\n\n## Finding forms\n\nThere is no list or search here: the Forms API has none. Find a form in\nDrive, or take its id from an edit URL (`/forms/d/<formId>/edit`). The\n`/forms/d/e/…` responder link is not the form id.\n\n## Reading\n\n- Every result stays inside KiB, so it crosses into\n `execute_code` whole. A page that would outgrow that ends early with\n `page.hasMore`; follow `page.nextCursor` rather than raising `limit`.\n- `get_form` lists items with their `questions`: one for a question,\n one per row for a grid, none for sections and media. Answers are keyed by\n those `questionId`s. A large form pages its items (`itemCount` is the\n total), and a grid too large for one page continues on the next: the item\n repeats with `questionsFrom`, the index of its first row there, and\n `moreQuestions` counts rows still to come. A cursor from a form that has\n since changed fails `conflict`.\n Long text is cut with a marker; `raw: true` returns Google's whole Form\n resource, or refuses one too large for a result.\n- `list_responses` labels each answer with its question's title, in form\n order; an answer to a since-deleted question has no title. Answers longer\n than characters end with a truncation marker; `get_response`\n reads one whole. Page with `page.nextCursor` and the same\n `submittedAfter`. To read only new responses, pass the newest\n `lastSubmittedTime` already seen as `submittedAfter`. A cursor\n continues after the last response it returned; if Google's page has\n changed since (new, deleted, or reordered responses), it fails\n `conflict` — start again, or use `submittedAfter`.\n- A response too large for one result comes back cut, with the answers left\n out named in `omittedQuestionIds`; pass them to `get_response` as\n `questionIds`.\n\n## Editing\n\n- `create_form` makes an empty form: Google takes only its title and Drive\n file name at creation. Add a description and questions afterwards.\n- `update_form_info` replaces the title or description.\n- `batch_update_form` is the escape hatch for everything else, and always\n needs approval. It takes Google's own Request objects, applied all or none,\n with the form's current `revisionId` as `requiredRevisionId`: a form\n someone changed since you read it is refused as `conflict`, so re-read it\n and rebuild the edit. Its reply carries the new `revisionId` and each created item's id.\n A reply too large to return whole comes back with `truncated: true`: the\n batch still applied; read the rest with `get_form`. An `updateItem` needs an `updateMask`; read `raw: true` first so the\n item it replaces keeps its grading, images, and navigation.\n- Publishing, sharing, deleting, and watches are not in this connection; the\n owner does those in Forms or Drive. Responses are read-only.\n",
4
4
  "instructionsHeading": "Connection instructions",
5
5
  "fragments": {
6
6
  "guide_0": "# Google Forms usage\n\nActs as the signed-in person in Google Forms through Workspace delegation: read and edit their forms, read responses, never submit or delete.\n\nConnection purpose: ",
7
- "guide_1": "\n\n## Whose forms\n\nEvery call reaches only the forms the Workspace user deployment config maps\nthe caller to can open; an agent acts as the owner of the pool it came through, unless the deployment fixes one shared account that every caller, agent or person, acts as. No argument names a user. A call with none mapped, or from an agent\nwhose pool has no owner, fails `auth_required`; only an operator can change\neither. A form id\nthat does not exist and one not shared with that user fail alike, as\n`connector_call_failed`: Google does not say which.\n\n## Finding forms\n\nThere is no list or search here: the Forms API has none. Find a form in\nDrive, or take its id from an edit URL (`/forms/d/<formId>/edit`). The\n`/forms/d/e/…` responder link is not the form id.\n\n## Reading\n\n- Every result stays inside ",
7
+ "guide_1": "\n\n## Whose forms\n\nEvery call reaches only the forms the Workspace user deployment config maps\nthe caller to can open; an agent acts as the owner of the pool it came through, unless the deployment fixes one shared account that every caller, agent or person, acts as. No argument names a user. A call with none mapped fails `auth_required`; only an operator can change\nthat. A form id\nthat does not exist and one not shared with that user fail alike, as\n`connector_call_failed`: Google does not say which.\n\n## Finding forms\n\nThere is no list or search here: the Forms API has none. Find a form in\nDrive, or take its id from an edit URL (`/forms/d/<formId>/edit`). The\n`/forms/d/e/…` responder link is not the form id.\n\n## Reading\n\n- Every result stays inside ",
8
8
  "guide_2": " KiB, so it crosses into\n `execute_code` whole. A page that would outgrow that ends early with\n `page.hasMore`; follow `page.nextCursor` rather than raising `limit`.\n- `get_form` lists items with their `questions`: one for a question,\n one per row for a grid, none for sections and media. Answers are keyed by\n those `questionId`s. A large form pages its items (`itemCount` is the\n total), and a grid too large for one page continues on the next: the item\n repeats with `questionsFrom`, the index of its first row there, and\n `moreQuestions` counts rows still to come. A cursor from a form that has\n since changed fails `conflict`.\n Long text is cut with a marker; `raw: true` returns Google's whole Form\n resource, or refuses one too large for a result.\n- `list_responses` labels each answer with its question's title, in form\n order; an answer to a since-deleted question has no title. Answers longer\n than ",
9
9
  "guide_3": " characters end with a truncation marker; `get_response`\n reads one whole. Page with `page.nextCursor` and the same\n `submittedAfter`. To read only new responses, pass the newest\n `lastSubmittedTime` already seen as `submittedAfter`. A cursor\n continues after the last response it returned; if Google's page has\n changed since (new, deleted, or reordered responses), it fails\n `conflict` — start again, or use `submittedAfter`.\n- A response too large for one result comes back cut, with the answers left\n out named in `omittedQuestionIds`; pass them to `get_response` as\n `questionIds`.\n\n## Editing\n\n- `create_form` makes an empty form: Google takes only its title and Drive\n file name at creation. Add a description and questions afterwards.\n- `update_form_info` replaces the title or description.\n- `batch_update_form` is the escape hatch for everything else, and always\n needs approval. It takes Google's own Request objects, applied all or none,\n with the form's current `revisionId` as `requiredRevisionId`: a form\n someone changed since you read it is refused as `conflict`, so re-read it\n and rebuild the edit. Its reply carries the new `revisionId` and each created item's id.\n A reply too large to return whole comes back with `truncated: true`: the\n batch still applied; read the rest with `get_form`. An `updateItem` needs an `updateMask`; read `raw: true` first so the\n item it replaces keeps its grading, images, and navigation.\n- Publishing, sharing, deleting, and watches are not in this connection; the\n owner does those in Forms or Drive. Responses are read-only.\n"
10
10
  }
@@ -21,7 +21,7 @@ import { skill } from "./skill.generated.js";
21
21
  *
22
22
  * Drift. Google publishes a credential-free Discovery document per API.
23
23
  * `src/providers/gmail/drift.json` records the nine methods the tools
24
- * call, and `npm run providers:check -- --provider gmail` reports a touched
24
+ * call, and `pnpm run providers:check --provider gmail` reports a touched
25
25
  * contract that moved or a method that stopped accepting the scopes below.
26
26
  */
27
27
  import { apiConnector as api, defined } from "../../connectors/api-connector.js";
@@ -1,9 +1,9 @@
1
1
  // Generated by scripts/generate-providers.mjs; do not edit.
2
2
  export const skill = {
3
- "content": "# Gmail usage (draft-only)\n\nActs as the signed-in person's own Gmail through Workspace delegation: read mail and write drafts, never send.\n\nConnection purpose: \n\n## Whose mailbox\n\nEvery call reads and writes the mailbox deployment config maps the caller\nto; an agent acts as the owner of the pool it came through, unless the deployment fixes one shared mailbox that every caller, agent or person, acts as. No argument\nnames a mailbox. A call with none mapped, or from an agent whose pool has no\nowner, fails `auth_required`; only an operator can change either.\n\n## Reading\n\n- `search_threads` takes Gmail search syntax; reuse the person's own\n phrasing (`from:`, `subject:`, `newer_than:7d`, `is:unread`,\n `label:`). Page with `page.nextCursor` and the same query.\n- `get_thread` returns messages oldest first with bodies capped by\n `maxBodyChars`; a cut body ends with a truncation marker. A long thread\n pages: follow `page.nextCursor` until `hasMore` is false, with the same\n arguments. `conflict` means the thread changed under the cursor; read it\n again from the start. Reduce inside\n `execute_code` before returning a long thread.\n- Results default to at most 192 KiB; `list_labels` pages the same way.\n- `get_message` and `get_thread` list attachment metadata. An attached or\n forwarded email is an attachment, not the body.\n- `bodyFormat: \"unavailable\"` means Gmail stored the body apart from the\n message and it is too large to read here; say so rather than summarizing.\n- `get_message`, each `get_thread` message, and `get_draft` return `bodyNote`\n when decoding falls back to UTF-8 for an unsupported charset. The note\n survives character and result-byte truncation, including `maxBodyChars: 0`\n when inline body data is decoded. It counts toward the result's byte limit.\n\n## Attachments\n\n- `get_attachment` takes `messageId` and the listing's immutable `partId`.\n Prefer `partId`, which stays stable across message reads, including an\n empty id for a root attachment. `attachmentId` is an alternative that\n Gmail can rotate between reads. When both are supplied, `partId` wins and\n the fresh message's download id is used. Read the listing again after\n an unknown-id refusal; never store an attachmentId for later use.\n- Small attachments can have inline data and no attachmentId. Use their\n partId too. Only carried attachment parts can be read, never arbitrary\n message body parts or blind download ids.\n- Text, JSON, XML, calendar invites, CSV, and `message/rfc822` return\n `format: \"text\"` and `content` decoded in the declared charset. An unsupported\n charset produces an explicit `note` that decoding used UTF-8. Message bodies\n return the notice separately as `bodyNote`.\n Parsed forwarded emails render From, To, Date, Subject, and their own\n plain body, else HTML converted to text. Raw forwarded data is preferred\n when present. A forwarded email without readable text returns an\n `unavailable` note.\n `maxChars` defaults to 12,000 code points, at most 100,000. A character\n or byte cut ends with a truncation marker and `contentTruncated: true`.\n Reduce attachment text inside `execute_code` before returning it.\n- Binary attachments return `format: \"base64\"` and standard base64 in\n `content`. Pass it unchanged to Drive `create_file` as `contentBase64`,\n with `name: filename` and `mimeType`. File creation is a write: use\n `call_destructive_tool` or a program on a writable endpoint for that step.\n- Downloads, text and binary, are limited to 1 MiB. Declared size is\n checked before a separate download. Parsed forwarded emails check the sum\n of descendant sizes before downloading any stored body, and return that\n sum as `size`. Attachment data must match both the message part's declared\n size and the download response's size when supplied; inconsistent payloads\n fail rather than return incomplete files. Binary content must also fit the\n whole-result `maxBytes`, which defaults to 192 KiB. `format: \"unavailable\"`\n returns a note and no content when it cannot fit. Raising `maxChars`\n or `maxBytes` cannot bypass the download cap.\n- Raise `maxBytes` only for a direct `call_tool` read, from 64 KiB up to\n 4 MiB, as with Drive `get_file_content`. `execute_code` cannot receive\n large results; keep the default there. Direct-call results may be paged.\n\n## Drafting\n\n- `create_draft` saves a draft and never sends it; tell the person it is\n waiting in Drafts. There is no send, delete, or label tool.\n- To reply, pass `replyToMessageId`: the draft joins that thread with\n In-Reply-To and References set, and subject (Re: …) and `to` default\n from that message's Reply-To, else its From. Keep the Re: subject or Gmail\n may start a new thread.\n- `update_draft` replaces the body entirely; send the full new text.\n From, To, Cc, Bcc, Reply-To, Subject, the thread, and reply headers are\n kept unless restated; no other header is. A draft with attachments or\n inline images is refused, unchanged — Gmail's update would delete them.\n- Gmail sets From to the mailbox itself; there is no From argument.\n",
3
+ "content": "# Gmail usage (draft-only)\n\nActs as the signed-in person's own Gmail through Workspace delegation: read mail and write drafts, never send.\n\nConnection purpose: \n\n## Whose mailbox\n\nEvery call reads and writes the mailbox deployment config maps the caller\nto; an agent acts as the owner of the pool it came through, unless the deployment fixes one shared mailbox that every caller, agent or person, acts as. No argument\nnames a mailbox. A call with none mapped fails `auth_required`; only an operator can change\nthat.\n\n## Reading\n\n- `search_threads` takes Gmail search syntax; reuse the person's own\n phrasing (`from:`, `subject:`, `newer_than:7d`, `is:unread`,\n `label:`). Page with `page.nextCursor` and the same query.\n- `get_thread` returns messages oldest first with bodies capped by\n `maxBodyChars`; a cut body ends with a truncation marker. A long thread\n pages: follow `page.nextCursor` until `hasMore` is false, with the same\n arguments. `conflict` means the thread changed under the cursor; read it\n again from the start. Reduce inside\n `execute_code` before returning a long thread.\n- Results default to at most 192 KiB; `list_labels` pages the same way.\n- `get_message` and `get_thread` list attachment metadata. An attached or\n forwarded email is an attachment, not the body.\n- `bodyFormat: \"unavailable\"` means Gmail stored the body apart from the\n message and it is too large to read here; say so rather than summarizing.\n- `get_message`, each `get_thread` message, and `get_draft` return `bodyNote`\n when decoding falls back to UTF-8 for an unsupported charset. The note\n survives character and result-byte truncation, including `maxBodyChars: 0`\n when inline body data is decoded. It counts toward the result's byte limit.\n\n## Attachments\n\n- `get_attachment` takes `messageId` and the listing's immutable `partId`.\n Prefer `partId`, which stays stable across message reads, including an\n empty id for a root attachment. `attachmentId` is an alternative that\n Gmail can rotate between reads. When both are supplied, `partId` wins and\n the fresh message's download id is used. Read the listing again after\n an unknown-id refusal; never store an attachmentId for later use.\n- Small attachments can have inline data and no attachmentId. Use their\n partId too. Only carried attachment parts can be read, never arbitrary\n message body parts or blind download ids.\n- Text, JSON, XML, calendar invites, CSV, and `message/rfc822` return\n `format: \"text\"` and `content` decoded in the declared charset. An unsupported\n charset produces an explicit `note` that decoding used UTF-8. Message bodies\n return the notice separately as `bodyNote`.\n Parsed forwarded emails render From, To, Date, Subject, and their own\n plain body, else HTML converted to text. Raw forwarded data is preferred\n when present. A forwarded email without readable text returns an\n `unavailable` note.\n `maxChars` defaults to 12,000 code points, at most 100,000. A character\n or byte cut ends with a truncation marker and `contentTruncated: true`.\n Reduce attachment text inside `execute_code` before returning it.\n- Binary attachments return `format: \"base64\"` and standard base64 in\n `content`. Pass it unchanged to Drive `create_file` as `contentBase64`,\n with `name: filename` and `mimeType`. File creation is a write: use\n `call_destructive_tool` or a program on a writable endpoint for that step.\n- Downloads, text and binary, are limited to 1 MiB. Declared size is\n checked before a separate download. Parsed forwarded emails check the sum\n of descendant sizes before downloading any stored body, and return that\n sum as `size`. Attachment data must match both the message part's declared\n size and the download response's size when supplied; inconsistent payloads\n fail rather than return incomplete files. Binary content must also fit the\n whole-result `maxBytes`, which defaults to 192 KiB. `format: \"unavailable\"`\n returns a note and no content when it cannot fit. Raising `maxChars`\n or `maxBytes` cannot bypass the download cap.\n- Raise `maxBytes` only for a direct `call_tool` read, from 64 KiB up to\n 4 MiB, as with Drive `get_file_content`. `execute_code` cannot receive\n large results; keep the default there. Direct-call results may be paged.\n\n## Drafting\n\n- `create_draft` saves a draft and never sends it; tell the person it is\n waiting in Drafts. There is no send, delete, or label tool.\n- To reply, pass `replyToMessageId`: the draft joins that thread with\n In-Reply-To and References set, and subject (Re: …) and `to` default\n from that message's Reply-To, else its From. Keep the Re: subject or Gmail\n may start a new thread.\n- `update_draft` replaces the body entirely; send the full new text.\n From, To, Cc, Bcc, Reply-To, Subject, the thread, and reply headers are\n kept unless restated; no other header is. A draft with attachments or\n inline images is refused, unchanged — Gmail's update would delete them.\n- Gmail sets From to the mailbox itself; there is no From argument.\n",
4
4
  "instructionsHeading": "Connection instructions",
5
5
  "fragments": {
6
6
  "guide_0": "# Gmail usage (draft-only)\n\nActs as the signed-in person's own Gmail through Workspace delegation: read mail and write drafts, never send.\n\nConnection purpose: ",
7
- "guide_1": "\n\n## Whose mailbox\n\nEvery call reads and writes the mailbox deployment config maps the caller\nto; an agent acts as the owner of the pool it came through, unless the deployment fixes one shared mailbox that every caller, agent or person, acts as. No argument\nnames a mailbox. A call with none mapped, or from an agent whose pool has no\nowner, fails `auth_required`; only an operator can change either.\n\n## Reading\n\n- `search_threads` takes Gmail search syntax; reuse the person's own\n phrasing (`from:`, `subject:`, `newer_than:7d`, `is:unread`,\n `label:`). Page with `page.nextCursor` and the same query.\n- `get_thread` returns messages oldest first with bodies capped by\n `maxBodyChars`; a cut body ends with a truncation marker. A long thread\n pages: follow `page.nextCursor` until `hasMore` is false, with the same\n arguments. `conflict` means the thread changed under the cursor; read it\n again from the start. Reduce inside\n `execute_code` before returning a long thread.\n- Results default to at most 192 KiB; `list_labels` pages the same way.\n- `get_message` and `get_thread` list attachment metadata. An attached or\n forwarded email is an attachment, not the body.\n- `bodyFormat: \"unavailable\"` means Gmail stored the body apart from the\n message and it is too large to read here; say so rather than summarizing.\n- `get_message`, each `get_thread` message, and `get_draft` return `bodyNote`\n when decoding falls back to UTF-8 for an unsupported charset. The note\n survives character and result-byte truncation, including `maxBodyChars: 0`\n when inline body data is decoded. It counts toward the result's byte limit.\n\n## Attachments\n\n- `get_attachment` takes `messageId` and the listing's immutable `partId`.\n Prefer `partId`, which stays stable across message reads, including an\n empty id for a root attachment. `attachmentId` is an alternative that\n Gmail can rotate between reads. When both are supplied, `partId` wins and\n the fresh message's download id is used. Read the listing again after\n an unknown-id refusal; never store an attachmentId for later use.\n- Small attachments can have inline data and no attachmentId. Use their\n partId too. Only carried attachment parts can be read, never arbitrary\n message body parts or blind download ids.\n- Text, JSON, XML, calendar invites, CSV, and `message/rfc822` return\n `format: \"text\"` and `content` decoded in the declared charset. An unsupported\n charset produces an explicit `note` that decoding used UTF-8. Message bodies\n return the notice separately as `bodyNote`.\n Parsed forwarded emails render From, To, Date, Subject, and their own\n plain body, else HTML converted to text. Raw forwarded data is preferred\n when present. A forwarded email without readable text returns an\n `unavailable` note.\n `maxChars` defaults to 12,000 code points, at most 100,000. A character\n or byte cut ends with a truncation marker and `contentTruncated: true`.\n Reduce attachment text inside `execute_code` before returning it.\n- Binary attachments return `format: \"base64\"` and standard base64 in\n `content`. Pass it unchanged to Drive `create_file` as `contentBase64`,\n with `name: filename` and `mimeType`. File creation is a write: use\n `call_destructive_tool` or a program on a writable endpoint for that step.\n- Downloads, text and binary, are limited to 1 MiB. Declared size is\n checked before a separate download. Parsed forwarded emails check the sum\n of descendant sizes before downloading any stored body, and return that\n sum as `size`. Attachment data must match both the message part's declared\n size and the download response's size when supplied; inconsistent payloads\n fail rather than return incomplete files. Binary content must also fit the\n whole-result `maxBytes`, which defaults to 192 KiB. `format: \"unavailable\"`\n returns a note and no content when it cannot fit. Raising `maxChars`\n or `maxBytes` cannot bypass the download cap.\n- Raise `maxBytes` only for a direct `call_tool` read, from 64 KiB up to\n 4 MiB, as with Drive `get_file_content`. `execute_code` cannot receive\n large results; keep the default there. Direct-call results may be paged.\n\n## Drafting\n\n- `create_draft` saves a draft and never sends it; tell the person it is\n waiting in Drafts. There is no send, delete, or label tool.\n- To reply, pass `replyToMessageId`: the draft joins that thread with\n In-Reply-To and References set, and subject (Re: …) and `to` default\n from that message's Reply-To, else its From. Keep the Re: subject or Gmail\n may start a new thread.\n- `update_draft` replaces the body entirely; send the full new text.\n From, To, Cc, Bcc, Reply-To, Subject, the thread, and reply headers are\n kept unless restated; no other header is. A draft with attachments or\n inline images is refused, unchanged — Gmail's update would delete them.\n- Gmail sets From to the mailbox itself; there is no From argument.\n"
7
+ "guide_1": "\n\n## Whose mailbox\n\nEvery call reads and writes the mailbox deployment config maps the caller\nto; an agent acts as the owner of the pool it came through, unless the deployment fixes one shared mailbox that every caller, agent or person, acts as. No argument\nnames a mailbox. A call with none mapped fails `auth_required`; only an operator can change\nthat.\n\n## Reading\n\n- `search_threads` takes Gmail search syntax; reuse the person's own\n phrasing (`from:`, `subject:`, `newer_than:7d`, `is:unread`,\n `label:`). Page with `page.nextCursor` and the same query.\n- `get_thread` returns messages oldest first with bodies capped by\n `maxBodyChars`; a cut body ends with a truncation marker. A long thread\n pages: follow `page.nextCursor` until `hasMore` is false, with the same\n arguments. `conflict` means the thread changed under the cursor; read it\n again from the start. Reduce inside\n `execute_code` before returning a long thread.\n- Results default to at most 192 KiB; `list_labels` pages the same way.\n- `get_message` and `get_thread` list attachment metadata. An attached or\n forwarded email is an attachment, not the body.\n- `bodyFormat: \"unavailable\"` means Gmail stored the body apart from the\n message and it is too large to read here; say so rather than summarizing.\n- `get_message`, each `get_thread` message, and `get_draft` return `bodyNote`\n when decoding falls back to UTF-8 for an unsupported charset. The note\n survives character and result-byte truncation, including `maxBodyChars: 0`\n when inline body data is decoded. It counts toward the result's byte limit.\n\n## Attachments\n\n- `get_attachment` takes `messageId` and the listing's immutable `partId`.\n Prefer `partId`, which stays stable across message reads, including an\n empty id for a root attachment. `attachmentId` is an alternative that\n Gmail can rotate between reads. When both are supplied, `partId` wins and\n the fresh message's download id is used. Read the listing again after\n an unknown-id refusal; never store an attachmentId for later use.\n- Small attachments can have inline data and no attachmentId. Use their\n partId too. Only carried attachment parts can be read, never arbitrary\n message body parts or blind download ids.\n- Text, JSON, XML, calendar invites, CSV, and `message/rfc822` return\n `format: \"text\"` and `content` decoded in the declared charset. An unsupported\n charset produces an explicit `note` that decoding used UTF-8. Message bodies\n return the notice separately as `bodyNote`.\n Parsed forwarded emails render From, To, Date, Subject, and their own\n plain body, else HTML converted to text. Raw forwarded data is preferred\n when present. A forwarded email without readable text returns an\n `unavailable` note.\n `maxChars` defaults to 12,000 code points, at most 100,000. A character\n or byte cut ends with a truncation marker and `contentTruncated: true`.\n Reduce attachment text inside `execute_code` before returning it.\n- Binary attachments return `format: \"base64\"` and standard base64 in\n `content`. Pass it unchanged to Drive `create_file` as `contentBase64`,\n with `name: filename` and `mimeType`. File creation is a write: use\n `call_destructive_tool` or a program on a writable endpoint for that step.\n- Downloads, text and binary, are limited to 1 MiB. Declared size is\n checked before a separate download. Parsed forwarded emails check the sum\n of descendant sizes before downloading any stored body, and return that\n sum as `size`. Attachment data must match both the message part's declared\n size and the download response's size when supplied; inconsistent payloads\n fail rather than return incomplete files. Binary content must also fit the\n whole-result `maxBytes`, which defaults to 192 KiB. `format: \"unavailable\"`\n returns a note and no content when it cannot fit. Raising `maxChars`\n or `maxBytes` cannot bypass the download cap.\n- Raise `maxBytes` only for a direct `call_tool` read, from 64 KiB up to\n 4 MiB, as with Drive `get_file_content`. `execute_code` cannot receive\n large results; keep the default there. Direct-call results may be paged.\n\n## Drafting\n\n- `create_draft` saves a draft and never sends it; tell the person it is\n waiting in Drafts. There is no send, delete, or label tool.\n- To reply, pass `replyToMessageId`: the draft joins that thread with\n In-Reply-To and References set, and subject (Re: …) and `to` default\n from that message's Reply-To, else its From. Keep the Re: subject or Gmail\n may start a new thread.\n- `update_draft` replaces the body entirely; send the full new text.\n From, To, Cc, Bcc, Reply-To, Subject, the thread, and reply headers are\n kept unless restated; no other header is. A draft with attachments or\n inline images is refused, unchanged — Gmail's update would delete them.\n- Gmail sets From to the mailbox itself; there is no From argument.\n"
8
8
  }
9
9
  };
@@ -0,0 +1,24 @@
1
+ import { type RemoteMcpAuth } from "../../connectors/remote-mcp.js";
2
+ import { type ProviderOptions } from "../../provider.js";
3
+ /** AU hosted workspaces are not supported by Intercom's MCP server. */
4
+ export type IntercomRegion = "us" | "eu";
5
+ /** Reviewed Streamable HTTP endpoints; never route EU workspace data through US. */
6
+ export declare const INTERCOM_MCP_ENDPOINTS: Readonly<Record<IntercomRegion, string>>;
7
+ export interface IntercomOptions extends ProviderOptions {
8
+ /** Required: match the workspace's hosted region. AU is unavailable. */
9
+ region: IntercomRegion;
10
+ /**
11
+ * Defaults to OAuth with dynamic registration and discovered scopes (mcp,
12
+ * operator). Non-loopback OAuth callbacks need Intercom's allowlist approval.
13
+ * A credential sends an operator-managed API token as Authorization: Bearer.
14
+ * Fin Operator tools require teammate OAuth and are unavailable to API tokens.
15
+ */
16
+ auth?: RemoteMcpAuth;
17
+ /**
18
+ * Defaults to personal for OAuth, shared for an explicitly supplied token.
19
+ * OAuth runs with the authorizing teammate's permissions and seat rules.
20
+ */
21
+ authScope?: "shared" | "personal";
22
+ }
23
+ /** A maintained Intercom hosted-MCP connection for a single workspace region. */
24
+ export declare const intercom: import("../../provider.js").ProviderFactory<IntercomOptions>;
@@ -0,0 +1,121 @@
1
+ import { remoteMcp, withCredentialDefaults } from "../../connectors/remote-mcp.js";
2
+ import { keys, optionsOf } from "../../config-schema.js";
3
+ import { REMOTE_MCP_AUTH } from "../../connectors/option-shapes.js";
4
+ import { defineProvider, PROVIDER_COMMON } from "../../provider.js";
5
+ import { skill } from "./skill.generated.js";
6
+ /** Reviewed Streamable HTTP endpoints; never route EU workspace data through US. */
7
+ export const INTERCOM_MCP_ENDPOINTS = {
8
+ us: "https://mcp.intercom.com/mcp",
9
+ eu: "https://mcp.eu.intercom.com/mcp",
10
+ };
11
+ /**
12
+ * Official-document review, 2026-10-19: 14 core tools and five optional Fin
13
+ * Operator tools. Both regions refuse anonymous tools/list (401); no schemas
14
+ * or schema digests are fabricated. Live tools/list owns schemas and results.
15
+ */
16
+ const CLASSIFICATION = {
17
+ tools: {
18
+ search: { verdict: "read", reason: "Searches existing conversations or contacts through the query DSL." },
19
+ fetch: { verdict: "read", reason: "Retrieves an existing conversation, contact or company by its prefixed id." },
20
+ search_conversations: { verdict: "read", reason: "Filters existing conversations without changing their state." },
21
+ get_conversation: { verdict: "read", reason: "Reads a conversation, its parts and metadata." },
22
+ search_contacts: { verdict: "read", reason: "Filters existing contacts without modifying their profiles." },
23
+ get_contact: { verdict: "read", reason: "Reads an existing contact's attributes, location and activity." },
24
+ list_companies: { verdict: "read", reason: "Lists or filters existing companies." },
25
+ get_company: { verdict: "read", reason: "Reads an existing company, its attributes, segments and tags." },
26
+ list_articles: { verdict: "read", reason: "Lists existing Help Center articles." },
27
+ search_articles: { verdict: "read", reason: "Searches existing Help Center articles." },
28
+ get_article: { verdict: "read", reason: "Reads an existing article's HTML body and metadata." },
29
+ create_article: { verdict: "write", reason: "Creates a Help Center article, potentially published immediately." },
30
+ update_article: {
31
+ verdict: "destructive",
32
+ reason: "Overwrites fields, content or publication state on an existing article.",
33
+ },
34
+ add_internal_note: {
35
+ verdict: "destructive",
36
+ reason: "Appends a teammate-only note and may unsnooze an existing conversation, changing its state to open.",
37
+ },
38
+ // Fin tools are OAuth-only and gated by workspace availability and teammate
39
+ // permission: https://developers.intercom.com/docs/guides/fin-operator-mcp#how-permissions-work
40
+ send_operator_message: {
41
+ optional: true,
42
+ verdict: "destructive",
43
+ reason: "Starts asynchronous Operator work and consumes usage; Intercom explicitly marks this destructive and non-idempotent.",
44
+ },
45
+ get_operator_response: {
46
+ optional: true,
47
+ verdict: "read",
48
+ reason: "Reads the current Operator chat response without starting work.",
49
+ },
50
+ get_proposal_details: {
51
+ optional: true,
52
+ verdict: "read",
53
+ reason: "Reads pending proposals and their before/after content without applying them.",
54
+ },
55
+ apply_proposals: {
56
+ optional: true,
57
+ verdict: "destructive",
58
+ reason: "Creates, overwrites, publishes or deletes live workspace configuration; some changes cannot be undone.",
59
+ },
60
+ reject_proposals: {
61
+ optional: true,
62
+ verdict: "destructive",
63
+ reason: "Discards pending proposals. Conservatively treats deletion of proposal state as destructive despite Intercom's non-destructive hint.",
64
+ },
65
+ },
66
+ };
67
+ const OPTIONS = optionsOf()({
68
+ ...PROVIDER_COMMON,
69
+ ...keys("region"),
70
+ auth: REMOTE_MCP_AUTH,
71
+ });
72
+ /** A maintained Intercom hosted-MCP connection for a single workspace region. */
73
+ export const intercom = defineProvider({
74
+ name: "intercom",
75
+ title: "Intercom",
76
+ kind: "mcp",
77
+ readme: "Intercom",
78
+ skill,
79
+ options: OPTIONS,
80
+ classify: CLASSIFICATION,
81
+ bundle: {
82
+ baselineGzip: 170245,
83
+ maxGzip: 230245,
84
+ note: "./providers/intercom initial measured hosted-MCP baseline is 170,245 B gzip; the cap reserves 60,000 B of headroom.",
85
+ },
86
+ create(id, options, provider) {
87
+ if (options.region !== "us" && options.region !== "eu") {
88
+ throw new Error(`intercom("${id}") requires region "us" or "eu" matching the workspace; AU is not supported.`);
89
+ }
90
+ const auth = options.auth ?? { type: "oauth" };
91
+ const authScope = options.authScope ?? (auth.type === "oauth" ? "personal" : "shared");
92
+ return remoteMcp(id, {
93
+ ...provider.connectorOptions,
94
+ url: INTERCOM_MCP_ENDPOINTS[options.region],
95
+ title: options.title ?? `Intercom (${options.region.toUpperCase()})`,
96
+ description: `Intercom conversations, contacts, companies, articles and optional Fin Operator — ${options.purpose}`,
97
+ authScope,
98
+ auth: withCredentialDefaults(auth, {
99
+ credential: {
100
+ label: "Intercom API token",
101
+ description: "An Intercom API access token with only the workspace permissions this connection needs. Stored encrypted and sent as Authorization: Bearer. Fin Operator requires OAuth instead.",
102
+ placeholder: "Paste Intercom API token",
103
+ },
104
+ }),
105
+ requireHttps: true,
106
+ classify: provider.classify,
107
+ usageGuide: provider.usageGuide({
108
+ context: [
109
+ `Customer support workspace in ${options.region.toUpperCase()}: ${options.purpose}. Article creation, article updates, internal notes and Operator actions can write.`,
110
+ authScope === "personal"
111
+ ? "Each person authorizes separately; OAuth uses that teammate's workspace permissions and seat rules."
112
+ : "This connector shares one workspace authorization; all callers act through its grant.",
113
+ auth.type === "oauth"
114
+ ? "OAuth discovers the mcp and operator scopes. Fin Operator additionally requires workspace availability and teammate permission."
115
+ : "Bearer API token access: Fin Operator tools are unavailable.",
116
+ ],
117
+ summary: `Intercom ${options.region.toUpperCase()}: sensitive support data, article/note writes and optional Fin Operator.`,
118
+ }),
119
+ });
120
+ },
121
+ });