@zackbart/connecta 0.30.0 → 0.32.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 (133) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +21 -16
  3. package/bin/connecta.mjs +1 -0
  4. package/bin/version.mjs +1 -1
  5. package/dist/auth-elicitation.js +1 -1
  6. package/dist/call-recovery.js +25 -0
  7. package/dist/catalog-service.js +7 -2
  8. package/dist/config-schema.d.ts +14 -7
  9. package/dist/config-schema.js +29 -10
  10. package/dist/config-value-sources.js +6 -3
  11. package/dist/config.d.ts +26 -8
  12. package/dist/config.js +38 -16
  13. package/dist/connector-access.d.ts +2 -1
  14. package/dist/connectors/guarded-fetch.d.ts +18 -1
  15. package/dist/connectors/guarded-fetch.js +21 -2
  16. package/dist/describe-config.d.ts +6 -3
  17. package/dist/describe-config.js +3 -2
  18. package/dist/errors.d.ts +3 -1
  19. package/dist/errors.js +1 -0
  20. package/dist/execute.js +16 -14
  21. package/dist/index.js +4 -3
  22. package/dist/invocation.js +39 -4
  23. package/dist/meta-tools.js +17 -19
  24. package/dist/operator-record.d.ts +2 -2
  25. package/dist/operator-record.js +2 -0
  26. package/dist/operator-ui/contract.d.ts +5 -4
  27. package/dist/operator-ui/generated.js +2 -2
  28. package/dist/operator-ui/model.d.ts +1 -1
  29. package/dist/operator-ui/view.d.ts +9 -0
  30. package/dist/operator-ui/view.js +12 -0
  31. package/dist/program-writes.js +4 -49
  32. package/dist/provider.d.ts +6 -5
  33. package/dist/provider.js +3 -4
  34. package/dist/providers/_shared/google/workspace.js +1 -1
  35. package/dist/providers/_shared/rest/dispatch.js +41 -0
  36. package/dist/providers/_shared/rest/operation-index.d.ts +133 -0
  37. package/dist/providers/_shared/rest/operation-index.js +527 -0
  38. package/dist/providers/_shared/rest/tools.d.ts +141 -0
  39. package/dist/providers/_shared/rest/tools.js +569 -0
  40. package/dist/providers/_shared/rest/value-safety.d.ts +151 -0
  41. package/dist/providers/_shared/rest/value-safety.js +580 -0
  42. package/dist/providers/breeze/skill.generated.js +2 -2
  43. package/dist/providers/cloudflare/graphql.js +385 -0
  44. package/dist/providers/cloudflare/index.d.ts +47 -51
  45. package/dist/providers/cloudflare/index.js +188 -1997
  46. package/dist/providers/cloudflare/openapi.generated.js +3347 -0
  47. package/dist/providers/cloudflare/rest.d.ts +37 -0
  48. package/dist/providers/cloudflare/rest.js +1056 -0
  49. package/dist/providers/cloudflare/skill.generated.js +7 -4
  50. package/dist/providers/cloudflare/value-safety.js +1247 -0
  51. package/dist/providers/docs/index.d.ts +1 -1
  52. package/dist/providers/docs/index.js +2 -2
  53. package/dist/providers/drive/index.d.ts +1 -1
  54. package/dist/providers/drive/index.js +1 -1
  55. package/dist/providers/forms/index.d.ts +1 -1
  56. package/dist/providers/forms/index.js +2 -2
  57. package/dist/providers/github/skill.generated.js +2 -2
  58. package/dist/providers/gmail/index.d.ts +1 -1
  59. package/dist/providers/gmail/index.js +274 -23
  60. package/dist/providers/gmail/skill.generated.js +2 -2
  61. package/dist/providers/linear/index.js +1 -1
  62. package/dist/providers/mixpanel/index.d.ts +27 -12
  63. package/dist/providers/mixpanel/index.js +58 -6
  64. package/dist/providers/mixpanel/operations.js +20 -0
  65. package/dist/providers/mixpanel/rest.js +603 -0
  66. package/dist/providers/mixpanel/skill.generated.js +3 -2
  67. package/dist/providers/mixpanel/value-safety.js +16 -0
  68. package/dist/providers/notion/index.d.ts +35 -31
  69. package/dist/providers/notion/index.js +76 -1499
  70. package/dist/providers/notion/openapi.generated.js +75 -0
  71. package/dist/providers/notion/rest.d.ts +24 -0
  72. package/dist/providers/notion/rest.js +1186 -0
  73. package/dist/providers/notion/skill.generated.js +5 -4
  74. package/dist/providers/notion/value-safety.js +31 -0
  75. package/dist/providers/planning-center/index.d.ts +27 -0
  76. package/dist/providers/planning-center/index.js +286 -24
  77. package/dist/providers/planning-center/skill.generated.js +24 -4
  78. package/dist/providers/sheets/index.d.ts +1 -1
  79. package/dist/providers/sheets/index.js +3 -3
  80. package/dist/providers/slides/index.d.ts +1 -1
  81. package/dist/providers/slides/index.js +3 -3
  82. package/dist/providers/stripe/index.d.ts +26 -20
  83. package/dist/providers/stripe/index.js +103 -130
  84. package/dist/providers/stripe/openapi.generated.js +649 -0
  85. package/dist/providers/stripe/rest.d.ts +14 -0
  86. package/dist/providers/stripe/rest.js +395 -0
  87. package/dist/providers/stripe/skill.generated.js +7 -5
  88. package/dist/providers/stripe/value-safety.js +705 -0
  89. package/dist/providers/vercel/index.d.ts +31 -22
  90. package/dist/providers/vercel/index.js +96 -853
  91. package/dist/providers/vercel/openapi.generated.js +456 -0
  92. package/dist/providers/vercel/rest.d.ts +20 -0
  93. package/dist/providers/vercel/rest.js +1002 -0
  94. package/dist/providers/vercel/skill.generated.js +4 -5
  95. package/dist/providers/vercel/value-safety.js +328 -0
  96. package/dist/providers/wispr-flow/index.d.ts +14 -0
  97. package/dist/providers/wispr-flow/index.js +117 -0
  98. package/dist/providers/wispr-flow/skill.generated.js +8 -0
  99. package/dist/routes/mcp.js +30 -10
  100. package/dist/routes/operator.js +14 -7
  101. package/dist/routes/ui-config.js +2 -2
  102. package/dist/server.js +3 -0
  103. package/dist/skills.js +5 -4
  104. package/dist/tool-safety.js +13 -5
  105. package/dist/types.d.ts +1 -1
  106. package/dist/ui.d.ts +1 -1
  107. package/dist/ui.js +1 -1
  108. package/dist/usage-guide.js +3 -3
  109. package/dist/version.d.ts +1 -1
  110. package/dist/version.js +1 -1
  111. package/dist/write-outcome.js +47 -0
  112. package/documentation/README.md +1 -0
  113. package/documentation/architecture.md +155 -28
  114. package/documentation/auth.md +86 -27
  115. package/documentation/code-mode.md +81 -72
  116. package/documentation/deploying.md +10 -5
  117. package/documentation/integrating.md +72 -9
  118. package/documentation/meta-tools.md +52 -38
  119. package/documentation/operating.md +11 -8
  120. package/documentation/operator-tests.md +2 -3
  121. package/documentation/operator-ui.md +9 -3
  122. package/documentation/provider-auth-migration.md +308 -0
  123. package/documentation/provider-migration-0.29.md +18 -6
  124. package/examples/worker/AGENTS.md +5 -0
  125. package/examples/worker/README.md +2 -2
  126. package/examples/worker/src/connecta.config.ts +5 -0
  127. package/package.json +7 -2
  128. package/templates/node/AGENTS.md +6 -4
  129. package/templates/node/README.md +6 -4
  130. package/templates/node/package.json +1 -1
  131. package/templates/node/src/connecta.config.ts +5 -0
  132. package/dist/providers/vercel/mcp-ownership.js +0 -212
  133. package/dist/tool-safety.d.ts +0 -9
