@zackbart/connecta 0.32.0 → 0.34.0

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 (158) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/README.md +95 -116
  3. package/SECURITY.md +1 -1
  4. package/bin/connecta.mjs +24 -6
  5. package/bin/version.mjs +1 -1
  6. package/dist/access-tokens.d.ts +167 -23
  7. package/dist/access-tokens.js +676 -140
  8. package/dist/activity.d.ts +3 -0
  9. package/dist/agent-identity.js +47 -0
  10. package/dist/auth/cloudflare-access.js +13 -5
  11. package/dist/branding.d.ts +7 -0
  12. package/dist/branding.js +76 -9
  13. package/dist/call-admission-context.js +15 -0
  14. package/dist/call-admission.js +30 -43
  15. package/dist/call-recovery.js +6 -2
  16. package/dist/catalog-service.js +15 -1
  17. package/dist/config-value-sources.js +16 -9
  18. package/dist/config.d.ts +48 -75
  19. package/dist/config.js +144 -36
  20. package/dist/connector-access.d.ts +4 -34
  21. package/dist/connector-access.js +15 -99
  22. package/dist/connector-caller.d.ts +7 -1
  23. package/dist/connector-caller.js +4 -2
  24. package/dist/connectors/api-connector.d.ts +27 -4
  25. package/dist/connectors/api-connector.js +2 -0
  26. package/dist/connectors/remote-mcp.js +2 -0
  27. package/dist/describe-config.d.ts +31 -7
  28. package/dist/describe-config.js +8 -17
  29. package/dist/execute.js +1 -11
  30. package/dist/fix-prompt.js +2 -2
  31. package/dist/index.d.ts +4 -3
  32. package/dist/index.js +30 -10
  33. package/dist/invocation.js +5 -3
  34. package/dist/membership.d.ts +53 -0
  35. package/dist/membership.js +121 -0
  36. package/dist/module-contracts.d.ts +23 -6
  37. package/dist/oauth-callback-outcome.js +2 -2
  38. package/dist/operator-record.d.ts +2 -2
  39. package/dist/operator-record.js +2 -4
  40. package/dist/operator-ui/agents.d.ts +74 -0
  41. package/dist/operator-ui/agents.js +104 -0
  42. package/dist/operator-ui/config-summary.js +562 -0
  43. package/dist/operator-ui/connection-map.js +375 -0
  44. package/dist/operator-ui/contract.d.ts +149 -28
  45. package/dist/operator-ui/endpoints.js +221 -0
  46. package/dist/operator-ui/fix-prompts.js +5 -5
  47. package/dist/operator-ui/generated.js +5 -4
  48. package/dist/operator-ui/member-page.js +97 -0
  49. package/dist/operator-ui/model.d.ts +14 -6
  50. package/dist/operator-ui/overview.js +134 -0
  51. package/dist/operator-ui/view.d.ts +107 -37
  52. package/dist/operator-ui/view.js +163 -86
  53. package/dist/page-styles.js +5 -3
  54. package/dist/provider.d.ts +61 -8
  55. package/dist/provider.js +46 -7
  56. package/dist/providers/_shared/google/workspace.d.ts +56 -17
  57. package/dist/providers/_shared/google/workspace.js +34 -8
  58. package/dist/providers/_shared/rest/tools.js +2 -2
  59. package/dist/providers/_shared/rest/value-safety.d.ts +50 -0
  60. package/dist/providers/_shared/rest/value-safety.js +699 -7
  61. package/dist/providers/ccb/index.js +2 -2
  62. package/dist/providers/cloudflare/index.js +3 -3
  63. package/dist/providers/cloudflare/rest.js +25 -3
  64. package/dist/providers/cloudflare/skill.generated.js +1 -1
  65. package/dist/providers/cloudflare/value-safety.js +32 -0
  66. package/dist/providers/docs/index.js +4 -4
  67. package/dist/providers/docs/skill.generated.js +2 -2
  68. package/dist/providers/drive/index.js +4 -4
  69. package/dist/providers/drive/skill.generated.js +2 -2
  70. package/dist/providers/forms/index.js +4 -4
  71. package/dist/providers/forms/skill.generated.js +2 -2
  72. package/dist/providers/gmail/index.js +11 -9
  73. package/dist/providers/gmail/skill.generated.js +2 -2
  74. package/dist/providers/mixpanel/index.js +1 -1
  75. package/dist/providers/mixpanel/skill.generated.js +3 -3
  76. package/dist/providers/notion/index.d.ts +7 -0
  77. package/dist/providers/notion/index.js +100 -11
  78. package/dist/providers/notion/rest.js +45 -5
  79. package/dist/providers/notion/skill.generated.js +2 -2
  80. package/dist/providers/notion/value-safety.js +30 -5
  81. package/dist/providers/overflow/index.js +2 -2
  82. package/dist/providers/overflow/skill.generated.js +2 -2
  83. package/dist/providers/planning-center/index.js +2 -2
  84. package/dist/providers/planning-center/skill.generated.js +4 -4
  85. package/dist/providers/revenuecat/skill.generated.js +2 -2
  86. package/dist/providers/sheets/index.d.ts +3 -0
  87. package/dist/providers/sheets/index.js +7 -4
  88. package/dist/providers/sheets/skill.generated.js +2 -2
  89. package/dist/providers/slides/index.js +4 -4
  90. package/dist/providers/slides/skill.generated.js +2 -2
  91. package/dist/providers/stripe/rest.js +9 -2
  92. package/dist/providers/stripe/skill.generated.js +1 -1
  93. package/dist/providers/stripe/value-safety.js +22 -0
  94. package/dist/providers/tithely/index.js +1 -1
  95. package/dist/providers/vercel/index.js +1 -1
  96. package/dist/providers/vercel/rest.js +20 -2
  97. package/dist/providers/vercel/skill.generated.js +2 -2
  98. package/dist/providers/vercel/value-safety.js +22 -0
  99. package/dist/registry.d.ts +2 -3
  100. package/dist/registry.js +23 -9
  101. package/dist/routes/access-tokens.js +82 -75
  102. package/dist/routes/activity-disclosure.js +17 -18
  103. package/dist/routes/activity.js +34 -54
  104. package/dist/routes/actor-labels.js +33 -4
  105. package/dist/routes/agent-path.js +43 -0
  106. package/dist/routes/connect.js +16 -12
  107. package/dist/routes/credentials.js +16 -40
  108. package/dist/routes/mcp.js +161 -104
  109. package/dist/routes/oauth-management.js +11 -5
  110. package/dist/routes/oauth.js +5 -11
  111. package/dist/routes/operator.js +24 -23
  112. package/dist/routes/shared.d.ts +60 -20
  113. package/dist/routes/shared.js +187 -96
  114. package/dist/routes/ui-config.js +437 -97
  115. package/dist/routes/ui-data.js +2 -2
  116. package/dist/routes/ui.js +9 -16
  117. package/dist/runtime/call-admission.js +9 -0
  118. package/dist/server.js +5 -0
  119. package/dist/skills.js +10 -0
  120. package/dist/storage/keys.js +9 -1
  121. package/dist/storage/sql.js +5 -1
  122. package/dist/types.d.ts +29 -7
  123. package/dist/ui.d.ts +1 -1
  124. package/dist/ui.js +23 -16
  125. package/dist/usage-guide.js +1 -1
  126. package/dist/version.d.ts +1 -1
  127. package/dist/version.js +1 -1
  128. package/dist/workspace-address.js +9 -0
  129. package/dist/workspace-subject.js +23 -0
  130. package/documentation/README.md +47 -23
  131. package/documentation/architecture.md +371 -61
  132. package/documentation/auth.md +897 -249
  133. package/documentation/code-mode.md +37 -16
  134. package/documentation/deploying.md +126 -25
  135. package/documentation/integrating.md +97 -23
  136. package/documentation/meta-tools.md +50 -31
  137. package/documentation/migration-0.33.md +352 -0
  138. package/documentation/migration-0.34.md +67 -0
  139. package/documentation/operating.md +3 -1
  140. package/documentation/operator-tests.md +147 -34
  141. package/documentation/operator-ui.md +519 -64
  142. package/documentation/provider-auth-migration.md +37 -23
  143. package/documentation/provider-migration-0.29.md +2 -2
  144. package/examples/worker/AGENTS.md +37 -9
  145. package/examples/worker/README.md +127 -61
  146. package/examples/worker/src/connecta.config.ts +40 -23
  147. package/examples/worker/wrangler.jsonc +4 -1
  148. package/package.json +19 -18
  149. package/templates/node/.env.example +3 -0
  150. package/templates/node/AGENTS.md +33 -6
  151. package/templates/node/Dockerfile +2 -2
  152. package/templates/node/README.md +80 -52
  153. package/templates/node/docker-compose.yml +3 -2
  154. package/templates/node/package.json +4 -3
  155. package/templates/node/src/adopt-token.ts +23 -0
  156. package/templates/node/src/connecta.config.ts +28 -12
  157. package/templates/node/src/rotate-token.ts +17 -0
  158. package/templates/node/src/provision-token.ts +0 -17
@@ -1,12 +1,12 @@
1
1
  // Generated by scripts/generate-providers.mjs; do not edit.
2
2
  export const skill = {
3
- "content": "# Planning Center usage\n\nPlanning Center Online church management — People, Services, Groups, Check-Ins, Calendar, Registrations, Giving, Publishing, Webhooks.\n\n## Identity first\n\n- A person id is the join key across every product: the id `search_people` returns is the `personId` for Services schedules, Groups memberships, Giving donations, and Check-Ins. Resolve ids with list tools; never guess one.\n- A person id that used to resolve and now answers `not_found` was most likely merged. Read `/people/v2/person_mergers` with `where[person_to_remove_id]` and follow `person_to_keep_id`.\n- `get_me` names the user this token acts as. Every read and write runs with that user's permissions, product by product; a `provider_permission_denied` means an administrator must grant that user access to the product or record.\n\n## Results and paging\n\n- Records keep Planning Center's own snake_case attribute names; a relationship appears as `<relationship>_id`. Pass `raw: true` for the untouched JSON:API resources and `included`.\n- Lists page by offset: `perPage` up to 100, then pass `page.nextOffset` back as `offset` while `page.hasMore` is true. `page.totalCount` answers \"how many\" without paging — request `perPage: 1`.\n- A bare date in a date filter is the organization's local day; pass a full timestamp for exact bounds. Name filters are exact; end one with `%` for a prefix match.\n- `get_plan` fetches the plan, its items, and its team in one call and sets `itemsTruncated`/`teamMembersTruncated` past 100 rows; page the rest with `pco_api_get`.\n\n## What each product lets the API write\n\n- People: full read/write. `update_person` with `status: inactive` archives reversibly; a DELETE is permanent and only reachable through `pco_api_mutate`. `run_list` can fire list automations.\n- Services: full read/write. A plan's dates come from its plan times. Scheduling needs a team.\n- Groups: memberships and group settings are writable; groups cannot be created, and events and attendance are read-only.\n- Check-Ins and Registrations: read-only. Nobody can be checked in or registered through the API.\n- Calendar: events, instances, and times are read-only; tags, resources, rooms, and folders are writable through `pco_api_mutate`.\n- Giving: the named tools only read, amounts in integer cents. Creating or editing donations, refunds, and committing a batch move money and go through `pco_api_mutate`.\n- Publishing and the `api` product (organization, OAuth applications, personal access tokens) have no named tools; use the hatches.\n\n## The hatches\n\n- `pco_api_get` reaches any GET; `pco_api_mutate` any POST, PATCH, or DELETE, always approval-gated. Paths start `/<app>/v2`, exactly as Planning Center documents them; the connector owns host, auth, content type, and the `X-PCO-API-Version` header.\n- Bodies are JSON:API documents: `{\"data\":{\"type\":\"Email\",\"attributes\":{...}}}`. `null` clears an attribute; an omitted key is left alone.\n- Each product is pinned to a reviewed API version: people 2026-06-04, services 2018-11-01, groups 2023-07-10, check-ins 2025-05-28, calendar 2026-06-22, registrations 2025-05-01, giving 2019-10-18, publishing 2024-03-25, webhooks 2022-10-20, api 2026-09-24. Pass `version` only for an endpoint those versions lack.\n- Query parameters are name/value pairs with literal brackets: `where[status]`, `include`, `order`, `filter`, `fields[Person]`. Every list response's `meta` names the `can_query_by`, `can_order_by`, and `can_include` values for that endpoint; an unknown `where` key is ignored rather than refused.\n- File uploads go to a separate host (upload.planningcenteronline.com), which this connection does not reach.\n\n## Rate limits\n\nPlanning Center allows 100 requests per 20 seconds per user, shared by every integration running as that user. This connection admits 100 calls per 20 seconds, five at a time, per runtime — an approximation, not a guarantee. A `rate_limited` failure carries Planning Center's wait. Filter server-side and use `totalCount` rather than paging to count.\n",
3
+ "content": "# Planning Center usage\n\nPlanning Center Online church management — People, Services, Groups, Check-Ins, Calendar, Registrations, Giving, Publishing, Webhooks.\n\n## Identity first\n\n- A person id is the join key across every product: the id `search_people` returns is the person id (`personId` argument, `person_id` field) for Services schedules, Groups memberships, Giving donations, and Check-Ins. Resolve ids with list tools; never guess one.\n- A person id that used to resolve and now answers `not_found` was most likely merged. Read `/people/v2/person_mergers` with `where[person_to_remove_id]` and follow `person_to_keep_id`.\n- `get_me` names the user this token acts as. Every read and write runs with that user's permissions, product by product; a `provider_permission_denied` means an administrator must grant that user access to the product or record.\n\n## Results and paging\n\n- Records keep Planning Center's own snake_case attribute names; a relationship appears as `<relationship>_id`. Pass `raw: true` for the untouched JSON:API resources and `included`; `get_me` and `list_webhook_subscriptions` have no raw mode.\n- Lists page by offset: `perPage` up to 100, then pass `page.nextOffset` back as `offset` while `page.hasMore` is true. `page.totalCount` answers \"how many\" without paging — request `perPage: 1`.\n- A bare date in a date filter is the organization's local day; pass a full timestamp for exact bounds. Name filters are exact; end one with `%` for a prefix match.\n- `get_plan` fetches the plan, its items, and its team in one call and sets `itemsTruncated`/`teamMembersTruncated` past 100 rows; page the rest with `pco_api_get`.\n\n## What each product lets the API write\n\n- People: full read/write. `update_person` with `status: inactive` archives reversibly; a DELETE is permanent and only reachable through `pco_api_mutate`. `run_list` can fire list automations.\n- Services: full read/write. A plan's dates come from its plan times. Scheduling needs a team.\n- Groups: memberships and group settings are writable; groups cannot be created, and events and attendance are read-only.\n- Check-Ins and Registrations: read-only. Nobody can be checked in or registered through the API.\n- Calendar: events, instances, and times are read-only; tags, resources, rooms, and folders are writable through `pco_api_mutate`.\n- Giving: the named tools only read, amounts in integer cents. Creating or editing donations, refunds, and committing a batch move money and go through `pco_api_mutate`.\n- Publishing and the `api` product (organization, OAuth applications, personal access tokens) have no named tools; use the hatches.\n\n## The hatches\n\n- `pco_api_get` reaches any GET; `pco_api_mutate` any POST, PATCH, or DELETE, always approval-gated. Paths start `/<app>/v2`, exactly as Planning Center documents them; the connector owns host, auth, content type, and the `X-PCO-API-Version` header.\n- Bodies are JSON:API documents: `{\"data\":{\"type\":\"Email\",\"attributes\":{...}}}`. `null` clears an attribute; an omitted key is left alone.\n- Each product is pinned to a reviewed API version: people 2026-06-04, services 2018-11-01, groups 2023-07-10, check-ins 2025-05-28, calendar 2026-06-22, registrations 2025-05-01, giving 2019-10-18, publishing 2024-03-25, webhooks 2022-10-20, api 2026-09-24. Pass `version` only for an endpoint those versions lack.\n- Query parameters are name/value pairs with literal brackets: `where[status]`, `include`, `order`, `filter`, `fields[Person]`. Every list response's `meta` names the `can_query_by`, `can_order_by`, and `can_include` values for that endpoint; an unknown `where` key is ignored rather than refused.\n- File uploads go to a separate host (upload.planningcenteronline.com), which this connection does not reach.\n\n## Rate limits\n\nPlanning Center allows 100 requests per 20 seconds per user, shared by every integration running as that user. This connection admits 100 calls per 20 seconds, five at a time, per runtime — an approximation, not a guarantee. A `rate_limited` failure carries Planning Center's wait. Filter server-side and use `totalCount` rather than paging to count.\n",
4
4
  "instructionsHeading": "Organization instructions",
5
5
  "fragments": {
6
6
  "heading": "# Planning Center usage\n\nPlanning Center Online church management — ",
7
7
  "purpose": " — for: ",
8
- "identity": "\n\n## Identity first\n\n- A person id is the join key across every product: the id `search_people` returns is the `personId` for ",
9
- "conventions": ". Resolve ids with list tools; never guess one.\n- A person id that used to resolve and now answers `not_found` was most likely merged. Read `/people/v2/person_mergers` with `where[person_to_remove_id]` and follow `person_to_keep_id`.\n- `get_me` names the user this token acts as. Every read and write runs with that user's permissions, product by product; a `provider_permission_denied` means an administrator must grant that user access to the product or record.\n\n## Results and paging\n\n- Records keep Planning Center's own snake_case attribute names; a relationship appears as `<relationship>_id`. Pass `raw: true` for the untouched JSON:API resources and `included`.\n- Lists page by offset: `perPage` up to 100, then pass `page.nextOffset` back as `offset` while `page.hasMore` is true. `page.totalCount` answers \"how many\" without paging — request `perPage: 1`.\n- A bare date in a date filter is the organization's local day; pass a full timestamp for exact bounds. Name filters are exact; end one with `%` for a prefix match.\n",
8
+ "identity": "\n\n## Identity first\n\n- A person id is the join key across every product: the id `search_people` returns is the person id (`personId` argument, `person_id` field) for ",
9
+ "conventions": ". Resolve ids with list tools; never guess one.\n- A person id that used to resolve and now answers `not_found` was most likely merged. Read `/people/v2/person_mergers` with `where[person_to_remove_id]` and follow `person_to_keep_id`.\n- `get_me` names the user this token acts as. Every read and write runs with that user's permissions, product by product; a `provider_permission_denied` means an administrator must grant that user access to the product or record.\n\n## Results and paging\n\n- Records keep Planning Center's own snake_case attribute names; a relationship appears as `<relationship>_id`. Pass `raw: true` for the untouched JSON:API resources and `included`; `get_me` and `list_webhook_subscriptions` have no raw mode.\n- Lists page by offset: `perPage` up to 100, then pass `page.nextOffset` back as `offset` while `page.hasMore` is true. `page.totalCount` answers \"how many\" without paging — request `perPage: 1`.\n- A bare date in a date filter is the organization's local day; pass a full timestamp for exact bounds. Name filters are exact; end one with `%` for a prefix match.\n",
10
10
  "plans": "- `get_plan` fetches the plan, its items, and its team in one call and sets `itemsTruncated`/`teamMembersTruncated` past 100 rows; page the rest with `pco_api_get`.\n",
11
11
  "writes": "\n## What each product lets the API write\n\n",
12
12
  "people": "- People: full read/write. `update_person` with `status: inactive` archives reversibly; a DELETE is permanent and only reachable through `pco_api_mutate`. `run_list` can fire list automations.\n",
@@ -25,6 +25,6 @@ export const skill = {
25
25
  "pins": ". Pass `version` only for an endpoint those versions lack.\n- Query parameters are name/value pairs with literal brackets: `where[status]`, `include`, `order`, `filter`, `fields[Person]`. Every list response's `meta` names the `can_query_by`, `can_order_by`, and `can_include` values for that endpoint; an unknown `where` key is ignored rather than refused.\n- File uploads go to a separate host (upload.planningcenteronline.com), which this connection does not reach.\n",
26
26
  "disabled": "- This deployment disables some products, and the hatches refuse their paths before sending. While any product is disabled they also refuse People lists, list results, rules, and conditions, by path or `include`, because a list's conditions can draw on any product; webhook event and delivery payloads; and any change to an existing webhook subscription, whose id does not name its product. A new subscription must name a reachable product's event.\n",
27
27
  "limits": "\n## Rate limits\n\nPlanning Center allows 100 requests per 20 seconds per user, shared by every integration running as that user. This connection admits 100 calls per 20 seconds, five at a time, per runtime — an approximation, not a guarantee. A `rate_limited` failure carries Planning Center's wait. Filter server-side and use `totalCount` rather than paging to count.\n",
28
- "published": "# Planning Center usage\n\nPlanning Center Online church management — People, Services, Groups, Check-Ins, Calendar, Registrations, Giving, Publishing, Webhooks.\n\n## Identity first\n\n- A person id is the join key across every product: the id `search_people` returns is the `personId` for Services schedules, Groups memberships, Giving donations, and Check-Ins. Resolve ids with list tools; never guess one.\n- A person id that used to resolve and now answers `not_found` was most likely merged. Read `/people/v2/person_mergers` with `where[person_to_remove_id]` and follow `person_to_keep_id`.\n- `get_me` names the user this token acts as. Every read and write runs with that user's permissions, product by product; a `provider_permission_denied` means an administrator must grant that user access to the product or record.\n\n## Results and paging\n\n- Records keep Planning Center's own snake_case attribute names; a relationship appears as `<relationship>_id`. Pass `raw: true` for the untouched JSON:API resources and `included`.\n- Lists page by offset: `perPage` up to 100, then pass `page.nextOffset` back as `offset` while `page.hasMore` is true. `page.totalCount` answers \"how many\" without paging — request `perPage: 1`.\n- A bare date in a date filter is the organization's local day; pass a full timestamp for exact bounds. Name filters are exact; end one with `%` for a prefix match.\n- `get_plan` fetches the plan, its items, and its team in one call and sets `itemsTruncated`/`teamMembersTruncated` past 100 rows; page the rest with `pco_api_get`.\n\n## What each product lets the API write\n\n- People: full read/write. `update_person` with `status: inactive` archives reversibly; a DELETE is permanent and only reachable through `pco_api_mutate`. `run_list` can fire list automations.\n- Services: full read/write. A plan's dates come from its plan times. Scheduling needs a team.\n- Groups: memberships and group settings are writable; groups cannot be created, and events and attendance are read-only.\n- Check-Ins and Registrations: read-only. Nobody can be checked in or registered through the API.\n- Calendar: events, instances, and times are read-only; tags, resources, rooms, and folders are writable through `pco_api_mutate`.\n- Giving: the named tools only read, amounts in integer cents. Creating or editing donations, refunds, and committing a batch move money and go through `pco_api_mutate`.\n- Publishing and the `api` product (organization, OAuth applications, personal access tokens) have no named tools; use the hatches.\n\n## The hatches\n\n- `pco_api_get` reaches any GET; `pco_api_mutate` any POST, PATCH, or DELETE, always approval-gated. Paths start `/<app>/v2`, exactly as Planning Center documents them; the connector owns host, auth, content type, and the `X-PCO-API-Version` header.\n- Bodies are JSON:API documents: `{\"data\":{\"type\":\"Email\",\"attributes\":{...}}}`. `null` clears an attribute; an omitted key is left alone.\n- Each product is pinned to a reviewed API version: people 2026-06-04, services 2018-11-01, groups 2023-07-10, check-ins 2025-05-28, calendar 2026-06-22, registrations 2025-05-01, giving 2019-10-18, publishing 2024-03-25, webhooks 2022-10-20, api 2026-09-24. Pass `version` only for an endpoint those versions lack.\n- Query parameters are name/value pairs with literal brackets: `where[status]`, `include`, `order`, `filter`, `fields[Person]`. Every list response's `meta` names the `can_query_by`, `can_order_by`, and `can_include` values for that endpoint; an unknown `where` key is ignored rather than refused.\n- File uploads go to a separate host (upload.planningcenteronline.com), which this connection does not reach.\n\n## Rate limits\n\nPlanning Center allows 100 requests per 20 seconds per user, shared by every integration running as that user. This connection admits 100 calls per 20 seconds, five at a time, per runtime — an approximation, not a guarantee. A `rate_limited` failure carries Planning Center's wait. Filter server-side and use `totalCount` rather than paging to count.\n"
28
+ "published": "# Planning Center usage\n\nPlanning Center Online church management — People, Services, Groups, Check-Ins, Calendar, Registrations, Giving, Publishing, Webhooks.\n\n## Identity first\n\n- A person id is the join key across every product: the id `search_people` returns is the person id (`personId` argument, `person_id` field) for Services schedules, Groups memberships, Giving donations, and Check-Ins. Resolve ids with list tools; never guess one.\n- A person id that used to resolve and now answers `not_found` was most likely merged. Read `/people/v2/person_mergers` with `where[person_to_remove_id]` and follow `person_to_keep_id`.\n- `get_me` names the user this token acts as. Every read and write runs with that user's permissions, product by product; a `provider_permission_denied` means an administrator must grant that user access to the product or record.\n\n## Results and paging\n\n- Records keep Planning Center's own snake_case attribute names; a relationship appears as `<relationship>_id`. Pass `raw: true` for the untouched JSON:API resources and `included`; `get_me` and `list_webhook_subscriptions` have no raw mode.\n- Lists page by offset: `perPage` up to 100, then pass `page.nextOffset` back as `offset` while `page.hasMore` is true. `page.totalCount` answers \"how many\" without paging — request `perPage: 1`.\n- A bare date in a date filter is the organization's local day; pass a full timestamp for exact bounds. Name filters are exact; end one with `%` for a prefix match.\n- `get_plan` fetches the plan, its items, and its team in one call and sets `itemsTruncated`/`teamMembersTruncated` past 100 rows; page the rest with `pco_api_get`.\n\n## What each product lets the API write\n\n- People: full read/write. `update_person` with `status: inactive` archives reversibly; a DELETE is permanent and only reachable through `pco_api_mutate`. `run_list` can fire list automations.\n- Services: full read/write. A plan's dates come from its plan times. Scheduling needs a team.\n- Groups: memberships and group settings are writable; groups cannot be created, and events and attendance are read-only.\n- Check-Ins and Registrations: read-only. Nobody can be checked in or registered through the API.\n- Calendar: events, instances, and times are read-only; tags, resources, rooms, and folders are writable through `pco_api_mutate`.\n- Giving: the named tools only read, amounts in integer cents. Creating or editing donations, refunds, and committing a batch move money and go through `pco_api_mutate`.\n- Publishing and the `api` product (organization, OAuth applications, personal access tokens) have no named tools; use the hatches.\n\n## The hatches\n\n- `pco_api_get` reaches any GET; `pco_api_mutate` any POST, PATCH, or DELETE, always approval-gated. Paths start `/<app>/v2`, exactly as Planning Center documents them; the connector owns host, auth, content type, and the `X-PCO-API-Version` header.\n- Bodies are JSON:API documents: `{\"data\":{\"type\":\"Email\",\"attributes\":{...}}}`. `null` clears an attribute; an omitted key is left alone.\n- Each product is pinned to a reviewed API version: people 2026-06-04, services 2018-11-01, groups 2023-07-10, check-ins 2025-05-28, calendar 2026-06-22, registrations 2025-05-01, giving 2019-10-18, publishing 2024-03-25, webhooks 2022-10-20, api 2026-09-24. Pass `version` only for an endpoint those versions lack.\n- Query parameters are name/value pairs with literal brackets: `where[status]`, `include`, `order`, `filter`, `fields[Person]`. Every list response's `meta` names the `can_query_by`, `can_order_by`, and `can_include` values for that endpoint; an unknown `where` key is ignored rather than refused.\n- File uploads go to a separate host (upload.planningcenteronline.com), which this connection does not reach.\n\n## Rate limits\n\nPlanning Center allows 100 requests per 20 seconds per user, shared by every integration running as that user. This connection admits 100 calls per 20 seconds, five at a time, per runtime — an approximation, not a guarantee. A `rate_limited` failure carries Planning Center's wait. Filter server-side and use `totalCount` rather than paging to count.\n"
29
29
  }
30
30
  };
@@ -1,9 +1,9 @@
1
1
  // Generated by scripts/generate-providers.mjs; do not edit.
2
2
  export const skill = {
3
- "content": "\n- Inspect result framing before accessing fields. A text result is not automatically an object; parse it only if the returned text is valid JSON. Do not iterate fields that the response does not contain.\n- Resolve ids before acting; never guess one. `list-projects` yields the `project_id` every project-scoped call takes. `list-apps`, `list-products`, `list-entitlements`, `list-offerings`, `list-paywalls`, `list-audiences`, and `list-customers` yield the ids their `get-`, `update-`, `archive-`, and `delete-` counterparts expect. A plausible-looking id belongs to another project or to nobody.\n- Customers are addressed by the app user id your SDK set, not by an internal key. Find one with `list-customers` before `get-customer`, and carry the id it returned unchanged.\n- Customer and subscription objects are large, and a customer's history is larger. Page with the cursor the list returned rather than raising the page size, and reduce inside `execute_code` — select the fields the question needs and return those, not the whole object.\n- Whether a customer should have access is `gives_access` on each subscription from `list-subscriptions`, which RevenueCat calls the authoritative flag. `status` and `expires_date` describe the store-side state and disagree with it during grace periods, billing retries, and promotional grants — answer access questions from `gives_access` and say which subscription it came from.\n- `get-chart-data` is the metrics path: read `get-chart-options-schema` for the chart you want before calling it, rather than guessing an option name. `get-overview-metrics` and `get-revenue-metric` answer the summary questions in one call.\n- `create-paywall-ai` and `edit-paywall-ai` are asynchronous. Poll the task id they return with `get-paywall-ai-task` rather than assuming the work finished when the call returned.\n- Store changes ride the plan workflow, not the deprecated direct tools: `create-product-store-state-plan`, then `plan-product-store-state-plan`, then `apply-product-store-state-plan`. Every step past the create is asynchronous — read the plan back with `get-product-store-state-plan` between steps instead of assuming the last one finished.\n- This connection's tool list is not a fixed set. RevenueCat gates parts of its MCP catalog by plan, platform, and beta enrollment — paywall AI editing, benchmarks, experiments, virtual currencies, and the account-billing tools are the usual absentees — so search this connector for what it actually exposes rather than assuming a documented tool is here.\n- `render-paywall-screenshot` is unclassified on purpose because RevenueCat's reference gives it no access column. An explicit, uncontradicted live read annotation qualifies it as a read; otherwise it fails closed as a write. The public reference alone cannot establish that rendering has no side effects.\n- RevenueCat meters API v2 per minute and per domain, and the domains differ: 480 requests per minute for customer information and virtual currencies, 60 for project configuration and audiences, 25 for charts and metrics. It answers a breach with `429`, a `Retry-After` header, and a `backoff_ms` field. Back off on that rather than retrying immediately, and expect chart sweeps to hit the ceiling long before customer reads do.\n- Treat every create, update, archive, unarchive, attach, detach, delete, publish, unpublish, grant, assign, and submit operation as a write. For guest calls, classification, and routing, fetch `skills({ name: \"usage\" })`.\n- An `auth_required` failure means this connector's RevenueCat authorization is missing or expired: run `authorize_connector` for this connector id, then retry the same call unchanged. A rejected argument, a permission gap, or a plan restriction comes back in RevenueCat's own words instead — read it rather than re-authorizing.\n# RevenueCat usage\n\nAccount-scoped connection: this OAuth session reaches every RevenueCat project the account can see. Connector purpose: \n\nCall `list-projects` first and carry the exact `project_id` it returned into every project-scoped call. Connecta does not pick a project, and the connector id, title, and purpose are routing hints rather than proof of which project a call will land in. If more than one project fits the request, stop and ask; never guess a `project_id`.\n. RevenueCat secret API keys are project-wide, so this key reaches exactly one project and nothing outside it. A second project is a second connector with its own key and its own id — never a `project_id` argument pointed somewhere else.\n\nConfirm the project on first use: `list-projects` returns the one project this key can see, and its `project_id` is the one every project-scoped call takes. An empty or unexpected result means wrong connector, not missing data.\n\nA RevenueCat secret key is issued read-only or write-enabled, and connecta cannot tell which this one is. It does not filter writes for a read-only key: every write is offered, reaches RevenueCat, and fails there in RevenueCat's own words. Read that refusal as \"this key cannot write\" rather than as a bad argument, and route the write to a connector configured with a write-enabled key.\n",
3
+ "content": "\n- Inspect result framing before accessing fields. A text result is not automatically an object; parse it only if the returned text is valid JSON. Do not iterate fields that the response does not contain.\n- Resolve ids before acting; never guess one. `list-projects` yields the `project_id` every project-scoped call takes. `list-apps`, `list-products`, `list-entitlements`, `list-offerings`, `list-paywalls`, `list-audiences`, and `list-customers` yield the ids their `get-`, `update-`, `archive-`, and `delete-` counterparts expect. A plausible-looking id belongs to another project or to nobody.\n- Customers are addressed by the app user id your SDK set, not by an internal key. Find one with `list-customers` before `get-customer`, and carry the id it returned unchanged.\n- Customer and subscription objects are large, and a customer's history is larger. Page with the cursor the list returned rather than raising the page size, and reduce inside `execute_code` — select the fields the question needs and return those, not the whole object.\n- Whether a customer should have access is `gives_access` on each subscription from `list-subscriptions`, which RevenueCat calls the authoritative flag. `status` and `expires_date` describe the store-side state and disagree with it during grace periods, billing retries, and promotional grants — answer access questions from `gives_access` and say which subscription it came from.\n- `get-chart-data` is the metrics path: read `get-chart-options-schema` for the chart you want before calling it, rather than guessing an option name. `get-overview-metrics` and `get-revenue-metric` answer the summary questions in one call.\n- `create-paywall-ai` and `edit-paywall-ai` are asynchronous. Poll the task id they return with `get-paywall-ai-task` rather than assuming the work finished when the call returned.\n- Store changes ride the plan workflow, not the deprecated direct tools: `create-product-store-state-plan`, then `plan-product-store-state-plan`, then `apply-product-store-state-plan`. Every step past the create is asynchronous — read the plan back with `get-product-store-state-plan` between steps instead of assuming the last one finished.\n- This connection's tool list is not a fixed set. RevenueCat gates parts of its MCP catalog by plan, platform, and beta enrollment — paywall AI editing, benchmarks, experiments, virtual currencies, and the account-billing tools are the usual absentees — so search this connector for what it actually exposes rather than assuming a documented tool is here.\n- `render-paywall-screenshot` is unclassified on purpose because RevenueCat's reference gives it no access column. An explicit, uncontradicted live read annotation qualifies it as a read; otherwise it fails closed as a write. The public reference alone cannot establish that rendering has no side effects.\n- RevenueCat meters API v2 per minute and per domain, and the domains differ: 480 requests per minute for customer information and virtual currencies, 60 for project configuration and audiences, 25 for charts and metrics. It answers a breach with `429`, a `Retry-After` header, and a `backoff_ms` field. Back off on that rather than retrying immediately, and expect chart sweeps to hit the ceiling long before customer reads do.\n- Treat every operation other than a `get-` or `list-` read as a write, including create, update, archive, duplicate, validate, start, attach, detach, delete, publish, grant, assign, and submit. For guest calls, classification, and routing, fetch `skills({ name: \"usage\" })`.\n- An `auth_required` failure means this connector's RevenueCat authorization is missing or expired: run `authorize_connector` for this connector id, then retry the same call unchanged. A rejected argument, a permission gap, or a plan restriction comes back in RevenueCat's own words instead — read it rather than re-authorizing.\n# RevenueCat usage\n\nAccount-scoped connection: this OAuth session reaches every RevenueCat project the account can see. Connector purpose: \n\nCall `list-projects` first and carry the exact `project_id` it returned into every project-scoped call. Connecta does not pick a project, and the connector id, title, and purpose are routing hints rather than proof of which project a call will land in. If more than one project fits the request, stop and ask; never guess a `project_id`.\n. RevenueCat secret API keys are project-wide, so this key reaches exactly one project and nothing outside it. A second project is a second connector with its own key and its own id — never a `project_id` argument pointed somewhere else.\n\nConfirm the project on first use: `list-projects` returns the one project this key can see, and its `project_id` is the one every project-scoped call takes. An empty or unexpected result means wrong connector, not missing data.\n\nA RevenueCat secret key is issued read-only or write-enabled, and connecta cannot tell which this one is. It does not filter writes for a read-only key: every write is offered, reaches RevenueCat, and fails there in RevenueCat's own words. Read that refusal as \"this key cannot write\" rather than as a bad argument, and route the write to a connector configured with a write-enabled key.\n",
4
4
  "instructionsHeading": "Project instructions",
5
5
  "fragments": {
6
- "guide_0": "\n- Inspect result framing before accessing fields. A text result is not automatically an object; parse it only if the returned text is valid JSON. Do not iterate fields that the response does not contain.\n- Resolve ids before acting; never guess one. `list-projects` yields the `project_id` every project-scoped call takes. `list-apps`, `list-products`, `list-entitlements`, `list-offerings`, `list-paywalls`, `list-audiences`, and `list-customers` yield the ids their `get-`, `update-`, `archive-`, and `delete-` counterparts expect. A plausible-looking id belongs to another project or to nobody.\n- Customers are addressed by the app user id your SDK set, not by an internal key. Find one with `list-customers` before `get-customer`, and carry the id it returned unchanged.\n- Customer and subscription objects are large, and a customer's history is larger. Page with the cursor the list returned rather than raising the page size, and reduce inside `execute_code` — select the fields the question needs and return those, not the whole object.\n- Whether a customer should have access is `gives_access` on each subscription from `list-subscriptions`, which RevenueCat calls the authoritative flag. `status` and `expires_date` describe the store-side state and disagree with it during grace periods, billing retries, and promotional grants — answer access questions from `gives_access` and say which subscription it came from.\n- `get-chart-data` is the metrics path: read `get-chart-options-schema` for the chart you want before calling it, rather than guessing an option name. `get-overview-metrics` and `get-revenue-metric` answer the summary questions in one call.\n- `create-paywall-ai` and `edit-paywall-ai` are asynchronous. Poll the task id they return with `get-paywall-ai-task` rather than assuming the work finished when the call returned.\n- Store changes ride the plan workflow, not the deprecated direct tools: `create-product-store-state-plan`, then `plan-product-store-state-plan`, then `apply-product-store-state-plan`. Every step past the create is asynchronous — read the plan back with `get-product-store-state-plan` between steps instead of assuming the last one finished.\n- This connection's tool list is not a fixed set. RevenueCat gates parts of its MCP catalog by plan, platform, and beta enrollment — paywall AI editing, benchmarks, experiments, virtual currencies, and the account-billing tools are the usual absentees — so search this connector for what it actually exposes rather than assuming a documented tool is here.\n- `render-paywall-screenshot` is unclassified on purpose because RevenueCat's reference gives it no access column. An explicit, uncontradicted live read annotation qualifies it as a read; otherwise it fails closed as a write. The public reference alone cannot establish that rendering has no side effects.\n- RevenueCat meters API v2 per minute and per domain, and the domains differ: 480 requests per minute for customer information and virtual currencies, 60 for project configuration and audiences, 25 for charts and metrics. It answers a breach with `429`, a `Retry-After` header, and a `backoff_ms` field. Back off on that rather than retrying immediately, and expect chart sweeps to hit the ceiling long before customer reads do.\n- Treat every create, update, archive, unarchive, attach, detach, delete, publish, unpublish, grant, assign, and submit operation as a write. For guest calls, classification, and routing, fetch `skills({ name: \"usage\" })`.\n- An `auth_required` failure means this connector's RevenueCat authorization is missing or expired: run `authorize_connector` for this connector id, then retry the same call unchanged. A rejected argument, a permission gap, or a plan restriction comes back in RevenueCat's own words instead — read it rather than re-authorizing.\n",
6
+ "guide_0": "\n- Inspect result framing before accessing fields. A text result is not automatically an object; parse it only if the returned text is valid JSON. Do not iterate fields that the response does not contain.\n- Resolve ids before acting; never guess one. `list-projects` yields the `project_id` every project-scoped call takes. `list-apps`, `list-products`, `list-entitlements`, `list-offerings`, `list-paywalls`, `list-audiences`, and `list-customers` yield the ids their `get-`, `update-`, `archive-`, and `delete-` counterparts expect. A plausible-looking id belongs to another project or to nobody.\n- Customers are addressed by the app user id your SDK set, not by an internal key. Find one with `list-customers` before `get-customer`, and carry the id it returned unchanged.\n- Customer and subscription objects are large, and a customer's history is larger. Page with the cursor the list returned rather than raising the page size, and reduce inside `execute_code` — select the fields the question needs and return those, not the whole object.\n- Whether a customer should have access is `gives_access` on each subscription from `list-subscriptions`, which RevenueCat calls the authoritative flag. `status` and `expires_date` describe the store-side state and disagree with it during grace periods, billing retries, and promotional grants — answer access questions from `gives_access` and say which subscription it came from.\n- `get-chart-data` is the metrics path: read `get-chart-options-schema` for the chart you want before calling it, rather than guessing an option name. `get-overview-metrics` and `get-revenue-metric` answer the summary questions in one call.\n- `create-paywall-ai` and `edit-paywall-ai` are asynchronous. Poll the task id they return with `get-paywall-ai-task` rather than assuming the work finished when the call returned.\n- Store changes ride the plan workflow, not the deprecated direct tools: `create-product-store-state-plan`, then `plan-product-store-state-plan`, then `apply-product-store-state-plan`. Every step past the create is asynchronous — read the plan back with `get-product-store-state-plan` between steps instead of assuming the last one finished.\n- This connection's tool list is not a fixed set. RevenueCat gates parts of its MCP catalog by plan, platform, and beta enrollment — paywall AI editing, benchmarks, experiments, virtual currencies, and the account-billing tools are the usual absentees — so search this connector for what it actually exposes rather than assuming a documented tool is here.\n- `render-paywall-screenshot` is unclassified on purpose because RevenueCat's reference gives it no access column. An explicit, uncontradicted live read annotation qualifies it as a read; otherwise it fails closed as a write. The public reference alone cannot establish that rendering has no side effects.\n- RevenueCat meters API v2 per minute and per domain, and the domains differ: 480 requests per minute for customer information and virtual currencies, 60 for project configuration and audiences, 25 for charts and metrics. It answers a breach with `429`, a `Retry-After` header, and a `backoff_ms` field. Back off on that rather than retrying immediately, and expect chart sweeps to hit the ceiling long before customer reads do.\n- Treat every operation other than a `get-` or `list-` read as a write, including create, update, archive, duplicate, validate, start, attach, detach, delete, publish, grant, assign, and submit. For guest calls, classification, and routing, fetch `skills({ name: \"usage\" })`.\n- An `auth_required` failure means this connector's RevenueCat authorization is missing or expired: run `authorize_connector` for this connector id, then retry the same call unchanged. A rejected argument, a permission gap, or a plan restriction comes back in RevenueCat's own words instead — read it rather than re-authorizing.\n",
7
7
  "guide_1": "# RevenueCat usage\n\nAccount-scoped connection: this OAuth session reaches every RevenueCat project the account can see. Connector purpose: ",
8
8
  "guide_2": "\n\nCall `list-projects` first and carry the exact `project_id` it returned into every project-scoped call. Connecta does not pick a project, and the connector id, title, and purpose are routing hints rather than proof of which project a call will land in. If more than one project fits the request, stop and ask; never guess a `project_id`.\n",
9
9
  "guide_3": ". RevenueCat secret API keys are project-wide, so this key reaches exactly one project and nothing outside it. A second project is a second connector with its own key and its own id — never a `project_id` argument pointed somewhere else.\n\nConfirm the project on first use: `list-projects` returns the one project this key can see, and its `project_id` is the one every project-scoped call takes. An empty or unexpected result means wrong connector, not missing data.\n\nA RevenueCat secret key is issued read-only or write-enabled, and connecta cannot tell which this one is. It does not filter writes for a read-only key: every write is offered, reaches RevenueCat, and fails there in RevenueCat's own words. Read that refusal as \"this key cannot write\" rather than as a bad argument, and route the write to a connector configured with a write-enabled key.\n"
@@ -46,6 +46,9 @@ export type SheetsOptions = GoogleWorkspaceOptions;
46
46
  * });
47
47
  * ```
48
48
  *
49
+ * Agents never reach that function: give each pool they use an `owner`,
50
+ * the address they act as there (decisions/0006).
51
+ *
49
52
  * Reads are read-only. `create_spreadsheet`, `add_sheet`, and `append_values`
50
53
  * are additive writes the host approves through call_destructive_tool or a
51
54
  * program on a writable endpoint; the value writes, `clear_values`, and the raw
@@ -28,12 +28,12 @@ import { skill } from "./skill.generated.js";
28
28
  *
29
29
  * Drift. Google publishes a credential-free Discovery document per API.
30
30
  * `src/providers/sheets/drift.json` records the seven methods the tools
31
- * call, and `npm run providers:check -- --provider sheets` reports a touched
31
+ * call, and `pnpm run providers:check --provider sheets` reports a touched
32
32
  * contract that moved or a method that stopped accepting the scope below.
33
33
  */
34
34
  import { apiConnector as api, defined } from "../../connectors/api-connector.js";
35
35
  import { ConnectorCallError } from "../../errors.js";
36
- import { googleOutcomeOf, GOOGLE_WORKSPACE_OPTIONS, googleWorkspaceClient, workspaceConnection, } from "../_shared/google/workspace.js";
36
+ import { googleOutcomeOf, GOOGLE_WORKSPACE_OPTIONS, googleWorkspaceClient, workspaceConnection, workspaceConnector, } from "../_shared/google/workspace.js";
37
37
  import { clampText, jsonBytes, RESULT_BUDGET_BYTES } from "../_shared/google/result-size.js";
38
38
  import { asProviderFactory } from "../../provider.js";
39
39
  /**
@@ -1186,6 +1186,9 @@ function usageGuide(purpose, instructions) {
1186
1186
  * });
1187
1187
  * ```
1188
1188
  *
1189
+ * Agents never reach that function: give each pool they use an `owner`,
1190
+ * the address they act as there (decisions/0006).
1191
+ *
1189
1192
  * Reads are read-only. `create_spreadsheet`, `add_sheet`, and `append_values`
1190
1193
  * are additive writes the host approves through call_destructive_tool or a
1191
1194
  * program on a writable endpoint; the value writes, `clear_values`, and the raw
@@ -1218,7 +1221,7 @@ function sheetsConnector(id, options) {
1218
1221
  notFound: "ambiguous",
1219
1222
  connection,
1220
1223
  });