@@ -1,10 +1,30 @@
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 — for: \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: . 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 `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",
4
4
  "instructionsHeading": "Organization instructions",
5
5
  "fragments": {
6
- "guide_0": "# Planning Center usage\n\nPlanning Center Online church management — People, Services, Groups, Check-Ins, Calendar, Registrations, Giving, Publishing, Webhooks — for: ",
7
- "guide_1": "\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: ",
8
- "guide_2": ". 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"
6
+ "heading": "# Planning Center usage\n\nPlanning Center Online church management — ",
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",
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
+ "writes": "\n## What each product lets the API write\n\n",
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",
13
+ "people_without_lists": "- People: full read/write, except lists. `update_person` with `status: inactive` archives reversibly; a DELETE is permanent and only reachable through `pco_api_mutate`.\n",
14
+ "services": "- Services: full read/write. A plan's dates come from its plan times. Scheduling needs a team.\n",
15
+ "groups": "- Groups: memberships and group settings are writable; groups cannot be created, and events and attendance are read-only.\n",
16
+ "check_ins_registrations": "- Check-Ins and Registrations: read-only. Nobody can be checked in or registered through the API.\n",
17
+ "check_ins": "- Check-Ins: read-only. Nobody can be checked in through the API.\n",
18
+ "registrations": "- Registrations: read-only. Nobody can be registered through the API.\n",
19
+ "calendar": "- Calendar: events, instances, and times are read-only; tags, resources, rooms, and folders are writable through `pco_api_mutate`.\n",
20
+ "giving": "- 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",
21
+ "publishing_api": "- Publishing and the `api` product (organization, OAuth applications, personal access tokens) have no named tools; use the hatches.\n",
22
+ "publishing": "- Publishing has no named tools; use the hatches.\n",
23
+ "api": "- The `api` product (organization, OAuth applications, personal access tokens) has no named tools; use the hatches.\n",
24
+ "hatches": "\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: ",
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
+ "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
+ "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"
9
29
  }
10
30
  };
@@ -48,7 +48,7 @@ export type SheetsOptions = GoogleWorkspaceOptions;
48
48
  *
49
49
  * Reads are read-only. `create_spreadsheet`, `add_sheet`, and `append_values`
50
50
  * are additive writes the host approves through call_destructive_tool or a
51
- * trusted program; the value writes, `clear_values`, and the raw
51
+ * program on a writable endpoint; the value writes, `clear_values`, and the raw
52
52
  * `batch_update_spreadsheet` are destructive.
53
53
  */
54
54
  export declare const sheets: import("../../provider.js").ProviderFactory<GoogleWorkspaceOptions>;
@@ -550,8 +550,8 @@ const SPREADSHEET_FIELDS = [
550
550
  // --- Tools ------------------------------------------------------------------------
551
551
  function tools(client) {
552
552
  const readOnly = { readOnlyHint: true };
553
- // Additive: nothing that existed is lost. Program writes require a trusted
554
- // pool; other writes use call_destructive_tool. The host controls approval.
553
+ // Additive: nothing that existed is lost. Writes require a writable
554
+ // endpoint: call_destructive_tool or a program. The host controls approval.
555
555
  const additive = { readOnlyHint: false, destructiveHint: false };
556
556
  const destructive = { readOnlyHint: false, destructiveHint: true };
557
557
  const spreadsheetPath = (args) => `/spreadsheets/${encodeURIComponent(String(args["spreadsheetId"]))}`;
@@ -1188,7 +1188,7 @@ function usageGuide(purpose, instructions) {
1188
1188
  *
1189
1189
  * Reads are read-only. `create_spreadsheet`, `add_sheet`, and `append_values`
1190
1190
  * are additive writes the host approves through call_destructive_tool or a
1191
- * trusted program; the value writes, `clear_values`, and the raw
1191
+ * program on a writable endpoint; the value writes, `clear_values`, and the raw
1192
1192
  * `batch_update_spreadsheet` are destructive.
1193
1193
  */
1194
1194
  export const sheets = asProviderFactory({
@@ -45,7 +45,7 @@ export type SlidesOptions = GoogleWorkspaceOptions;
45
45
  *
46
46
  * Reads run in programs. `create_presentation`, `create_slide`,
47
47
  * `create_comment`, and `create_comment_reply` are additive writes the host
48
- * approves through call_destructive_tool or a trusted program;
48
+ * approves through call_destructive_tool or a program on a writable endpoint;
49
49
  * `replace_all_text`, `update_comment_thread`, `update_comment_post`,
50
50
  * `delete_comment`, `delete_comment_reply`, and `batch_update_presentation`
51
51
  * are destructive.
@@ -2051,7 +2051,7 @@ function tools(client) {
2051
2051
  name: "create_presentation",
2052
2052
  description: "Create an empty Google Slides deck with one title slide in the user's My Drive root. It cannot place the deck in a folder or copy a template.",
2053
2053
  // Additive: a new file changes nothing that existed. Not read-only, so
2054
- // program calls require a trusted pool; other writes use
2054
+ // program calls require a writable endpoint; other writes use
2055
2055
  // call_destructive_tool. The host controls approval.
2056
2056
  annotations: { readOnlyHint: false, destructiveHint: false },
2057
2057
  inputSchema: input({
@@ -2239,7 +2239,7 @@ function tools(client) {
2239
2239
  name: "create_comment",
2240
2240
  description: "Comment on a slide, a notes page, an element, a range of its text, or a table cell, optionally assigning it. Adds a thread; Slides notifies as in the editor.",
2241
2241
  // Additive: a new thread changes nothing that existed. Not read-only,
2242
- // so program calls require a trusted pool; other writes use
2242
+ // so program calls require a writable endpoint; other writes use
2243
2243
  // call_destructive_tool. The host controls approval.
2244
2244
  annotations: { readOnlyHint: false, destructiveHint: false },
2245
2245
  inputSchema: input({
@@ -2594,7 +2594,7 @@ function usageGuide(purpose, instructions) {
2594
2594
  *
2595
2595
  * Reads run in programs. `create_presentation`, `create_slide`,
2596
2596
  * `create_comment`, and `create_comment_reply` are additive writes the host
2597
- * approves through call_destructive_tool or a trusted program;
2597
+ * approves through call_destructive_tool or a program on a writable endpoint;
2598
2598
  * `replace_all_text`, `update_comment_thread`, `update_comment_post`,
2599
2599
  * `delete_comment`, `delete_comment_reply`, and `batch_update_presentation`
2600
2600
  * are destructive.
@@ -1,10 +1,10 @@
1
- import { type RemoteMcpAuth } from "../../connectors/remote-mcp.js";
2
- /** Which Stripe environment a static credential reaches. */
3
- export type StripeMode = "production" | "sandbox";
1
+ import { type StripeMode } from "./rest.js";
2
+ export type { StripeMode } from "./rest.js";
3
+ export { STRIPE_API_BASE_URL } from "./rest.js";
4
4
  /** Stripe publishes one hosted MCP endpoint for every account and mode. */
5
5
  export declare const STRIPE_MCP_ENDPOINT = "https://mcp.stripe.com/";
6
6
  interface StripeCommonOptions {
7
- /** Human-readable display name; defaults to "Stripe" for OAuth. */
7
+ /** Human-readable display name; defaults to "Stripe" for OAuth and the mode for a key. */
8
8
  title?: string;
9
9
  /** Downstream auth ownership. Defaults to one shared deployment grant. */
10
10
  authScope?: "shared" | "personal";
@@ -15,33 +15,39 @@ interface StripeCommonOptions {
15
15
  /** Connector-specific inline result limit; omit to inherit the deployment. */
16
16
  maxResultBytes?: number;
17
17
  }
18
- /** OAuth sessions discover account and mode together from Stripe's live tools. */
18
+ /**
19
+ * Stripe's hosted MCP server over OAuth. One session can reach live and
20
+ * sandbox accounts; Stripe returns the mode with each account.
21
+ */
19
22
  export interface StripeOAuthOptions extends StripeCommonOptions {
20
- auth?: {
23
+ auth: {
21
24
  type: "oauth";
22
25
  };
26
+ /** Refused: OAuth reaches live and sandbox accounts, and Stripe returns mode with each. */
23
27
  mode?: never;
28
+ /** Refused: a Connect account needs an API-key connector. */
24
29
  connectedAccount?: never;
25
30
  }
26
31
  /**
27
- * Static credentials have one fixed mode, including Stripe Connect calls.
28
- *
29
- * Both static shapes belong here: a key the deployment supplies as a literal
30
- * header, and one the operator pastes into the connection in the operator UI.
31
- * Neither can discover its own mode, since a restricted key answers for
32
- * exactly one, so both declare it.
32
+ * Connecta's REST connector to `api.stripe.com` with one operator-managed
33
+ * secret or restricted key, pasted into the connection in the operator UI.
34
+ * A key reaches exactly one mode, so the connector declares it, and a key
35
+ * whose prefix says otherwise is refused before any request leaves.
33
36
  */
34
- export interface StripeHeaderOptions extends StripeCommonOptions {
35
- auth: Exclude<RemoteMcpAuth, {
36
- type: "oauth" | "request";
37
- }>;
37
+ export interface StripeApiKeyOptions extends StripeCommonOptions {
38
+ auth: {
39
+ type: "apiKey";
40
+ };
38
41
  mode: StripeMode;
39
- /** Act as one Connect account by sending Stripe's `Stripe-Account` header. */
42
+ /** Act as one Connect account: `Stripe-Account` on v1, `Stripe-Context` on v2. */
40
43
  connectedAccount?: string;
41
44
  }
42
- export type StripeOptions = StripeOAuthOptions | StripeHeaderOptions;
43
- /** A maintained Stripe hosted-MCP connection. */
45
+ export type StripeOptions = StripeOAuthOptions | StripeApiKeyOptions;
46
+ /**
47
+ * A maintained Stripe connection. `auth` selects the implementation: OAuth
48
+ * reaches Stripe's hosted MCP server; an API key reaches Connecta's REST
49
+ * connector to `api.stripe.com`.
50
+ */
44
51
  export declare const stripe: import("../../provider.js").ProviderFactory<StripeOptions>;
45
52
  /** @deprecated Read `stripe.definition.classify` instead. Kept for existing imports. */
46
53
  export declare const STRIPE_VETTED_CATALOG: import("../../catalog-drift.js").VettedCatalog;
47
- export {};
@@ -1,18 +1,21 @@
1
1
  import { skill } from "./skill.generated.js";
2
- import { remoteMcp, withCredentialDefaults } from "../../connectors/remote-mcp.js";
2
+ import { apiConnector } from "../../connectors/api-connector.js";
3
3
  import { reviewedCatalog } from "../../catalog-drift.js";
4
- import { keys, optionsOf, strings, variants } from "../../config-schema.js";
5
- import { CREDENTIAL } from "../../connectors/option-shapes.js";
4
+ import { keys, optionsOf, variants } from "../../config-schema.js";
6
5
  import { defineProvider } from "../../provider.js";
6
+ import { byAuth, hostedOAuth } from "../_shared/rest/dispatch.js";
7
+ import { restTools } from "../_shared/rest/tools.js";
8
+ import { stripeRest } from "./rest.js";
9
+ export { STRIPE_API_BASE_URL } from "./rest.js";
7
10
  /** Stripe publishes one hosted MCP endpoint for every account and mode. */
8
11
  export const STRIPE_MCP_ENDPOINT = "https://mcp.stripe.com/";
9
12
  /**
10
13
  * Stripe documents no MCP-specific limit, so these transcribe the account limit
11
- * MCP traffic spends: 100 requests per second live, 25 in a sandbox. OAuth takes
12
- * the sandbox rule (`mode ?? "sandbox"` below) because one session can reach
13
- * either mode and the policy is fixed before the account-scoped call begins.
14
- * The `maxConcurrency` figures are Connecta's own choice — Stripe says
15
- * per-account and per-endpoint concurrency limits exist but publishes no number.
14
+ * every request spends: 100 requests per second live, 25 in a sandbox. OAuth
15
+ * takes the sandbox rule because one session can reach either mode and the
16
+ * policy is fixed before the account-scoped call begins. The `maxConcurrency`
17
+ * figures are Connecta's own choice — Stripe says per-account and per-endpoint
18
+ * concurrency limits exist but publishes no number.
16
19
  */
17
20
  const STRIPE_ADMISSION = {
18
21
  production: {
@@ -40,7 +43,9 @@ const STRIPE_ADMISSION = {
40
43
  * Reviewed in #705's provider audit against https://docs.stripe.com/mcp.
41
44
  * Retains the release-reviewed inventory, including names absent from today's
42
45
  * public reference. Live annotations were not reverified without credentials.
43
- * No schema digest is asserted without a captured schema to review.
46
+ * No schema digest is asserted without a captured schema to review. This
47
+ * classifies the hosted OAuth catalog only; the REST connector annotates each
48
+ * tool it authors.
44
49
  */
45
50
  const STRIPE_CLASSIFICATION = {
46
51
  tools: {
@@ -109,158 +114,126 @@ const STRIPE_CLASSIFICATION = {
109
114
  },
110
115
  },
111
116
  };
112
- /** Stripe key prefixes carry their own mode; only a clear reading counts. */
113
- const LIVE_KEY = /\b(?:sk|rk|pk)_live_/;
114
- const TEST_KEY = /\b(?:sk|rk|pk)_test_/;
115
- /**
116
- * Refuse a recognizable key/mode mismatch. This is the only mode guard Connecta
117
- * can offer, and it needs the key as a literal header to read the prefix: an
118
- * operator-managed key is not in the deployment file, so its declared mode
119
- * stands alone and a key pointed at the other environment fails at Stripe.
120
- */
121
- function assertModeMatchesKey(id, mode, auth) {
122
- if (auth.type !== "headers")
123
- return;
124
- for (const value of Object.values(auth.headers)) {
125
- const keyMode = LIVE_KEY.test(value) ? "production" : TEST_KEY.test(value) ? "sandbox" : undefined;
126
- if (keyMode !== undefined && keyMode !== mode) {
127
- throw new Error(`stripe("${id}") declares mode "${mode}" but its auth headers carry a ` +
128
- `${keyMode === "production" ? "live" : "test"}-mode Stripe key.`);
129
- }
130
- }
131
- }
132
- function resolveAuth(id, options) {
133
- const auth = withCredentialDefaults(options.auth ?? { type: "oauth" }, {
134
- credential: {
135
- label: "Secret or restricted API key",
136
- description: "A Stripe secret or restricted API key for this connector's declared mode. Stripe sends it as a bearer token; it is stored encrypted and never displayed.",
137
- placeholder: "sk_… or rk_…",
138
- },
139
- });
140
- const connectedAccount = options.connectedAccount?.trim();
141
- if (connectedAccount === undefined || connectedAccount === "")
142
- return auth;
143
- if (!connectedAccount.startsWith("acct_")) {
144
- throw new Error(`stripe("${id}") connectedAccount must be a Stripe account id ("acct_...").`);
145
- }
146
- if (auth.type === "oauth") {
147
- throw new Error(`stripe("${id}") cannot reach a connected account over OAuth; Stripe ` +
148
- `requires a restricted API key for Stripe-Account calls.`);
149
- }
150
- if (auth.type === "credential" || auth.type === "request") {
151
- // `Stripe-Account` is a second header beside the credential's own, and the
152
- // credential shape assembles exactly one. A Connect connector therefore
153
- // still takes its restricted key as a literal header.
154
- throw new Error(`stripe("${id}") cannot reach a connected account with an ` +
155
- `operator-managed credential; Stripe-Account is a second static ` +
156
- `header, so declare auth: { type: "headers" } for this connector.`);
157
- }
158
- return {
159
- type: "headers",
160
- headers: { ...auth.headers, "Stripe-Account": connectedAccount },
161
- };
117
+ function instructionsSection(instructions) {
118
+ const text = instructions?.trim();
119
+ return text ? `\n## ${skill.instructionsHeading}\n\n${text}\n` : "";
162
120
  }