1221
- return api(id, {
1224
+ return workspaceConnector(connection, api(id, {
1222
1225
  ...defined({
1223
1226
  callAdmission: options.callAdmission,
1224
1227
  maxResultBytes: options.maxResultBytes,
@@ -1233,5 +1236,5 @@ function sheetsConnector(id, options) {
1233
1236
  required: true,
1234
1237
  },
1235
1238
  tools: tools(client),
1236
- });
1239
+ }));
1237
1240
  }
@@ -1,10 +1,10 @@
1
1
  // Generated by scripts/generate-providers.mjs; do not edit.
2
2
  export const skill = {
3
- "content": "# Google Sheets usage\n\nActs as the signed-in person in their own Google Sheets through Workspace delegation: it reaches only spreadsheets they can open, starting from an id.\n\nConnection purpose: \n\n## Whose spreadsheets\n\nEvery call acts as the Workspace account deployment config maps the caller\nto. No argument names an account. A call with none mapped fails\n`auth_required`; only an operator can change the mapping. An id that is\nunknown or not shared with this person fails the same way — Google does not\nsay which.\n\n## Finding a spreadsheet\n\n- Sheets cannot list or search files. Find one by name with a Google Drive\n search, or take the id from its URL (between `/d/` and `/edit`).\n- `get_spreadsheet` first: sheet titles, ids, grid sizes, and named ranges,\n so ranges are named right rather than guessed.\n\n## Ranges\n\n- A1 notation. Quote a sheet title with spaces or punctuation:\n `'Q3 Budget'!A1:D20`, doubling any `'` inside it. A bare title is the\n whole sheet; `Sheet1!B:B` a whole column; a named range works by name.\n- `get_values` pages by whole rows under `maxCells` and `maxBytes`:\n page with `page.nextCursor` and the same spreadsheetId, ranges, and\n render options. A range with explicit rows (`A1:D500`, `B:D`) resumes\n where it stopped; a sheet title or named range is re-read and its earlier\n rows skipped, so `rowOffset` says how far into Google's echoed range the\n page starts.\n- A row too wide or too large for one page is refused, not cut: narrow the\n columns or lower `maxCellChars`. The default `maxBytes` is what\n `execute_code` can receive; raise it only for a direct `call_tool`\n read.\n- A cell over characters ends with a truncation marker;\n read it alone with `maxCellChars` for the rest. Reduce inside\n `execute_code` before returning a large read.\n\n## Writing\n\n- `valueInputOption` is required: `RAW` stores text as given;\n `USER_ENTERED` parses it as typed, so a leading `=` makes a formula. Use\n RAW for data from outside the spreadsheet.\n- In written rows, `null` leaves a cell as it was and `\"\"` empties it.\n- `append_values` inserts rows below the table and overwrites nothing;\n `update_values` and `batch_update_values` overwrite the cells they\n cover; `clear_values` empties values but keeps formatting.\n- `batch_update_spreadsheet` takes Google's own Request objects for\n everything else (formatting, sorting, deleting rows or sheets); it is\n atomic, always needs approval, and can destroy data. Replies too large\n to return come back cut to their kind, ids, and counts with a `note`;\n the requests still applied, so never send them again to see the rest.\n- Sheets has no revision check on writes: edits are last-writer-wins, so a\n person editing the same cells meanwhile is overwritten without warning.\n Read just before writing over anything a person may be editing.\n- Moving, sharing, or deleting a spreadsheet file is Drive's job.\n",
3
+ "content": "# Google Sheets usage\n\nActs as the signed-in person in their own Google Sheets through Workspace delegation: it reaches only spreadsheets they can open, starting from an id.\n\nConnection purpose: \n\n## Whose spreadsheets\n\nEvery call acts as the Workspace account deployment config maps the caller\nto; 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\nnames an account. A call with none mapped fails `auth_required`; only an operator can change\nthat. An id that is\nunknown or not shared with this person fails `connector_call_failed` (or\n`provider_permission_denied` when Google refuses access) — Google does not\nsay which; check the id or ask the owner to share it.\n\n## Finding a spreadsheet\n\n- Sheets cannot list or search files. Find one by name with a Google Drive\n search, or take the id from its URL (between `/d/` and `/edit`).\n- `get_spreadsheet` first: sheet titles, ids, grid sizes, and named ranges,\n so ranges are named right rather than guessed.\n\n## Ranges\n\n- A1 notation. Quote a sheet title with spaces or punctuation:\n `'Q3 Budget'!A1:D20`, doubling any `'` inside it. A bare title is the\n whole sheet; `Sheet1!B:B` a whole column; a named range works by name.\n- `get_values` pages by whole rows under `maxCells` and `maxBytes`:\n page with `page.nextCursor` and the same spreadsheetId, ranges, and\n render options. A range with explicit rows (`A1:D500`, `B:D`) resumes\n where it stopped; a sheet title or named range is re-read and its earlier\n rows skipped, so `rowOffset` says how far into Google's echoed range the\n page starts.\n- A row too wide or too large for one page is refused, not cut: narrow the\n columns or lower `maxCellChars`. The default `maxBytes` is what\n `execute_code` can receive; raise it only for a direct `call_tool`\n read.\n- A cell over characters ends with a truncation marker;\n read it alone with `maxCellChars` for the rest. Reduce inside\n `execute_code` before returning a large read.\n\n## Writing\n\n- `valueInputOption` is required: `RAW` stores text as given;\n `USER_ENTERED` parses it as typed, so a leading `=` makes a formula. Use\n RAW for data from outside the spreadsheet.\n- In written rows, `null` leaves a cell as it was and `\"\"` empties it.\n- `append_values` inserts rows below the table and overwrites nothing;\n `update_values` and `batch_update_values` overwrite the cells they\n cover; `clear_values` empties values but keeps formatting.\n- `batch_update_spreadsheet` takes Google's own Request objects for\n everything else (formatting, sorting, deleting rows or sheets); it is\n atomic, always needs approval, and can destroy data. Replies too large\n to return come back cut to their kind, ids, and counts with a `note`;\n the requests still applied, so never send them again to see the rest.\n- Sheets has no revision check on writes: edits are last-writer-wins, so a\n person editing the same cells meanwhile is overwritten without warning.\n Read just before writing over anything a person may be editing.\n- Moving, sharing, or deleting a spreadsheet file is Drive's job.\n",
4
4
  "instructionsHeading": "Connection instructions",
5
5
  "fragments": {
6
6
  "guide_0": "# Google Sheets usage\n\nActs as the signed-in person in their own Google Sheets through Workspace delegation: it reaches only spreadsheets they can open, starting from an id.\n\nConnection purpose: ",
7
- "guide_1": "\n\n## Whose spreadsheets\n\nEvery call acts as the Workspace account deployment config maps the caller\nto. No argument names an account. A call with none mapped fails\n`auth_required`; only an operator can change the mapping. An id that is\nunknown or not shared with this person fails the same way — Google does not\nsay which.\n\n## Finding a spreadsheet\n\n- Sheets cannot list or search files. Find one by name with a Google Drive\n search, or take the id from its URL (between `/d/` and `/edit`).\n- `get_spreadsheet` first: sheet titles, ids, grid sizes, and named ranges,\n so ranges are named right rather than guessed.\n\n## Ranges\n\n- A1 notation. Quote a sheet title with spaces or punctuation:\n `'Q3 Budget'!A1:D20`, doubling any `'` inside it. A bare title is the\n whole sheet; `Sheet1!B:B` a whole column; a named range works by name.\n- `get_values` pages by whole rows under `maxCells` and `maxBytes`:\n page with `page.nextCursor` and the same spreadsheetId, ranges, and\n render options. A range with explicit rows (`A1:D500`, `B:D`) resumes\n where it stopped; a sheet title or named range is re-read and its earlier\n rows skipped, so `rowOffset` says how far into Google's echoed range the\n page starts.\n- A row too wide or too large for one page is refused, not cut: narrow the\n columns or lower `maxCellChars`. The default `maxBytes` is what\n `execute_code` can receive; raise it only for a direct `call_tool`\n read.\n- A cell over ",
7
+ "guide_1": "\n\n## Whose spreadsheets\n\nEvery call acts as the Workspace account deployment config maps the caller\nto; 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\nnames an account. A call with none mapped fails `auth_required`; only an operator can change\nthat. An id that is\nunknown or not shared with this person fails `connector_call_failed` (or\n`provider_permission_denied` when Google refuses access) — Google does not\nsay which; check the id or ask the owner to share it.\n\n## Finding a spreadsheet\n\n- Sheets cannot list or search files. Find one by name with a Google Drive\n search, or take the id from its URL (between `/d/` and `/edit`).\n- `get_spreadsheet` first: sheet titles, ids, grid sizes, and named ranges,\n so ranges are named right rather than guessed.\n\n## Ranges\n\n- A1 notation. Quote a sheet title with spaces or punctuation:\n `'Q3 Budget'!A1:D20`, doubling any `'` inside it. A bare title is the\n whole sheet; `Sheet1!B:B` a whole column; a named range works by name.\n- `get_values` pages by whole rows under `maxCells` and `maxBytes`:\n page with `page.nextCursor` and the same spreadsheetId, ranges, and\n render options. A range with explicit rows (`A1:D500`, `B:D`) resumes\n where it stopped; a sheet title or named range is re-read and its earlier\n rows skipped, so `rowOffset` says how far into Google's echoed range the\n page starts.\n- A row too wide or too large for one page is refused, not cut: narrow the\n columns or lower `maxCellChars`. The default `maxBytes` is what\n `execute_code` can receive; raise it only for a direct `call_tool`\n read.\n- A cell over ",
8
8
  "guide_2": " characters ends with a truncation marker;\n read it alone with `maxCellChars` for the rest. Reduce inside\n `execute_code` before returning a large read.\n\n## Writing\n\n- `valueInputOption` is required: `RAW` stores text as given;\n `USER_ENTERED` parses it as typed, so a leading `=` makes a formula. Use\n RAW for data from outside the spreadsheet.\n- In written rows, `null` leaves a cell as it was and `\"\"` empties it.\n- `append_values` inserts rows below the table and overwrites nothing;\n `update_values` and `batch_update_values` overwrite the cells they\n cover; `clear_values` empties values but keeps formatting.\n- `batch_update_spreadsheet` takes Google's own Request objects for\n everything else (formatting, sorting, deleting rows or sheets); it is\n atomic, always needs approval, and can destroy data. Replies too large\n to return come back cut to their kind, ids, and counts with a `note`;\n the requests still applied, so never send them again to see the rest.\n- Sheets has no revision check on writes: edits are last-writer-wins, so a\n person editing the same cells meanwhile is overwritten without warning.\n Read just before writing over anything a person may be editing.\n- Moving, sharing, or deleting a spreadsheet file is Drive's job.\n"
9
9
  }
10
10
  };
@@ -40,13 +40,13 @@ import { skill } from "./skill.generated.js";
40
40
  *
41
41
  * Drift. Google publishes a credential-free Discovery document per API.
42
42
  * `src/providers/slides/drift.json` records the five methods the tools
43
- * call, and `npm run providers:check -- --provider slides` reports a touched
43
+ * call, and `pnpm run providers:check --provider slides` reports a touched
44
44
  * contract that moved or a method that stopped accepting the scope below.
45
45
  */
46
46
  import { apiConnector as api, defined } from "../../connectors/api-connector.js";
47
47
  import { ConnectorCallError } from "../../errors.js";
48
48
  import { RESULT_BUDGET_BYTES, clampText, jsonBytes } from "../_shared/google/result-size.js";
49
- import { googleOutcomeOf, GOOGLE_WORKSPACE_OPTIONS, googleWorkspaceClient, workspaceConnection, } from "../_shared/google/workspace.js";
49
+ import { googleOutcomeOf, GOOGLE_WORKSPACE_OPTIONS, googleWorkspaceClient, workspaceConnection, workspaceConnector, } from "../_shared/google/workspace.js";
50
50
  import { asProviderFactory } from "../../provider.js";
51
51
  /** The Slides API root. Every path beneath it names a presentation by id. */
52
52
  export const SLIDES_API_BASE_URL = "https://slides.googleapis.com/v1";
@@ -2626,7 +2626,7 @@ function slidesConnector(id, options) {
2626
2626
  notFound: "ambiguous",
2627
2627
  connection,
2628
2628
  });
2629
- return api(id, {
2629
+ return workspaceConnector(connection, api(id, {
2630
2630
  ...defined({
2631
2631
  callAdmission: options.callAdmission,
2632
2632
  maxResultBytes: options.maxResultBytes,
@@ -2641,5 +2641,5 @@ function slidesConnector(id, options) {
2641
2641
  required: true,
2642
2642
  },
2643
2643
  tools: tools(client),
2644
- });
2644
+ }));
2645
2645
  }
@@ -1,9 +1,9 @@
1
1
  // Generated by scripts/generate-providers.mjs; do not edit.
2
2
  export const skill = {
3
- "content": "# Google Slides usage\n\nActs as the signed-in person in Google Slides through Workspace delegation: read, create, and edit the decks they can open.\n\nConnection purpose: \n\n## Whose decks\n\nEvery call acts as the Workspace account deployment config maps the caller\nto, and reaches exactly the decks that person can open. No argument names an\naccount. A call with none mapped fails `auth_required`; only an operator can\nchange the mapping. A deck the person cannot see and one that does not exist\nfail alike — Google does not say which.\n\n## Finding a deck\n\nSlides cannot list or search decks; that is Google Drive's job. Take the id\nfrom a Drive search or from a URL (`/presentation/d/<id>/`).\n\n## Reading\n\n- `get_presentation` returns each slide's text in reading order (top to\n bottom, then left to right), speaker notes, and alt text for images,\n videos, and charts. Empty placeholders are listed with their id and type;\n lines and other empty shapes are counted in `omittedElements`. `index`\n is 0-based, the numbering `create_slide` takes.\n- Nothing is cut without a way back. Text past `maxCharsPerSlide`, or past\n what one result can carry (about 192 KB), ends with a marker, and its\n element's `textCursor` (the slide's `notesCursor` for notes) is a\n `get_page` cursor that continues from the cut. A slide too crowded to\n list whole gives `elementsNotShown` and an `elementsCursor`.\n- `get_page` reads any page by objectId — a slide, a layout (its\n placeholders' types and indexes, for `placeholderIdMappings`), a master,\n a notes page (`notesPageId`), or the notes master (`notesMasterId`) —\n listing every element, groups opened,\n and continuing long text across pages from `page.nextCursor`.\n- `list_layouts` lists masters and their layouts; `get_presentation`\n previews them, and `layoutsCursor` continues where the preview stops.\n- `raw: true` returns Slides' own JSON: `get_presentation` a page of\n slides, `get_page` the page and its top-level elements. One too large\n for a result is named (`rawNotShown`) or sent in `rawJson` chunks;\n join the chunks' `json` in order and parse.\n- Every cursor is bound to the deck and the revision it was read at. A\n `conflict` on a cursor means the deck changed: start again without one.\n A page may hold fewer rows than `limit`; follow `page.nextCursor`.\n- `get_slide_thumbnail` returns a link, never the image. The link opens as\n this person for about 30 minutes; do not share it.\n\n## Writing\n\n- `create_presentation` and `create_slide` are additive. A new slide is\n empty: fill it with `batch_update_presentation` (`insertText` into its\n placeholders, after reading their ids with `get_presentation`).\n- `replace_all_text` changes every literal match, case-sensitive unless\n `matchCase: false`. Pass `requiredRevisionId` to refuse a deck that\n changed since you read it.\n- `batch_update_presentation` takes Slides' own Request objects and always\n requires the `revisionId` from the read the requests were built on. All\n requests apply or none do, except comment changes (below). Each write\n returns the new `revisionId` for the next one. A `conflict` means the\n deck changed since that read: re-read it, rebuild the requests, and send\n them with the new `revisionId`. Its replies keep every id at every depth\n whole, in every reply; text is cut first, and a large reply's other fields\n are named in `cut`. Ids too many for this tool's 192 KiB budget are all\n returned anyway, without text; past 256 KiB, more than a program can\n receive, call the tool directly and page with `connecta.result`. A batch\n whose ids pass even that returns its comment state and `repliesNotShown`;\n read what it made with `list_comments`, `get_presentation`, or\n `get_page`, and do not send it again.\n- An id or revision too long to copy into a result is left out and flagged\n (`revisionIdNotShown`), never cut. A failure that says the write\n applied means Google answered 2xx: do not repeat it; re-read instead.\n- Nothing here shares, moves, or deletes a deck.\n\n## Comments\n\n- `list_comments` lists the deck's threads, or one page's with\n `pageObjectId` (a speaker-notes comment is on the notes page). Each has\n its `commentId`, `status` (OPEN or RESOLVED), where it is anchored\n (`pageObjectIds`, `objectIds`), the `quote` it was made on, its\n `headPost`, and `replies`. A post's `author.user` (`users/…`) is\n the stable id; `displayName` is only a name. Slides gives no `user`\n for an anonymous or imported post. Text too long for one result continues\n on the next page: a row with `continued` picks up its thread where the\n last stopped, and `repliesOffset` counts the replies before it. Its\n cursor is bound to the threads and their anchors as well as the revision,\n so a comment added or moved between pages is a `conflict` too. Reading\n comments needs comment access; a view-only account is refused.\n- The projection leaves out each post's HTML (`contentHtml`), whether it\n came from a copied deck, and the text and cell ranges an anchor covers.\n `raw: true` returns each thread as Slides sends it, with\n `commentAnchors` added (its anchors, each with its `pageObjectId`),\n in `rawJson` chunks when one is too large.\n- `create_comment` and `create_comment_reply` are additive and return\n every new id whole. `update_comment_thread` resolves, reopens, or\n reassigns a thread (one at a time; a reassignment needs `content`),\n replacing its status or assignee, and is destructive,\n like `update_comment_post`, `delete_comment`, and\n `delete_comment_reply`; Slides allows the last three only to the\n post's author. Comments notify people the way the Slides editor does.\n- Slides saves comment changes apart from the rest of a write. When it does\n not confirm they saved, the result carries `commentUpdateState` — for\n example `ALL_FAILED_UNKNOWN_REASON` — and the write's other changes may\n still have applied. Do not repeat it: re-read with `list_comments`, then\n decide.\n",
3
+ "content": "# Google Slides usage\n\nActs as the signed-in person in Google Slides through Workspace delegation: read, create, and edit the decks they can open.\n\nConnection purpose: \n\n## Whose decks\n\nEvery call acts as the Workspace account deployment config maps the caller\nto, and reaches exactly the decks that person can open; an agent acts as the\nowner 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 account. A call with\nnone mapped fails `auth_required`; only an operator can change that. A deck the person cannot see and one that does not exist\nfail alike — Google does not say which.\n\n## Finding a deck\n\nSlides cannot list or search decks; that is Google Drive's job. Take the id\nfrom a Drive search or from a URL (`/presentation/d/<id>/`).\n\n## Reading\n\n- `get_presentation` returns each slide's text in reading order (top to\n bottom, then left to right), speaker notes, and alt text for images,\n videos, and charts. Empty placeholders are listed with their id and type;\n lines and other empty shapes are counted in `omittedElements`. `index`\n is 0-based, the numbering `create_slide` takes.\n- Nothing is cut without a way back. Text past `maxCharsPerSlide`, or past\n what one result can carry (about 192 KB), ends with a marker, and its\n element's `textCursor` (the slide's `notesCursor` for notes) is a\n `get_page` cursor that continues from the cut; pass it with the page it came\n from (the slide's `notesPageId` for a `notesCursor`). A slide too crowded to\n list whole gives `elementsNotShown` and an `elementsCursor`.\n- `get_page` reads any page by objectId — a slide, a layout (its\n placeholders' types and indexes, for `placeholderIdMappings`), a master,\n a notes page (`notesPageId`), or the notes master (`notesMasterId`) —\n listing every element, groups opened,\n and continuing long text across pages from `page.nextCursor`.\n- `list_layouts` lists masters and their layouts; `get_presentation`\n previews them, and `layoutsCursor` continues where the preview stops.\n- `raw: true` returns Slides' own JSON: `get_presentation` a page of\n slides, `get_page` the page and its top-level elements. One too large\n for a result is named (`rawNotShown`) or sent in `rawJson` chunks;\n join the chunks' `json` in order and parse.\n- Every cursor is bound to the deck and the revision it was read at. A\n `conflict` on a cursor means the deck changed: start again without one.\n A page may hold fewer rows than `limit`; follow `page.nextCursor`.\n- `get_slide_thumbnail` returns a link, never the image. The link opens as\n this person for about 30 minutes; do not share it.\n\n## Writing\n\n- `create_presentation` and `create_slide` are additive. A new slide is\n empty: fill it with `batch_update_presentation` (`insertText` into its\n placeholders, after reading their ids with `get_presentation`).\n- `replace_all_text` changes every literal match, case-sensitive unless\n `matchCase: false`. Pass `requiredRevisionId` to refuse a deck that\n changed since you read it.\n- `batch_update_presentation` takes Slides' own Request objects and always\n requires `requiredRevisionId`: the `revisionId` from the read the requests\n were built on. All\n requests apply or none do, except comment changes (below). Each write\n returns the new `revisionId` for the next one. A `conflict` means the\n deck changed since that read: re-read it, rebuild the requests, and send\n them with the new `revisionId`. Its replies keep every id at every depth\n whole, in every reply; text is cut first, and a large reply's other fields\n are named in `cut`. Ids too many for this tool's 192 KiB budget are all\n returned anyway, without text; past 256 KiB, more than a program can\n receive, call the tool directly and page with `connecta.result`. A batch\n whose ids pass even that returns its comment state and `repliesNotShown`;\n read what it made with `list_comments`, `get_presentation`, or\n `get_page`, and do not send it again.\n- An id or revision too long to copy into a result is left out and flagged\n (`revisionIdNotShown`), never cut. A failure that says the write\n applied means Google answered 2xx: do not repeat it; re-read instead.\n- Nothing here shares, moves, or deletes a deck.\n\n## Comments\n\n- `list_comments` lists the deck's threads, or one page's with\n `pageObjectId` (a speaker-notes comment is on the notes page). Each has\n its `commentId`, `status` (OPEN or RESOLVED), where it is anchored\n (`pageObjectIds`, `objectIds`), the `quote` it was made on, its\n `headPost`, and `replies`. A post's `author.user` (`users/…`) is\n the stable id; `displayName` is only a name. Slides gives no `user`\n for an anonymous or imported post. Text too long for one result continues\n on the next page: a row with `continued` picks up its thread where the\n last stopped, and `repliesOffset` counts the replies before it. Its\n cursor is bound to the threads and their anchors as well as the revision,\n so a comment added or moved between pages is a `conflict` too. Reading\n comments needs comment access; a view-only account is refused.\n- The projection leaves out each post's HTML (`contentHtml`), whether it\n came from a copied deck, and the text and cell ranges an anchor covers.\n `raw: true` returns each thread as Slides sends it, with\n `commentAnchors` added (its anchors, each with its `pageObjectId`),\n in `rawJson` chunks when one is too large.\n- `create_comment` and `create_comment_reply` are additive and return\n every new id whole. `update_comment_thread` resolves, reopens, or\n reassigns a thread (one at a time; a reassignment needs `content`),\n replacing its status or assignee, and is destructive,\n like `update_comment_post`, `delete_comment`, and\n `delete_comment_reply`; Slides allows the last three only to the\n post's author. Comments notify people the way the Slides editor does.\n- Slides saves comment changes apart from the rest of a write. When it does\n not confirm they saved, the result carries `commentUpdateState` — for\n example `ALL_FAILED_UNKNOWN_REASON` — and the write's other changes may\n still have applied. Do not repeat it: re-read with `list_comments`, then\n decide.\n",
4
4
  "instructionsHeading": "Connection instructions",
5
5
  "fragments": {
6
6
  "guide_0": "# Google Slides usage\n\nActs as the signed-in person in Google Slides through Workspace delegation: read, create, and edit the decks they can open.\n\nConnection purpose: ",
7
- "guide_1": "\n\n## Whose decks\n\nEvery call acts as the Workspace account deployment config maps the caller\nto, and reaches exactly the decks that person can open. No argument names an\naccount. A call with none mapped fails `auth_required`; only an operator can\nchange the mapping. A deck the person cannot see and one that does not exist\nfail alike — Google does not say which.\n\n## Finding a deck\n\nSlides cannot list or search decks; that is Google Drive's job. Take the id\nfrom a Drive search or from a URL (`/presentation/d/<id>/`).\n\n## Reading\n\n- `get_presentation` returns each slide's text in reading order (top to\n bottom, then left to right), speaker notes, and alt text for images,\n videos, and charts. Empty placeholders are listed with their id and type;\n lines and other empty shapes are counted in `omittedElements`. `index`\n is 0-based, the numbering `create_slide` takes.\n- Nothing is cut without a way back. Text past `maxCharsPerSlide`, or past\n what one result can carry (about 192 KB), ends with a marker, and its\n element's `textCursor` (the slide's `notesCursor` for notes) is a\n `get_page` cursor that continues from the cut. A slide too crowded to\n list whole gives `elementsNotShown` and an `elementsCursor`.\n- `get_page` reads any page by objectId — a slide, a layout (its\n placeholders' types and indexes, for `placeholderIdMappings`), a master,\n a notes page (`notesPageId`), or the notes master (`notesMasterId`) —\n listing every element, groups opened,\n and continuing long text across pages from `page.nextCursor`.\n- `list_layouts` lists masters and their layouts; `get_presentation`\n previews them, and `layoutsCursor` continues where the preview stops.\n- `raw: true` returns Slides' own JSON: `get_presentation` a page of\n slides, `get_page` the page and its top-level elements. One too large\n for a result is named (`rawNotShown`) or sent in `rawJson` chunks;\n join the chunks' `json` in order and parse.\n- Every cursor is bound to the deck and the revision it was read at. A\n `conflict` on a cursor means the deck changed: start again without one.\n A page may hold fewer rows than `limit`; follow `page.nextCursor`.\n- `get_slide_thumbnail` returns a link, never the image. The link opens as\n this person for about 30 minutes; do not share it.\n\n## Writing\n\n- `create_presentation` and `create_slide` are additive. A new slide is\n empty: fill it with `batch_update_presentation` (`insertText` into its\n placeholders, after reading their ids with `get_presentation`).\n- `replace_all_text` changes every literal match, case-sensitive unless\n `matchCase: false`. Pass `requiredRevisionId` to refuse a deck that\n changed since you read it.\n- `batch_update_presentation` takes Slides' own Request objects and always\n requires the `revisionId` from the read the requests were built on. All\n requests apply or none do, except comment changes (below). Each write\n returns the new `revisionId` for the next one. A `conflict` means the\n deck changed since that read: re-read it, rebuild the requests, and send\n them with the new `revisionId`. Its replies keep every id at every depth\n whole, in every reply; text is cut first, and a large reply's other fields\n are named in `cut`. Ids too many for this tool's 192 KiB budget are all\n returned anyway, without text; past 256 KiB, more than a program can\n receive, call the tool directly and page with `connecta.result`. A batch\n whose ids pass even that returns its comment state and `repliesNotShown`;\n read what it made with `list_comments`, `get_presentation`, or\n `get_page`, and do not send it again.\n- An id or revision too long to copy into a result is left out and flagged\n (`revisionIdNotShown`), never cut. A failure that says the write\n applied means Google answered 2xx: do not repeat it; re-read instead.\n- Nothing here shares, moves, or deletes a deck.\n\n## Comments\n\n- `list_comments` lists the deck's threads, or one page's with\n `pageObjectId` (a speaker-notes comment is on the notes page). Each has\n its `commentId`, `status` (OPEN or RESOLVED), where it is anchored\n (`pageObjectIds`, `objectIds`), the `quote` it was made on, its\n `headPost`, and `replies`. A post's `author.user` (`users/…`) is\n the stable id; `displayName` is only a name. Slides gives no `user`\n for an anonymous or imported post. Text too long for one result continues\n on the next page: a row with `continued` picks up its thread where the\n last stopped, and `repliesOffset` counts the replies before it. Its\n cursor is bound to the threads and their anchors as well as the revision,\n so a comment added or moved between pages is a `conflict` too. Reading\n comments needs comment access; a view-only account is refused.\n- The projection leaves out each post's HTML (`contentHtml`), whether it\n came from a copied deck, and the text and cell ranges an anchor covers.\n `raw: true` returns each thread as Slides sends it, with\n `commentAnchors` added (its anchors, each with its `pageObjectId`),\n in `rawJson` chunks when one is too large.\n- `create_comment` and `create_comment_reply` are additive and return\n every new id whole. `update_comment_thread` resolves, reopens, or\n reassigns a thread (one at a time; a reassignment needs `content`),\n replacing its status or assignee, and is destructive,\n like `update_comment_post`, `delete_comment`, and\n `delete_comment_reply`; Slides allows the last three only to the\n post's author. Comments notify people the way the Slides editor does.\n- Slides saves comment changes apart from the rest of a write. When it does\n not confirm they saved, the result carries `commentUpdateState` — for\n example `ALL_FAILED_UNKNOWN_REASON` — and the write's other changes may\n still have applied. Do not repeat it: re-read with `list_comments`, then\n decide.\n"
7
+ "guide_1": "\n\n## Whose decks\n\nEvery call acts as the Workspace account deployment config maps the caller\nto, and reaches exactly the decks that person can open; an agent acts as the\nowner 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 account. A call with\nnone mapped fails `auth_required`; only an operator can change that. A deck the person cannot see and one that does not exist\nfail alike — Google does not say which.\n\n## Finding a deck\n\nSlides cannot list or search decks; that is Google Drive's job. Take the id\nfrom a Drive search or from a URL (`/presentation/d/<id>/`).\n\n## Reading\n\n- `get_presentation` returns each slide's text in reading order (top to\n bottom, then left to right), speaker notes, and alt text for images,\n videos, and charts. Empty placeholders are listed with their id and type;\n lines and other empty shapes are counted in `omittedElements`. `index`\n is 0-based, the numbering `create_slide` takes.\n- Nothing is cut without a way back. Text past `maxCharsPerSlide`, or past\n what one result can carry (about 192 KB), ends with a marker, and its\n element's `textCursor` (the slide's `notesCursor` for notes) is a\n `get_page` cursor that continues from the cut; pass it with the page it came\n from (the slide's `notesPageId` for a `notesCursor`). A slide too crowded to\n list whole gives `elementsNotShown` and an `elementsCursor`.\n- `get_page` reads any page by objectId — a slide, a layout (its\n placeholders' types and indexes, for `placeholderIdMappings`), a master,\n a notes page (`notesPageId`), or the notes master (`notesMasterId`) —\n listing every element, groups opened,\n and continuing long text across pages from `page.nextCursor`.\n- `list_layouts` lists masters and their layouts; `get_presentation`\n previews them, and `layoutsCursor` continues where the preview stops.\n- `raw: true` returns Slides' own JSON: `get_presentation` a page of\n slides, `get_page` the page and its top-level elements. One too large\n for a result is named (`rawNotShown`) or sent in `rawJson` chunks;\n join the chunks' `json` in order and parse.\n- Every cursor is bound to the deck and the revision it was read at. A\n `conflict` on a cursor means the deck changed: start again without one.\n A page may hold fewer rows than `limit`; follow `page.nextCursor`.\n- `get_slide_thumbnail` returns a link, never the image. The link opens as\n this person for about 30 minutes; do not share it.\n\n## Writing\n\n- `create_presentation` and `create_slide` are additive. A new slide is\n empty: fill it with `batch_update_presentation` (`insertText` into its\n placeholders, after reading their ids with `get_presentation`).\n- `replace_all_text` changes every literal match, case-sensitive unless\n `matchCase: false`. Pass `requiredRevisionId` to refuse a deck that\n changed since you read it.\n- `batch_update_presentation` takes Slides' own Request objects and always\n requires `requiredRevisionId`: the `revisionId` from the read the requests\n were built on. All\n requests apply or none do, except comment changes (below). Each write\n returns the new `revisionId` for the next one. A `conflict` means the\n deck changed since that read: re-read it, rebuild the requests, and send\n them with the new `revisionId`. Its replies keep every id at every depth\n whole, in every reply; text is cut first, and a large reply's other fields\n are named in `cut`. Ids too many for this tool's 192 KiB budget are all\n returned anyway, without text; past 256 KiB, more than a program can\n receive, call the tool directly and page with `connecta.result`. A batch\n whose ids pass even that returns its comment state and `repliesNotShown`;\n read what it made with `list_comments`, `get_presentation`, or\n `get_page`, and do not send it again.\n- An id or revision too long to copy into a result is left out and flagged\n (`revisionIdNotShown`), never cut. A failure that says the write\n applied means Google answered 2xx: do not repeat it; re-read instead.\n- Nothing here shares, moves, or deletes a deck.\n\n## Comments\n\n- `list_comments` lists the deck's threads, or one page's with\n `pageObjectId` (a speaker-notes comment is on the notes page). Each has\n its `commentId`, `status` (OPEN or RESOLVED), where it is anchored\n (`pageObjectIds`, `objectIds`), the `quote` it was made on, its\n `headPost`, and `replies`. A post's `author.user` (`users/…`) is\n the stable id; `displayName` is only a name. Slides gives no `user`\n for an anonymous or imported post. Text too long for one result continues\n on the next page: a row with `continued` picks up its thread where the\n last stopped, and `repliesOffset` counts the replies before it. Its\n cursor is bound to the threads and their anchors as well as the revision,\n so a comment added or moved between pages is a `conflict` too. Reading\n comments needs comment access; a view-only account is refused.\n- The projection leaves out each post's HTML (`contentHtml`), whether it\n came from a copied deck, and the text and cell ranges an anchor covers.\n `raw: true` returns each thread as Slides sends it, with\n `commentAnchors` added (its anchors, each with its `pageObjectId`),\n in `rawJson` chunks when one is too large.\n- `create_comment` and `create_comment_reply` are additive and return\n every new id whole. `update_comment_thread` resolves, reopens, or\n reassigns a thread (one at a time; a reassignment needs `content`),\n replacing its status or assignee, and is destructive,\n like `update_comment_post`, `delete_comment`, and\n `delete_comment_reply`; Slides allows the last three only to the\n post's author. Comments notify people the way the Slides editor does.\n- Slides saves comment changes apart from the rest of a write. When it does\n not confirm they saved, the result carries `commentUpdateState` — for\n example `ALL_FAILED_UNKNOWN_REASON` — and the write's other changes may\n still have applied. Do not repeat it: re-read with `list_comments`, then\n decide.\n"
8
8
  }
9
9
  };
@@ -2,7 +2,7 @@ import { retryAfterMs } from "../../connectors/guarded-fetch.js";
2
2
  import { ConnectorCallError } from "../../errors.js";
3
3
  import { OperationIndex } from "../_shared/rest/operation-index.js";
4
4
  import { callRest, restCall, restTransport, } from "../_shared/rest/tools.js";
5
- import { valueSafety } from "../_shared/rest/value-safety.js";
5
+ import { valueSafety, withholdEmbeddedUrlsIn, refuseWithheldLinks } from "../_shared/rest/value-safety.js";
6
6
  import { openapi } from "./openapi.generated.js";
7
7
  import { STRIPE_VALUE_SAFETY } from "./value-safety.js";
8
8
  /** Stripe's REST origin. */
@@ -137,7 +137,7 @@ function refuse(call) {
137
137
  * the type, code, decline code, and param still route.
138
138
  */
139
139
  function stripeFailure(status, headers, body, withheld = false) {
140
- const error = record(record(body)["error"]);
140
+ const error = record(record(withholdEmbeddedUrlsIn(body))["error"]);
141
141
  const code = text(error["code"]);
142
142
  const kind = [text(error["type"]), code, text(error["decline_code"])].filter(Boolean).join(" ");
143
143
  const param = text(error["param"]);
@@ -277,6 +277,13 @@ export function stripeRest(mode, connectedAccount) {
277
277
  failure: (status, headers, body, call) => stripeFailure(status, headers, body, call ? SAFETY.withholdsErrors(call.op) : false),
278
278
  readPosts: READ_POSTS,
279
279
  valueSafety: SAFETY,
280
+ admit(call) {
281
+ if (call.method === "GET" ||
282
+ call.method === "HEAD" ||
283
+ READ_POSTS.some(([method, path]) => method === call.method && path === call.op.path))
284
+ return;
285
+ refuseWithheldLinks([call.body, call.query, call.path], "Stripe");
286
+ },
280
287
  refuse,
281
288
  encode,
282
289
  page,
@@ -4,7 +4,7 @@ export const skill = {
4
4
  "instructionsHeading": "Account instructions",
5
5
  "fragments": {
6
6
  "oauth": "\n\nThis OAuth session may expose both live and sandbox Stripe accounts. Discover the live account-selection tools, select the intended account and mode, then carry exact context fields such as `stripe_context` and `livemode` wherever the schema requires them. A live-mode write moves real money; a sandbox write changes test data. Never infer the account or mode from connector metadata.\n\n- Discover account and mode with the live account-selection tools before every account-scoped read or write. The published inventory has changed account-tool names; do not assume `list_available_accounts_or_orgs` is available. Select the intended result and carry the exact context fields required by the live schema unchanged. If the account, mode, or supported selector is ambiguous, stop and ask; never guess.\n- Organization accounts are not Stripe Connect connected accounts. A Connect call requires a separate API-key connector whose `connectedAccount` sends Stripe's documented `Stripe-Account` header; OAuth does not support that path.\n- Four generic tools reach any Stripe API method. Find the method with `stripe_api_search`, read its parameters with `stripe_api_details`, then call `stripe_api_read` (GET) or `stripe_api_write` (POST/PATCH/PUT/DELETE). Never guess a path or a parameter name — `stripe_api_details` is cheaper than a rejected write.\n- Prefer a dedicated tool when one covers the task: `get_balance_summary` for balances and `stripe_analytics` for Sigma or Metrics reporting. Use `stripe_api_search` for everything else instead of assuming a retired dedicated tool still exists.\n- `stripe_implementation_planner` and the query-execution intents of `stripe_analytics` create provider-side planning or query-run state. Connecta therefore classifies both as writes, which a read-only endpoint refuses; their retrieval paths stay behind the same tool boundary.\n- `stripe_api_write` carries the blast radius of the entire write API — every POST, PATCH, PUT, and DELETE, from a customer edit to a subscription cancellation. State the method and path explicitly; a read-only endpoint refuses it.\n- Any `stripe_api_read` list or `stripe_api_search` that returns full objects belongs inside `execute_code`, projected to the fields the question needs before `return`. Neither `limit` nor `expand` substitutes for that: an unprojected list of customers or invoices truncates long before it answers, and a projected one keeps the customer's name, email, and address out of the transcript.\n- Find an object with `stripe_api_search` (or a list endpoint through `stripe_api_read`) and carry the `id` it returned into the write.\n- This connection's tool list is not a fixed set. Stripe gates parts of its MCP catalog by account, integration, and beta enrollment, so search this connector for what it actually exposes rather than assuming a documented tool is here.\n- Send an `Idempotency-Key` on every write you might retry, if the tool accepts it, and reuse the same key for the retry. A retry with a fresh key is a second charge, not a second attempt.\n",
7
- "key": "\n\n- This is Connecta's REST connector to `api.stripe.com`, holding one operator-managed key. It reaches exactly one account in one mode, so there is no account selection and no `stripe_context` or `livemode` argument.\n- `stripe_api_search` finds API operations, not records: it searches the pinned API index and names the tool that calls each operation. `stripe_api_details` returns one operation's parameters with types, enums, and required names. Neither sends a request.\n- `stripe_api_read` calls GET operations, plus `POST /v1/invoices/create_preview`, which only previews. `stripe_api_write` calls every other POST and every DELETE, from a customer edit to a subscription cancellation, with `method` and `path` stated; a read-only endpoint refuses it.\n- Paths are concrete: substitute ids into the template search returned (`/v1/payment_intents/pi_123`). Pass `query` and `body` as JSON objects named as `stripe_api_details` lists them. Connecta form-encodes v1 bodies and query lists (`expand: [\"latest_charge\"]`), sends v2 bodies as JSON, and pins `Stripe-Version` to the index's API version. Send `null` or `\"\"` to unset a field.\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 Stripe, so fix the call from that answer.\n- Results arrive as `{ status, data, page? }`. On a list, `page.next` is the cursor and `page.param` the query parameter to pass it as: `starting_after`, or `page` for search and v2 lists. Lists belong inside `execute_code`; pass `select` (dot paths such as `data.id` and `data.status`) to keep only the fields the question needs, which also keeps customer names, emails, and addresses out of the transcript.\n- `get_stripe_account_info` confirms the account, mode, and connected account; `get_balance_summary` returns available and pending balances. Use them when they answer the question.\n- Find records with list and search operations through `stripe_api_read` (`GET /v1/customers/search`, `GET /v1/charges` with `customer`) and carry the returned `id` into the write.\n- `stripe_api_write` sends an `Idempotency-Key` on every POST. Pass `idempotencyKey` for anything you might retry, or reuse the generated one the result returns. An ambiguous failure carries it in its message or as `uncertainCall.recovery.idempotencyKey`. Reuse it only with the exact original arguments and within Stripe's retention (v1 keys may be pruned after 24 hours); after that, look the object up before retrying. A retry with a fresh key is a second charge, not a second attempt.\n- Refused by design: operations that hand out a credential (ephemeral keys, Terminal connection tokens, account links (v1 and v2), Terminal onboarding links, Express login links, account sessions, customer sessions, billing portal sessions, file link creation, Financial Connections session creation, and the meter event session), Stripe Apps secret payloads (`expand` of `payload`), Issuing card `number` and `cvc` expansion on any Issuing path, multipart uploads such as `POST /v1/files` (use the Dashboard), and the meter event stream (send single events with `POST /v2/billing/meter_events`). Each refusal names its reason. Quote PDFs arrive from `files.stripe.com` as base64.\n- Secrets read as `\"[redacted]\"` in every result, expansions included: client secrets (PaymentIntents, SetupIntents, Checkout and Identity sessions, Sources, Financial Connections, invoice confirmation secrets), webhook and event destination signing secrets, Apps secret payloads, app-install authorization codes, Terminal Wi-Fi passwords, forwarded request header values, and Issuing card numbers and CVCs. Webhook and event destination URLs, file link URLs (in an expanded File, a list, or an event too), and pre-signed import URLs keep only their scheme and host. Identity verification `url`s come back whole. A Checkout Session's `url` comes back whole only for a guest payment session (`mode: \"payment\"`, no `customer` or `customer_account`, no customer creation, no saved payment method options); any other session returns only its scheme and host, because its page can show, reuse, or remove a customer's saved payment methods. Create those from your own server code, or send a payment link or the invoice's hosted page instead. These rules follow each object wherever it appears, expansions and events included. A `[redacted]` field is never the real value; do not send it back.\n- Failures of credential-bearing operations (webhook endpoints, Apps secrets, Terminal configurations, forwarding) carry Stripe's type, code, decline code, and param, not its message. Payment, billing, and account failures keep Stripe's message.\n- The hosted OAuth connection's natural-language Sigma, metrics, documentation search, implementation planner, and feedback tools do not exist with a key.\n",
7
+ "key": "\n\n- This is Connecta's REST connector to `api.stripe.com`, holding one operator-managed key. It reaches exactly one account in one mode, so there is no account selection and no `stripe_context` or `livemode` argument.\n- `stripe_api_search` finds API operations, not records: it searches the pinned API index and names the tool that calls each operation. `stripe_api_details` returns one operation's parameters with types, enums, and required names. Neither sends a request.\n- `stripe_api_read` calls GET operations, plus `POST /v1/invoices/create_preview`, which only previews. `stripe_api_write` calls every other POST and every DELETE, from a customer edit to a subscription cancellation, with `method` and `path` stated; a read-only endpoint refuses it.\n- Paths are concrete: substitute ids into the template search returned (`/v1/payment_intents/pi_123`). Pass `query` and `body` as JSON objects named as `stripe_api_details` lists them. Connecta form-encodes v1 bodies and query lists (`expand: [\"latest_charge\"]`), sends v2 bodies as JSON, and pins `Stripe-Version` to the index's API version. Send `null` or `\"\"` to unset a field.\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 Stripe, so fix the call from that answer.\n- Results arrive as `{ status, data, page? }`. On a list, `page.next` is the cursor and `page.param` the query parameter to pass it as: `starting_after`, or `page` for search and v2 lists. Lists belong inside `execute_code`; pass `select` (dot paths such as `data.id` and `data.status`) to keep only the fields the question needs, which also keeps customer names, emails, and addresses out of the transcript.\n- `get_stripe_account_info` confirms the account, mode, and connected account; `get_balance_summary` returns available and pending balances. Use them when they answer the question.\n- Find records with list and search operations through `stripe_api_read` (`GET /v1/customers/search`, `GET /v1/charges` with `customer`) and carry the returned `id` into the write.\n- `stripe_api_write` sends an `Idempotency-Key` on every POST. Pass `idempotencyKey` for anything you might retry, or reuse the generated one the result returns. An ambiguous failure carries it in its message or as `uncertainCall.recovery.idempotencyKey`. Reuse it only with the exact original arguments and within Stripe's retention (v1 keys may be pruned after 24 hours); after that, look the object up before retrying. A retry with a fresh key is a second charge, not a second attempt.\n- Refused by design: operations that hand out a credential (ephemeral keys, Terminal connection tokens, account links (v1 and v2), Terminal onboarding links, Express login links, account sessions, customer sessions, billing portal sessions, file link creation, Financial Connections session creation, and the meter event session), Stripe Apps secret payloads (`expand` of `payload`), Issuing card `number` and `cvc` expansion on any Issuing path, multipart uploads such as `POST /v1/files` (use the Dashboard), and the meter event stream (send single events with `POST /v2/billing/meter_events`). Each refusal names its reason. Quote PDFs arrive from `files.stripe.com` as base64.\n- Secrets read as `\"[redacted]\"` in every result, expansions included: client secrets (PaymentIntents, SetupIntents, Checkout and Identity sessions, Sources, Financial Connections, invoice confirmation secrets), webhook and event destination signing secrets, Apps secret payloads, app-install authorization codes, Terminal Wi-Fi passwords, forwarded request header values, and Issuing card numbers and CVCs. Webhook and event destination URLs, file link URLs (in an expanded File, a list, or an event too), and pre-signed import URLs keep only their scheme and host. Identity verification `url`s come back whole. A Checkout Session's `url` comes back whole only for a guest payment session (`mode: \"payment\"`, no `customer` or `customer_account`, no customer creation, no saved payment method options); any other session returns only its scheme and host, because its page can show, reuse, or remove a customer's saved payment methods. Create those from your own server code, or send a payment link or the invoice's hosted page instead. These rules follow each object wherever it appears, expansions and events included. A `[redacted]` field is never the real value; do not send it back.\n- Failures of credential-bearing operations (webhook endpoints, Apps secrets, Terminal configurations, forwarding) carry Stripe's type, code, decline code, and param, not its message. Payment, billing, and account failures keep Stripe's message.\n- The hosted OAuth connection's natural-language Sigma, metrics, documentation search, implementation planner, and feedback tools do not exist with a key.\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 charge `failure_message` and `outcome.seller_message` wherever charge objects appear, including event partial copies and vendor error text. Ordinary `code`/`key` links can therefore be false positives in these diagnostics. Descriptions, metadata, price nicknames, invoice footers and notes 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. 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.\n",
8
8
  "shared": "- Lists are cursor-paginated: `limit` defaults to 10 and caps at 100, `starting_after` and `ending_before` take an object id and are mutually exclusive, and `has_more` says whether to continue. Page inside `execute_code`.\n- Search filters on a documented per-resource field set, not on arbitrary attributes. Charges search takes `amount`, `created`, `currency`, `customer`, `status`, `refunded`, `disputed`, `metadata`, `billing_details.address.postal_code`, and `payment_method_details.<source>.*` card fields — there is no `payment_intent` field. When the field you want is not searchable, retrieve the parent object and follow its reference (the PaymentIntent's `latest_charge`) instead of retrying the search with another spelling.\n- The account → `stripe_api_search` → `stripe_api_details` → `stripe_api_read` sequence is one program, not four turns: resolve the method once, then call that method as many times as the investigation needs in the same run.\n- Decline outcomes live on the charge — `outcome`, `failure_code`, `failure_message` — reached from the PaymentIntent's `latest_charge`, so \"why did this payment fail\" is a PaymentIntent read followed by one charge read.\n- Resolve ids before acting; never guess one. Stripe ids are typed prefixes — `cus_` customer, `sub_` subscription, `ch_` charge, `pi_` payment intent, `in_` invoice, `acct_` account — and a plausible-looking id belongs to a different object or to nobody.\n- Amounts are integers in the currency's minor unit: `1099` is 10.99 USD, and zero-decimal currencies like JPY take `10` for 10 JPY. Never send a decimal.\n- Treat every create, update, delete, refund, and report run as a write. For guest calls, classification, and routing, fetch `skills({ name: \"usage\" })`.\n",
9
9
  "oauth_limits": "- Stripe answers a rate limit with `429` and a `Stripe-Rate-Limited-Reason` header; back off on that rather than retrying immediately. Stripe documents an account ceiling of 100 requests per second in live mode and 25 in sandbox mode, and any single endpoint is capped at 25 per second regardless of mode, so paging one list is the real constraint.\n- Use `search_stripe_documentation` when the shape of an object or a flow is unclear; it is a read and costs nothing but a call.\n- An `auth_required` failure means this connector's Stripe authorization is missing or expired: run `authorize_connector` for this connector id, then retry the same call unchanged. A rejected argument or a plan restriction comes back in Stripe's own words instead — read it rather than re-authorizing.\n",
10
10
  "key_limits": "- Stripe answers a rate limit with `429` and a `Stripe-Rate-Limited-Reason` header; back off on that rather than retrying immediately. Stripe documents an account ceiling of ",
@@ -48,6 +48,21 @@
48
48
  // - Issuing card numbers and CVCs come back only as expansions, which the
49
49
  // connector refuses on every Issuing path (`rest.ts`); the card operations
50
50
  // also redact both fields.
51
+ // Embedded URL verdict (#812): reviewed paths + global signing parameters.
52
+ // Evidence: digest-verified Stripe v2555, components/schemas/charge's
53
+ // failure_message ("Message to user further explaining reason for charge
54
+ // failure") and charge_outcome.seller_message (Stripe's human-readable
55
+ // outcome/reason). Scan those paths on charge objects, including expansions,
56
+ // events and previous_attributes. All vendor error text is scanned too.
57
+ // Product/customer/charge/invoice descriptions, metadata, price nicknames,
58
+ // invoice footers and notes are customer data: generic code/key/token names
59
+ // there retain their bytes, even in whole URL strings. The required literal
60
+ // X-Amz-/X-Goog- Signature/Credential/Security-Token defense applies globally.
61
+ // file.url/file_link.url and catalog-import upload/download capabilities are
62
+ // structured and retain existing resource/operation rules. The v2555 review
63
+ // found no documented vendor-minted link embedded in other free text; no
64
+ // Stripe-wide file-host/bucket policy is justified. Errors and diagnostic
65
+ // paths can have false positives for ordinary code/key links.
51
66
  import { redact, refuse, safe, vendorErrors, } from "../_shared/rest/value-safety.js";
52
67
  const EPHEMERAL_KEY = "It mints an ephemeral API key whose `secret` makes authorized Stripe API requests for a customer or Issuing card.";
53
68
  const CONNECTION_TOKEN = "It mints a Terminal connection token whose `secret` connects a reader SDK to this account.";
@@ -698,6 +713,13 @@ const RESOURCES = {
698
713
  },
699
714
  };
700
715
  export const STRIPE_VALUE_SAFETY = {
716
+ embeddedUrls: {
717
+ resources: {
718
+ key: "object",
719
+ partials: ["previous_attributes"],
720
+ paths: { charge: ["failure_message", "outcome.seller_message"] },
721
+ },
722
+ },
701
723
  title: "Stripe",
702
724
  operations: OPERATIONS,
703
725
  fields: FIELDS,
@@ -46,7 +46,7 @@ import { skill } from "./skill.generated.js";
46
46
  * as `.md` embeds a one-operation OpenAPI 3.1 snippet. `scripts/drift-check.mjs`
47
47
  * assembles those snippets for the pages listed in
48
48
  * `src/providers/tithely/drift.json` and digests each operation like any
49
- * other provider's, so `npm run providers:check -- --provider tithely` runs
49
+ * other provider's, so `pnpm run providers:check --provider tithely` runs
50
50
  * credential-free.
51
51
  */
52
52
  import { apiConnector as api, defined } from "../../connectors/api-connector.js";
@@ -5,7 +5,7 @@ import { skill } from "./skill.generated.js";
5
5
  * generated model graph. The token connector reads a pinned operation index
6
6
  * compiled from Vercel's published OpenAPI document (https://openapi.vercel.sh/,
7
7
  * unversioned, so pinned by content hash in `openapi.source.json`);
8
- * `npm run providers:check -- --provider vercel` reports when the live document
8
+ * `pnpm run providers:check --provider vercel` reports when the live document
9
9
  * moves, and `src/providers/vercel/drift.json` also records the contract digest
10
10
  * of every endpoint a named tool is written against.
11
11
  */
@@ -3,7 +3,7 @@ import { ConnectorCallError } from "../../errors.js";
3
3
  import { OperationIndex } from "../_shared/rest/operation-index.js";
4
4
  import { callRest, restCall, unknownOutcome, restTransport, } from "../_shared/rest/tools.js";
5
5
  import { openapi } from "./openapi.generated.js";
6
- import { valueSafety } from "../_shared/rest/value-safety.js";
6
+ import { valueSafety, withholdEmbeddedUrlsIn, refuseWithheldLinks, refuseWithheldUpload, refuseWithheldBase64, } from "../_shared/rest/value-safety.js";
7
7
  import { VERCEL_VALUE_SAFETY } from "./value-safety.js";
8
8
  /** Vercel's public REST origin. Override only for a proxy or test double. */
9
9
  export const VERCEL_API_BASE_URL = "https://api.vercel.com";
@@ -89,7 +89,7 @@ function reviewedCode(value) {
89
89
  * value: it says the status, a reviewed code, and what to do.
90
90
  */
91
91
  function vercelFailure(status, headers, payload, family = false) {
92
- const root = record(payload);
92
+ const root = record(withholdEmbeddedUrlsIn(payload));
93
93
  const error = record(root["error"]);
94
94
  const code = text(error["code"]);
95
95
  const message = text(error["message"])?.trim() ?? text(root["message"])?.trim() ?? `Vercel returned HTTP ${status}.`;
@@ -456,6 +456,22 @@ export function vercelRest(options) {
456
456
  failure: (status, headers, body, call) => vercelFailure(status, headers, body, call ? SAFETY.withholdsErrors(call.op) : false),
457
457
  readPosts: READ_POSTS,
458
458
  valueSafety: SAFETY,
459
+ async admit(call) {
460
+ if (call.method === "GET" ||
461
+ call.method === "HEAD" ||
462
+ READ_POSTS.some(([method, path]) => method === call.method && path === call.op.path))
463
+ return;
464
+ refuseWithheldLinks([call.body, call.query, call.path], "Vercel");
465
+ if (call.method === "POST" && call.op.path === "/v13/deployments") {
466
+ const files = record(call.body)["files"];
467
+ if (Array.isArray(files)) {
468
+ for (const file of files) {
469
+ if (record(file)["encoding"] === "base64")
470
+ await refuseWithheldBase64(record(file)["data"], "Vercel");
471
+ }
472
+ }
473
+ }
474
+ },
459
475
  refuse,
460
476
  scope(call) {
461
477
  const query = { ...call.query };
@@ -909,6 +925,8 @@ export function vercelRest(options) {
909
925
  // Decode first, then validate what was supplied: a required binary body
910
926
  // is present whenever bytes are (the index types it as a string).
911
927
  const bytes = decodeUpload(args);
928
+ await vendor.admit?.(call, ctx);
929
+ await refuseWithheldUpload(bytes, "Vercel");
912
930
  operations.check(call.op, call.query, String(args["base64Body"] ?? args["textBody"]));
913
931
  const digest = call.op.path === "/v2/files" ? await sha1(bytes) : undefined;
914
932
  const query = {};
@@ -3,8 +3,8 @@ export const skill = {
3
3
  "content": "- Resolve team, project, and deployment ids with list reads; never guess one. Team ids begin `team_`, project ids `prj_`, and deployment ids `dpl_`.\n- Diagnose a deployment by reading its state first (`readyState`, `errorCode`, `errorMessage`), then its build logs, then runtime logs for a deployment that built. Narrow time windows before raising limits.\n- Environment variable changes apply only to future deployments; a new deployment is a separate write. Promoting an existing deployment to production does not rebuild it.\n- Treat every create, update, delete, promotion, cancellation, and purchase 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\nTool names, descriptions, argument schemas, and result schemas come from Vercel's live server. Connecta applies release-reviewed classification; reviewed writes stay writes even when Vercel claims they only read.\n\n- Discover the live catalog before assuming a tool exists. Vercel can change the surface independently of a Connecta release, and account features may affect what the authorization can reach.\n- Complete authorization and grant access to the owning team before acting. Listing this server does not prove team access.\n- MCP pagination and arguments follow the live MCP schemas; do not apply REST pagination or argument shapes to them.\n- Diagnose deployments with `get_deployment`, then build logs, runtime error clusters, and runtime logs.\n- `get_project_env` and `filter_project_envs` can return decrypted environment values, so Connecta reviews them as writes, and a read-only endpoint refuses them. Prefer reading variable keys and targets without values.\n- Purchase tools change billing. Read a quote first and carry its price, idempotency key, and requested term into the confirmed purchase unchanged.\n- `get_access_to_vercel_url` creates a temporary access grant. Treat the URL it returns as a credential and do not expose it outside the requested task.\n- `deploy_to_vercel` and `import-claude-design-from-url` can create or update live projects. Read the target and deployment mode before invoking them.\n- An `auth_required` failure means this connector's OAuth grant is missing or expired. Run `authorize_connector` for this connector id, then retry.\n",
7
- "key": "\n\n- This is Connecta's REST connector to `api.vercel.com`, holding one operator-managed access token. A default team routes calls; it does not narrow what the token can reach.\n- `vercel_api_search` finds API operations, not records: it searches the pinned API index and names the tool that calls each operation. `vercel_api_details` returns one operation's parameters with types, enums, and required names. Neither sends a request.\n- `vercel_api_read` calls GET operations, plus reviewed POSTs that only read: `POST /v2/observability/query`, registrar availability, price, and search, and `POST /v8/artifacts`. `vercel_api_write` calls every other POST, PUT, PATCH, and DELETE, with `method` and `path` stated; a read-only endpoint refuses it.\n- Paths are concrete and carry the version search returned: `/v13/deployments/dpl_123`, not `/deployments/dpl_123`. Pass `query` and `body` as JSON objects named as `vercel_api_details` lists them. Every call is checked against the pinned index before it is sent: an unknown path returns the nearest operations, and an unknown or missing parameter returns `validation.issues` with the accepted names. Nothing reached Vercel, so fix the call from that answer.\n- Results arrive as `{ status, data, page? }`. On a list, `page.next` is the cursor and `page.param` the query parameter to pass it as: `until` (a millisecond timestamp from `pagination.next`) on most lists, `cursor` on newer ones. Lists belong inside `execute_code`; pass `select` (dot paths such as `deployments.uid` and `deployments.state`) to keep only the fields the question needs.\n- `list_teams` returns team ids and slugs and the connection's default team. `get_deployment_build_logs` reads a deployment's build events without following live output. `get_runtime_logs` collects the runtime log stream for at most `waitMs` or `maxRows`, then stops and says why. Generic reads refuse `follow` and the runtime-log stream, which never end on their own.\n- `vercel_api_upload` sends explicit text or base64 bytes to an octet-stream operation, such as `POST /v2/files` before a deployment (Connecta computes `x-vercel-digest`). `vercel_api_write` sends JSON only.\n- Environment values never come back. `list_project_env_vars` never decrypts. `upsert_project_env_var` (`upsert: false` for create-only), `update_project_env_var`, and `delete_project_env_var` return metadata without values; values are write input only. Generic reads refuse `GET /v1/projects/{idOrName}/env/{id}`, `GET /v10/projects/{idOrName}/env`, `GET /v1/env/{id}`, and any `decrypt`, and Connecta replaces the value of every environment variable in any result with `\"[redacted]\"`, including project reads, shared variables, and audit events.\n- Other secrets read as `[redacted]` in every result: credential-named fields and their whole contents (`token`, `secret`, `password`, `jwt`, `credentials`, `Authorization`, …), protection-bypass secrets (each bypass's metadata stays under a numbered placeholder), deploy hook URLs, drain and webhook headers and signing secrets (including the one a new webhook or drain returns), drain and webhook destination URLs and external route, rewrite, and redirect destinations beyond their scheme and host, the args and values of every header, cookie, and query rule (route transforms and conditions, firewall conditions), team invite codes, synced Global Config items, and credentials inside other URLs. Failures of every reviewed operation, and of any operation whose request takes a credential-named field, say only the status and a reviewed code. Read or rotate withheld secrets in the Vercel dashboard. A `[redacted]` field is never the real value; do not send it back.\n- Refused by design: endpoints that mint credentials (access tokens, AI Gateway API keys, project OIDC and trace tokens, Connect and SSO tokens, Edge Config tokens, KMS-signed tokens, Flags SDK keys, protection-bypass secrets, installation credential rotation), Connect authorization requests, project transfer requests (the code lets another team claim the project), KMS signing, a domain move-out (its answer is a transfer token), Global Config items, backups, and tokens, and domain transfer auth codes. Each refusal names its reason.\n- The hosted OAuth connection's documentation search, runtime error clusters, toolbar threads, agent runs, web analytics summaries, purchase quotes, `deploy_to_vercel`, and access grants for protected URLs do not exist with a token. Configure an OAuth connector for those.\n- A `429` carries Vercel's reset time; Vercel meters endpoints separately, so wait before retrying the same operation. An `auth_required` failure means the token is missing, expired, or outside this team: an operator must replace it in this connection in the operator UI, and `authorize_connector` returns that handoff. A rejected argument comes back in Vercel's own words; read it rather than replacing the token.\n",
6
+ "oauth": "\n\nTool names, descriptions, argument schemas, and result schemas come from Vercel's live server. Connecta applies release-reviewed classification; reviewed writes stay writes even when Vercel claims they only read.\n\n- Discover the live catalog before assuming a tool exists. Vercel can change the surface independently of a Connecta release, and account features may affect what the authorization can reach.\n- Complete authorization and grant access to the owning team before acting. Listing this server does not prove team access.\n- MCP pagination and arguments follow the live MCP schemas; do not apply REST pagination or argument shapes to them.\n- Diagnose deployments with `get_deployment`, then build logs, runtime error clusters, and runtime logs.\n- `get_project_env` and `filter_project_envs` can return decrypted environment values, so Connecta reviews them as writes, and a read-only endpoint refuses them. Prefer reading variable keys and targets without values.\n- Purchase tools change billing. Read a quote first and carry its price, idempotency key, and requested term into the confirmed purchase unchanged.\n- `get_access_to_vercel_url` creates a temporary access grant. Treat the URL it returns as a credential and do not expose it outside the requested task.\n- `web_fetch_vercel_url` runs application code and is reviewed as destructive; the toolbar reply, reaction, edit, and resolve tools are writes.\n- `deploy_to_vercel` and `import-claude-design-from-url` can create or update live projects. Read the target and deployment mode before invoking them.\n- An `auth_required` failure means this connector's OAuth grant is missing or expired. Run `authorize_connector` for this connector id, then retry.\n",
7
+ "key": "\n\n- This is Connecta's REST connector to `api.vercel.com`, holding one operator-managed access token. A default team routes calls; it does not narrow what the token can reach.\n- `vercel_api_search` finds API operations, not records: it searches the pinned API index and names the tool that calls each operation. `vercel_api_details` returns one operation's parameters with types, enums, and required names. Neither sends a request.\n- `vercel_api_read` calls GET operations, plus reviewed POSTs that only read: `POST /v2/observability/query`, registrar availability, price, and search, and `POST /v8/artifacts`. `vercel_api_write` calls every other POST, PUT, PATCH, and DELETE, with `method` and `path` stated; a read-only endpoint refuses it.\n- Paths are concrete and carry the version search returned: `/v13/deployments/dpl_123`, not `/deployments/dpl_123`. Pass `query` and `body` as JSON objects named as `vercel_api_details` lists them. Every call is checked against the pinned index before it is sent: an unknown path returns the nearest operations, and an unknown or missing parameter returns `validation.issues` with the accepted names. Nothing reached Vercel, so fix the call from that answer.\n- Results arrive as `{ status, data, page? }`. On a list, `page.next` is the cursor and `page.param` the query parameter to pass it as: `until` (a millisecond timestamp from `pagination.next`) on most lists, `cursor` on newer ones. Lists belong inside `execute_code`; pass `select` (dot paths such as `deployments.uid` and `deployments.state`) to keep only the fields the question needs.\n- `list_teams` returns team ids and slugs and the connection's default team. `get_deployment_build_logs` reads a deployment's build events without following live output. `get_runtime_logs` collects the runtime log stream for at most `waitMs` or `maxRows`, then stops and says why. Generic reads refuse `follow` and the runtime-log stream, which never end on their own.\n- `vercel_api_upload` sends explicit text or base64 bytes to an octet-stream operation, such as `POST /v2/files` before a deployment (Connecta computes `x-vercel-digest`). `vercel_api_write` sends JSON only.\n- Environment values never come back. `list_project_env_vars` never decrypts. `upsert_project_env_var` (`upsert: false` for create-only), `update_project_env_var`, and `delete_project_env_var` return metadata without values; values are write input only. Generic reads refuse `GET /v1/projects/{idOrName}/env/{id}`, `GET /v10/projects/{idOrName}/env`, `GET /v1/env/{id}`, and any `decrypt`, and Connecta replaces the value of every environment variable in any result with `\"[redacted]\"`, including project reads, shared variables, and audit events.\n- Other secrets read as `[redacted]` in every result: credential-named fields and their whole contents (`token`, `secret`, `password`, `jwt`, `credentials`, `Authorization`, …), protection-bypass secrets (each bypass's metadata stays under a numbered placeholder), deploy hook URLs, drain and webhook headers and signing secrets (including the one a new webhook or drain returns), drain and webhook destination URLs and external route, rewrite, and redirect destinations beyond their scheme and host, the args and values of every header, cookie, and query rule (route transforms and conditions, firewall conditions), team invite codes, synced Global Config items, and credentials inside other URLs. Failures of every reviewed operation, and of any operation whose request takes a credential-named field, say only the status and a reviewed code. Read or rotate withheld secrets in the Vercel dashboard. A `[redacted]` field is never the real value; do not send it back.\n- Refused by design: endpoints that mint credentials (access tokens, AI Gateway API keys, project OIDC and trace tokens, Connect and SSO tokens, Edge Config tokens, KMS-signed tokens, Flags SDK keys, protection-bypass secrets, installation credential rotation), Connect authorization requests, project transfer requests (the code lets another team claim the project), KMS signing, a domain move-out (its answer is a transfer token), Global Config items, backups, and tokens, and domain transfer auth codes. Each refusal names its reason.\n- The hosted OAuth connection's documentation search, runtime error clusters, toolbar threads, agent runs, web analytics summaries, purchase quotes, `deploy_to_vercel`, and access grants for protected URLs do not exist with a token. Configure an OAuth connector for those.\n- A `429` carries Vercel's reset time; Vercel meters endpoints separately, so wait before retrying the same operation. An `auth_required` failure means the token is missing, expired, or outside this team: an operator must replace it in this connection in the operator UI, and `authorize_connector` returns that handoff. A rejected argument comes back in Vercel's own words; read it rather than replacing the token.\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 deployment `errorMessage`, build-event `payload.text`/`text`, and runtime-log `message` on the reviewed deployment/log operations and vendor error text. Ordinary `code`/`key` links can therefore be false positives in these diagnostics. Deployment metadata and other application 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. Deployment creation also inspects decoded `files[].data` when `encoding` is `base64` (one decode bounded by the field's encoded length and the host's request limit; malformed base64 is refused). 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.\n",
8
8
  "shared": "- Resolve team, project, and deployment ids with list reads; never guess one. Team ids begin `team_`, project ids `prj_`, and deployment ids `dpl_`.\n- Diagnose a deployment by reading its state first (`readyState`, `errorCode`, `errorMessage`), then its build logs, then runtime logs for a deployment that built. Narrow time windows before raising limits.\n- Environment variable changes apply only to future deployments; a new deployment is a separate write. Promoting an existing deployment to production does not rebuild it.\n- Treat every create, update, delete, promotion, cancellation, and purchase as a write. For guest calls, classification, and routing, fetch `skills({ name: \"usage\" })`.\n"
9
9
  }
10
10
  };
@@ -23,6 +23,19 @@
23
23
  //
24
24
  // Residual risk, as with Infisical: a secret a person typed into a free-text
25
25
  // name, description, log line, or identifier is out of scope.
26
+ // Embedded URL verdict (#812): reviewed paths + global signing parameters.
27
+ // Evidence: pinned index/candidates sha256:32399b3ed273 identify deployment
28
+ // reads/creation/listing, build events and runtime logs; the operation/path
29
+ // list below scans errorMessage, deployments.errorMessage, payload.text/text
30
+ // and message respectively, both lists and individual named-stream rows.
31
+ // All vendor error text is scanned too. These diagnostics can have false
32
+ // positives for ordinary code/key links. Deployment meta, descriptions and
33
+ // other application content retain generic query parameters byte for byte.
34
+ // Literal X-Amz-/X-Goog- Signature/Credential/Security-Token values are
35
+ // withheld in every string. Existing structured-capability rules still apply.
36
+ // The original full response document is unavailable at its unversioned URL:
37
+ // current upstream differs and is supplementary evidence only. This is not a
38
+ // claim of complete pinned-response review or of a Vercel-wide host policy.
26
39
  import { redact, refuse as refusal, safe, under, } from "../_shared/rest/value-safety.js";
27
40
  /** A refusal's reason reads as a sentence about the operation: "It mints …". */
28
41
  const refuse = (reason) => refusal(`It ${reason}`);
@@ -287,6 +300,15 @@ const VALUE_SAFETY = {
287
300
  * names, which the heuristic leaves.
288
301
  */
289
302
  export const VERCEL_VALUE_SAFETY = {
303
+ embeddedUrls: {
304
+ operations: {
305
+ "GET /v13/deployments/{idOrUrl}": ["errorMessage"],
306
+ "POST /v13/deployments": ["errorMessage"],
307
+ "GET /v7/deployments": ["deployments.errorMessage"],
308
+ "GET /v3/deployments/{idOrUrl}/events": ["payload.text", "text"],
309
+ "GET /v1/projects/{projectId}/deployments/{deploymentId}/runtime-logs": ["message"],
310
+ },
311
+ },
290
312
  title: "Vercel",
291
313
  operations: VALUE_SAFETY,
292
314
  envBodies: /^\/v\d+\/(?:env(?:\/|$)|projects\/\{[^}]+\}\/env(?:\/|$))/,
@@ -130,14 +130,13 @@ export interface RegistryView {
130
130
  }
131
131
  /**
132
132
  * Connector id → the only tool names this view may see on it. A connector
133
- * absent from the map is visible whole. Derived from `connectorAccess`
134
- * addresses at the auth gate; never from caller input.
133
+ * absent from the map is visible whole. Derived from a pool's `tools`
134
+ * addresses at construction; never from caller input.
135
135
  */
136
136
  export type ToolAccess = ReadonlyMap<string, ReadonlySet<string>>;
137
137
  export interface RegistryScope {
138
138
  connectorIds: "all" | readonly string[];
139
139
  toolAccess?: ToolAccess;
140
- guardedToolAccess?: ToolAccess;
141
140
  subjectKey?: string;
142
141
  principalKey?: string;
143
142
  endpoint?: string;