163
121
  function oauthUsageGuide(purpose, instructions) {
164
- const accountInstructions = instructions?.trim();
122
+ const { oauth, shared, oauth_limits } = skill.fragments;
165
123
  return `# Stripe usage
166
124
 
167
- Scope: live and sandbox accounts. Connector purpose: ${purpose}${skill.fragments.guide_0}${sharedUsageGuide("100 requests per second in live mode and 25 in sandbox mode")}
168
- ${accountInstructions ? `\n## ${skill.instructionsHeading}\n\n${accountInstructions}\n` : ""}`;
125
+ Scope: live and sandbox accounts. Connector purpose: ${purpose}${oauth}${shared}${oauth_limits}
126
+ ${instructionsSection(instructions)}`;
169
127
  }
170
128
  const MODE_COPY = {
171
129
  production: {
172
130
  title: "Stripe (production)",
173
131
  blurb: "production — live money and real customers",
174
132
  warning: "This is a PRODUCTION Stripe connection. Every write moves real money against real customers, and a refund cannot be undone. If a request could plausibly be a rehearsal, route it to a sandbox connector instead.",
133
+ rate: "100 requests per second",
175
134
  },
176
135
  sandbox: {
177
136
  title: "Stripe (sandbox)",
178
137
  blurb: "sandbox — test data, no real money",
179
138
  warning: "This is a SANDBOX Stripe connection. Nothing here is real money and none of these objects exist in production, so never answer a question about live revenue, payouts, or a named customer from this connector.",
139
+ rate: "25 requests per second",
180
140
  },
181
141
  };
182
- function fixedModeUsageGuide(mode, purpose, instructions) {
142
+ function keyUsageGuide(mode, purpose, connectedAccount, instructions) {
183
143
  const copy = MODE_COPY[mode];
184
- const accountInstructions = instructions?.trim();
144
+ const { key, shared, key_limits, key_tail } = skill.fragments;
145
+ const connect = connectedAccount
146
+ ? `\n\nConnected account: \`${connectedAccount}\`. Every call acts as this Connect account; the platform account's own objects are out of reach here.`
147
+ : "";
185
148
  return `# Stripe usage
186
149
 
187
150
  Mode: ${mode}. Connector purpose: ${purpose}
188
151
 
189
- ${copy.warning}${skill.fragments.guide_1}${sharedUsageGuide(`${mode === "production" ? "100" : "25"} requests per second`)}
190
- ${accountInstructions ? `\n## ${skill.instructionsHeading}\n\n${accountInstructions}\n` : ""}`;
191
- }
192
- function sharedUsageGuide(rate) {
193
- return `${skill.fragments.guide_2}${rate}${skill.fragments.guide_3}`;
152
+ ${copy.warning}${connect}${key}${shared}${key_limits}${copy.rate}${key_tail}
153
+ ${instructionsSection(instructions)}`;
194
154
  }
195
- /** The closed options stripe() accepts; see `assertKnownOptions`. */
196
- const STRIPE_OPTIONS = optionsOf()({
197
- ...keys("title", "authScope", "purpose", "instructions", "maxResultBytes", "mode", "connectedAccount"),
198
- auth: variants("type", {
199
- oauth: keys("type"),
200
- headers: optionsOf()({ ...keys("type"), headers: strings() }).shape,
201
- credential: optionsOf()({
202
- ...keys("type", "header", "scheme"),
203
- credential: CREDENTIAL,
204
- }).shape,
205
- }),
155
+ const COMMON = keys("title", "authScope", "purpose", "instructions", "maxResultBytes");
156
+ /** The closed options stripe() accepts, selected by `auth.type`; see `assertKnownOptions`. */
157
+ const STRIPE_OPTIONS = variants(["auth", "type"], {
158
+ // The never-typed keys stay out of the shape, so the walk refuses them by name.
159
+ oauth: optionsOf()({
160
+ ...COMMON,
161
+ auth: optionsOf()(keys("type")),
162
+ }).shape,
163
+ apiKey: optionsOf()({
164
+ ...COMMON,
165
+ ...keys("mode", "connectedAccount"),
166
+ auth: optionsOf()(keys("type")),
167
+ }).shape,
206
168
  });
207
- /** A maintained Stripe hosted-MCP connection. */
169
+ function stripeOAuth(id, options, provider) {
170
+ return hostedOAuth(id, provider, {
171
+ url: STRIPE_MCP_ENDPOINT,
172
+ title: options.title ?? "Stripe",
173
+ description: `Stripe payments (live and sandbox accounts) — ${options.purpose}`,
174
+ callAdmission: STRIPE_ADMISSION.sandbox,
175
+ usageGuide: {
176
+ content: oauthUsageGuide(options.purpose, options.instructions),
177
+ // Explicit rather than derived: OAuth must lead with its account-scoped selector pair.
178
+ summary: "Live and sandbox Stripe accounts. List accounts; carry the returned stripe_context and livemode before acting.",
179
+ },
180
+ });
181
+ }
182
+ function stripeApiKey(id, options, provider) {
183
+ const mode = options.mode;
184
+ if (mode !== "production" && mode !== "sandbox") {
185
+ throw new Error(`stripe("${id}") requires mode "production" or "sandbox" with apiKey auth.`);
186
+ }
187
+ let connectedAccount;
188
+ if (options.connectedAccount !== undefined) {
189
+ connectedAccount = typeof options.connectedAccount === "string" ? options.connectedAccount.trim() : "";
190
+ if (!/^acct_[A-Za-z0-9]+$/.test(connectedAccount)) {
191
+ throw new Error(`stripe("${id}") connectedAccount must be a Stripe account id ("acct_...").`);
192
+ }
193
+ }
194
+ const copy = MODE_COPY[mode];
195
+ const live = mode === "production" ? "live" : "test";
196
+ const rest = stripeRest(mode, connectedAccount);
197
+ return apiConnector(id, {
198
+ ...provider.connectorOptions,
199
+ title: options.title ?? copy.title,
200
+ description: `Stripe payments (${copy.blurb}) — ${options.purpose}`,
201
+ credential: {
202
+ label: `Stripe ${live}-mode secret or restricted key`,
203
+ description: `A ${live}-mode secret (sk_${live}_…) or restricted (rk_${live}_…) key; prefer a restricted key with only the permissions this connector needs. It is sent only to api.stripe.com and files.stripe.com, stored encrypted, and never displayed.`,
204
+ placeholder: `rk_${live}_…`,
205
+ },
206
+ testCredential: rest.testCredential,
207
+ callAdmission: STRIPE_ADMISSION[mode],
208
+ usageGuide: {
209
+ content: keyUsageGuide(mode, options.purpose, connectedAccount, options.instructions),
210
+ // Explicit rather than derived: a key connector must lead with its one mode.
211
+ summary: mode === "production"
212
+ ? "PRODUCTION: real money. One live-mode key over REST; search operations, read details, then read or write."
213
+ : "Sandbox: test data only. One test-mode key over REST; search operations, read details, then read or write.",
214
+ },
215
+ tools: [...restTools(rest.vendor), ...rest.tools],
216
+ });
217
+ }
218
+ /**
219
+ * A maintained Stripe connection. `auth` selects the implementation: OAuth
220
+ * reaches Stripe's hosted MCP server; an API key reaches Connecta's REST
221
+ * connector to `api.stripe.com`.
222
+ */
208
223
  export const stripe = defineProvider({
209
224
  name: "stripe",
210
225
  title: "Stripe",
211
- kind: "mcp",
226
+ kind: "dual",
212
227
  readme: "Stripe",
213
- bundle: { "baselineGzip": 129103, "maxGzip": 189103 },
228
+ bundle: {
229
+ "baselineGzip": 232089,
230
+ "maxGzip": 292089,
231
+ "note": "./providers/stripe remeasures at 232,089 B gzip (#801), from 170,771 B on main: auth now selects the implementation, so the entry carries the hosted MCP client and Connecta's REST connector (api(), the schema validator, guarded transport, the shared REST module) plus the pinned Stripe operation index (638 operations; request details to depth 2, without descriptions or enums over 50 values; 37,918 B gzip as source). Details were shrunk before the cap moved: the first full index measured 81 KB gzip. The cap uses the existing baseline + 60,000 B policy.",
232
+ },
214
233
  skill,
215
234
  options: STRIPE_OPTIONS,
216
235
  classify: STRIPE_CLASSIFICATION,
217
- create: stripeConnector,
236
+ create: byAuth({ oauth: stripeOAuth, apiKey: stripeApiKey }),
218
237
  });
219
- function stripeConnector(id, options, provider) {
220
- const purpose = options.purpose.trim();
221
- const auth = resolveAuth(id, options);
222
- const mode = "mode" in options ? options.mode : undefined;
223
- if (auth.type === "oauth" && mode !== undefined) {
224
- throw new Error(`stripe("${id}") cannot declare a connector-wide mode for OAuth; Stripe returns mode with each account.`);
225
- }
226
- if (auth.type !== "oauth" && mode !== "production" && mode !== "sandbox") {
227
- throw new Error(`stripe("${id}") with headers or credential auth requires mode ` + `"production" or "sandbox".`);
228
- }
229
- // Only a literal header can be inspected. An operator-managed credential is
230
- // not readable at construction — there is nothing in the deployment file to
231
- // read — so the declared mode stands alone, and a key pointed at the other
232
- // one is Stripe's own refusal to report.
233
- if (auth.type === "headers") {
234
- assertModeMatchesKey(id, mode, auth);
235
- }
236
- const copy = mode === undefined ? undefined : MODE_COPY[mode];
237
- const connector = remoteMcp(id, {
238
- url: STRIPE_MCP_ENDPOINT,
239
- ...provider.connectorOptions,
240
- title: options.title ?? copy?.title ?? "Stripe",
241
- description: mode === undefined
242
- ? `Stripe payments (live and sandbox accounts) — ${purpose}`
243
- : `Stripe payments (${copy?.blurb}) — ${purpose}`,
244
- auth,
245
- requireHttps: true,
246
- classify: provider.classify,
247
- callAdmission: STRIPE_ADMISSION[mode ?? "sandbox"],
248
- usageGuide: {
249
- content: mode === undefined
250
- ? oauthUsageGuide(purpose, options.instructions)
251
- : fixedModeUsageGuide(mode, purpose, options.instructions),
252
- // Explicit rather than derived: fixed credentials must lead with mode,
253
- // while OAuth must lead with its account-scoped selector pair.
254
- summary: mode === undefined
255
- ? "Live and sandbox Stripe accounts. List accounts; carry the returned stripe_context and livemode before acting."
256
- : mode === "production"
257
- ? "PRODUCTION: real money. This static credential has one fixed live-mode scope."
258
- : "Sandbox: test data only. This static credential has one fixed sandbox scope.",
259
- // Not `required`. The four generic tools are the routing decision; a
260
- // guide forced into every call would pay for the same prose repeatedly.
261
- },
262
- });
263
- return connector;
264
- }
265
238
  /** @deprecated Read `stripe.definition.classify` instead. Kept for existing imports. */
266
239
  export const STRIPE_VETTED_CATALOG = reviewedCatalog(stripe.definition.classify, 'defineProvider("stripe")');