@zackbart/connecta 0.33.0 → 0.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +100 -128
  3. package/SECURITY.md +1 -1
  4. package/bin/version.mjs +1 -1
  5. package/dist/activity.d.ts +3 -0
  6. package/dist/branding.d.ts +7 -0
  7. package/dist/branding.js +76 -9
  8. package/dist/catalog-drift.d.ts +30 -26
  9. package/dist/catalog-drift.js +169 -84
  10. package/dist/catalog-service.js +16 -2
  11. package/dist/config-value-sources.js +6 -1
  12. package/dist/config.d.ts +13 -2
  13. package/dist/config.js +36 -2
  14. package/dist/connector-caller.js +2 -1
  15. package/dist/connectors/api-connector.d.ts +3 -0
  16. package/dist/connectors/api-connector.js +5 -1
  17. package/dist/connectors/option-shapes.js +1 -1
  18. package/dist/connectors/remote-mcp.d.ts +2 -0
  19. package/dist/connectors/remote-mcp.js +5 -1
  20. package/dist/describe-config.d.ts +5 -1
  21. package/dist/describe-config.js +2 -0
  22. package/dist/execute.js +1 -11
  23. package/dist/fix-prompt.js +2 -2
  24. package/dist/index.js +1 -0
  25. package/dist/invocation.js +3 -2
  26. package/dist/membership.d.ts +2 -0
  27. package/dist/meta-tools.js +3 -2
  28. package/dist/oauth-callback-outcome.js +2 -2
  29. package/dist/operator-ui/agents.d.ts +1 -1
  30. package/dist/operator-ui/agents.js +16 -16
  31. package/dist/operator-ui/config-summary.js +565 -0
  32. package/dist/operator-ui/connection-map.js +376 -0
  33. package/dist/operator-ui/endpoints.js +45 -16
  34. package/dist/operator-ui/fix-prompts.js +1 -1
  35. package/dist/operator-ui/generated.js +4 -4
  36. package/dist/operator-ui/health.js +52 -0
  37. package/dist/operator-ui/member-page.js +13 -28
  38. package/dist/operator-ui/overview.js +123 -0
  39. package/dist/operator-ui/view.d.ts +26 -22
  40. package/dist/operator-ui/view.js +130 -96
  41. package/dist/page-styles.js +5 -3
  42. package/dist/provider.d.ts +6 -1
  43. package/dist/provider.js +18 -1
  44. package/dist/providers/_shared/google/workspace.d.ts +9 -4
  45. package/dist/providers/_shared/google/workspace.js +7 -3
  46. package/dist/providers/basecamp/index.d.ts +2 -0
  47. package/dist/providers/breeze/index.d.ts +2 -0
  48. package/dist/providers/ccb/index.d.ts +2 -0
  49. package/dist/providers/ccb/index.js +2 -2
  50. package/dist/providers/cloudflare/index.d.ts +2 -0
  51. package/dist/providers/cloudflare/index.js +5 -1
  52. package/dist/providers/cloudflare/openapi.generated.js +3296 -3206
  53. package/dist/providers/cloudflare/rest.js +19 -0
  54. package/dist/providers/cloudflare/skill.generated.js +2 -2
  55. package/dist/providers/cloudflare/value-safety.js +51 -18
  56. package/dist/providers/docs/index.js +1 -1
  57. package/dist/providers/docs/skill.generated.js +2 -2
  58. package/dist/providers/drive/index.js +1 -1
  59. package/dist/providers/drive/skill.generated.js +2 -2
  60. package/dist/providers/forms/index.js +1 -1
  61. package/dist/providers/forms/skill.generated.js +2 -2
  62. package/dist/providers/gmail/index.js +1 -1
  63. package/dist/providers/gmail/skill.generated.js +2 -2
  64. package/dist/providers/intercom/index.d.ts +24 -0
  65. package/dist/providers/intercom/index.js +121 -0
  66. package/dist/providers/intercom/skill.generated.js +8 -0
  67. package/dist/providers/linear/index.js +204 -19
  68. package/dist/providers/linear/skill.generated.js +2 -2
  69. package/dist/providers/mixpanel/index.d.ts +2 -0
  70. package/dist/providers/mixpanel/index.js +21 -17
  71. package/dist/providers/mixpanel/skill.generated.js +3 -3
  72. package/dist/providers/notion/index.d.ts +2 -0
  73. package/dist/providers/notion/index.js +1 -1
  74. package/dist/providers/notion/skill.generated.js +1 -1
  75. package/dist/providers/overflow/index.d.ts +2 -0
  76. package/dist/providers/overflow/index.js +1 -1
  77. package/dist/providers/overflow/skill.generated.js +2 -2
  78. package/dist/providers/planning-center/index.d.ts +2 -0
  79. package/dist/providers/planning-center/index.js +1 -1
  80. package/dist/providers/planning-center/skill.generated.js +4 -4
  81. package/dist/providers/revenuecat/index.d.ts +5 -3
  82. package/dist/providers/revenuecat/index.js +147 -7
  83. package/dist/providers/revenuecat/skill.generated.js +3 -3
  84. package/dist/providers/sheets/index.js +1 -1
  85. package/dist/providers/sheets/skill.generated.js +2 -2
  86. package/dist/providers/slides/index.js +1 -1
  87. package/dist/providers/slides/skill.generated.js +2 -2
  88. package/dist/providers/stripe/index.d.ts +2 -0
  89. package/dist/providers/stripe/index.js +50 -30
  90. package/dist/providers/stripe/skill.generated.js +2 -1
  91. package/dist/providers/tithely/index.d.ts +2 -0
  92. package/dist/providers/tithely/index.js +1 -1
  93. package/dist/providers/vercel/index.d.ts +2 -0
  94. package/dist/providers/vercel/index.js +2 -2
  95. package/dist/providers/vercel/openapi.generated.js +391 -374
  96. package/dist/providers/vercel/skill.generated.js +2 -2
  97. package/dist/providers/vercel/value-safety.js +14 -4
  98. package/dist/registry.d.ts +10 -14
  99. package/dist/registry.js +77 -62
  100. package/dist/routes/mcp.js +20 -13
  101. package/dist/routes/oauth.js +2 -2
  102. package/dist/routes/ui-data.js +8 -2
  103. package/dist/skills.js +18 -2
  104. package/dist/storage/sql.js +5 -1
  105. package/dist/types.d.ts +36 -11
  106. package/dist/ui.js +9 -4
  107. package/dist/usage-guide.js +1 -1
  108. package/dist/version.d.ts +1 -1
  109. package/dist/version.js +1 -1
  110. package/documentation/README.md +2 -2
  111. package/documentation/architecture.md +66 -18
  112. package/documentation/auth.md +183 -67
  113. package/documentation/code-mode.md +30 -10
  114. package/documentation/deploying.md +5 -1
  115. package/documentation/integrating.md +64 -7
  116. package/documentation/meta-tools.md +38 -24
  117. package/documentation/migration-0.33.md +21 -13
  118. package/documentation/migration-0.34.md +67 -0
  119. package/documentation/operating.md +1 -1
  120. package/documentation/operator-tests.md +52 -20
  121. package/documentation/operator-ui.md +341 -104
  122. package/documentation/provider-auth-migration.md +22 -0
  123. package/examples/worker/AGENTS.md +15 -9
  124. package/examples/worker/README.md +41 -14
  125. package/examples/worker/src/connecta.config.ts +5 -4
  126. package/examples/worker/wrangler.jsonc +1 -1
  127. package/package.json +23 -19
  128. package/templates/node/AGENTS.md +13 -7
  129. package/templates/node/README.md +14 -11
  130. package/templates/node/package.json +1 -1
  131. package/templates/node/src/connecta.config.ts +2 -2
@@ -1,11 +1,11 @@
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- The documented `get-product-store-state-operation`, `create-product-prices`, `equalize-subscription-prices`, `set-product-store-state`, and `upload-product-store-state-screenshot` tools are deprecated; they remain supported when the server exposes them. Store changes use the plan workflow: `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. The reviewed catalog is a superset, not a promise that every reviewed tool is available. RevenueCat gates parts of its MCP catalog by API key permissions, 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- `list-app-subscription-groups`, `preview-audience`, and `render-paywall-screenshot` are reviewed reads. Audience preview uses an observational POST that returns matching customers without creating an audience; screenshot rendering previews a paywall without editing or publishing it.\n- `validate-app-credentials` stays classified as write conservatively: the MCP reference labels it Read and says credentials are unchanged, but the live annotation says otherwise and the public API v2 app contract does not establish whether validation status or results persist. Do not infer permission to call it from its presence under a read-only API key.\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- Use discovery's effective classification for routing rather than inferring safety from a name or HTTP method. Creates, updates, archives, grants, publishes, and store changes are writes; audience previews and screenshot rendering are reads. 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 has per-resource permissions. RevenueCat can omit tools this key cannot use from its live catalog; Connecta serves the returned catalog rather than adding missing tools from its reviewed superset. Catalog presence alone does not prove a call is permitted. A served tool can still fail at RevenueCat for missing permissions; read that refusal and route the call to a connector with the required permissions. A read-only Connecta endpoint independently omits and blocks tools classified as writes.\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- The documented `get-product-store-state-operation`, `create-product-prices`, `equalize-subscription-prices`, `set-product-store-state`, and `upload-product-store-state-screenshot` tools are deprecated; they remain supported when the server exposes them. Store changes use the plan workflow: `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. The reviewed catalog is a superset, not a promise that every reviewed tool is available. RevenueCat gates parts of its MCP catalog by API key permissions, 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- `list-app-subscription-groups`, `preview-audience`, and `render-paywall-screenshot` are reviewed reads. Audience preview uses an observational POST that returns matching customers without creating an audience; screenshot rendering previews a paywall without editing or publishing it.\n- `validate-app-credentials` stays classified as write conservatively: the MCP reference labels it Read and says credentials are unchanged, but the live annotation says otherwise and the public API v2 app contract does not establish whether validation status or results persist. Do not infer permission to call it from its presence under a read-only API key.\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- Use discovery's effective classification for routing rather than inferring safety from a name or HTTP method. Creates, updates, archives, grants, publishes, and store changes are writes; audience previews and screenshot rendering are reads. 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
- "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"
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 has per-resource permissions. RevenueCat can omit tools this key cannot use from its live catalog; Connecta serves the returned catalog rather than adding missing tools from its reviewed superset. Catalog presence alone does not prove a call is permitted. A served tool can still fail at RevenueCat for missing permissions; read that refusal and route the call to a connector with the required permissions. A read-only Connecta endpoint independently omits and blocks tools classified as writes.\n"
10
10
  }
11
11
  };
@@ -28,7 +28,7 @@ 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";
@@ -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; 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, or from an agent whose pool has\nno owner, fails `auth_required`; only an operator can change either. 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; 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, or from an agent whose pool has\nno owner, fails `auth_required`; only an operator can change either. 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,7 +40,7 @@ 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";
@@ -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; 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, or from an agent whose pool has no owner, fails\n`auth_required`; only an operator can change either. 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; 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, or from an agent whose pool has no owner, fails\n`auth_required`; only an operator can change either. 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
  };
@@ -14,6 +14,8 @@ interface StripeCommonOptions {
14
14
  instructions?: string;
15
15
  /** Connector-specific inline result limit; omit to inherit the deployment. */
16
16
  maxResultBytes?: number;
17
+ /** Serve only read-classified tools on every endpoint. Defaults to false. */
18
+ readOnly?: boolean;
17
19
  }
18
20
  /**
19
21
  * Stripe's hosted MCP server over OAuth. One session can reach live and
@@ -40,77 +40,97 @@ const STRIPE_ADMISSION = {
40
40
  },
41
41
  };
42
42
  /**
43
- * Reviewed in #705's provider audit against https://docs.stripe.com/mcp.
44
- * Retains the release-reviewed inventory, including names absent from today's
45
- * public reference. Live annotations were not reverified without credentials.
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.
43
+ * Re-reviewed for #343 against the hosted OAuth catalog and official MCP and
44
+ * analytics docs on 2026-10-19 (#703). Digests cover the complete describe
45
+ * schemas captured in fixtures.ts, not inferred result shapes. The documented
46
+ * get_stripe_account_info remains reviewed without an uncaptured digest.
47
+ * This review applies only to hosted OAuth; REST annotates its own tools.
49
48
  */
50
49
  const STRIPE_CLASSIFICATION = {
51
- tools: {
50
+ "tools": {
52
51
  "stripe_api_search": {
53
52
  "verdict": "read",
54
53
  "reason": "Searches API method contracts and object records; it does not invoke mutating methods.",
54
+ "schemaDigest": "sha256:1fdb44c12347b21b3896250e2a3c88763b54f4cdfe4fff27f7067d004e3097e4",
55
55
  },
56
56
  "stripe_api_details": {
57
57
  "verdict": "read",
58
58
  "reason": "Reads the parameter contract for an API method; it does not execute that method.",
59
+ "schemaDigest": "sha256:a80bf5b685eba9b0cb91df7fa36afac04bcb97a7dcadb3c07bd22f0a4fcc7771",
59
60
  },
60
61
  "stripe_api_read": {
61
62
  "verdict": "read",
62
63
  "reason": "The vendor restricts this generic tool to HTTP GET; all mutating methods use stripe_api_write.",
64
+ "schemaDigest": "sha256:90b15c317384e244a19472d2bffcc0da73a1174f16fd89a7562134a3da5d0d53",
63
65
  },
64
- "get_stripe_account_info": { "verdict": "read", "reason": "Reads metadata for the selected Stripe account." },
65
- "get_balance_summary": {
66
- "verdict": "read",
67
- "reason": "Retrieves Stripe balance summary information without changing vendor state.",
68
- },
69
- "list_metrics": {
66
+ "get_stripe_account_info": {
70
67
  "verdict": "read",
71
- "reason": "Retrieves Stripe metrics information without changing vendor state.",
68
+ "reason": "Reads metadata for the selected Stripe account; still documented although absent from the observed hosted catalog.",
72
69
  },
73
- "explain_metric": {
70
+ "get_balance_summary": {
74
71
  "verdict": "read",
75
- "reason": "Explains an existing metric definition without creating an analytics run.",
72
+ "reason": "Retrieves Stripe balance summary information without changing vendor state.",
73
+ "schemaDigest": "sha256:857973344e59ca7199bc65e62d5e7198332542339f387b7363626ad752437a11",
76
74
  },
77
- "metric_drilldown": { "verdict": "read", "reason": "Reads the breakdown of an existing metric." },
78
- "show_metric_app": { "verdict": "read", "reason": "Displays an existing metric app without changing its data." },
79
75
  "list_available_accounts_or_orgs": {
80
76
  "verdict": "read",
81
- "reason": "Retrieves Stripe available accounts or orgs information without changing vendor state.",
77
+ "reason": "Lists accessible accounts with stripe_context and livemode without changing vendor state; verified in the hosted catalog despite omission from public docs.",
78
+ "schemaDigest": "sha256:5ed608b204ddf3e117f419a0443db5bdc28783133476e953338f525489dd5626",
82
79
  },
83
80
  "manage_stripe_accounts": {
84
81
  "verdict": "read",
85
- "reason": "Selects account context for the session; the reviewed contract does not mutate payment or account records.",
82
+ "reason": "Returns a Dashboard reconsent URL; the tool itself does not add accounts, remove accounts, or change permissions.",
83
+ "schemaDigest": "sha256:abb66368ed8d461b1106eb2257235a5d836f76906674c44be9ac816b6d509cd4",
86
84
  },
87
85
  "search_stripe_documentation": {
88
86
  "verdict": "read",
89
87
  "reason": "Searches Stripe reference documentation without accessing payment mutations.",
88
+ "schemaDigest": "sha256:18b5d9f565d28bc4786ba1f657a2c291c43f4c9137e4c5ca5fbbf5dd114eae67",
90
89
  },
91
90
  "stripe_api_write": {
92
91
  "verdict": "destructive",
93
92
  "reason": "Can dispatch POST, PATCH, PUT, or DELETE across the Stripe API, including edits and cancellations.",
93
+ "schemaDigest": "sha256:61d1bdc4e6862989c46250bdbea3adfc6169c24dbf6e8b20fc4251108da5c4e7",
94
+ },
95
+ "convert_currency": {
96
+ "verdict": "destructive",
97
+ "reason": "Converts existing funds between currencies in a Financial Account, changing balances.",
98
+ "schemaDigest": "sha256:5a861f71e41b6c680452a5817d7c67c4a708796a4e0dcf7adbd8c4f89c6c99f8",
94
99
  },
95
- "create_refund": {
100
+ "send_money": {
96
101
  "verdict": "destructive",
97
- "reason": "Moves money and changes an existing payment; a refund cannot be undone.",
102
+ "reason": "Sends funds to a third-party recipient through an outbound payment or payout link.",
103
+ "schemaDigest": "sha256:5415856ee7e5073f3e6ea3280c5f17289449ded3dbb92d4a4ab9b52af1ed3049",
104
+ },
105
+ "setup_recipient": {
106
+ "verdict": "destructive",
107
+ "reason": "Creates a recipient account for outbound payments; retain the upstream destructive hint rather than treating setup as a read.",
108
+ "schemaDigest": "sha256:75e0bb1003a3a74badab81d4fb76f4a8481b186f7755b0a7ee73f1623b163457",
109
+ },
110
+ "transfer_money": {
111
+ "verdict": "destructive",
112
+ "reason": "Moves existing funds between Stripe balances or Financial Accounts.",
113
+ "schemaDigest": "sha256:e75be95113883df72577127387cf2a11fae51d2cc01df80a085f4b276bd362d8",
114
+ },
115
+ "atlas_company_formation": {
116
+ "verdict": "destructive",
117
+ "reason": "Creates or advances company formation state; list and retrieve actions share this mutating tool boundary.",
118
+ "schemaDigest": "sha256:648e1faca5f5ac3dc7018890a336081135842d9418ee6df94dcbd81b7d861694",
98
119
  },
99
120
  "stripe_implementation_planner": {
100
121
  "verdict": "write",
101
122
  "reason": "Creates or continues provider-side planning state.",
123
+ "schemaDigest": "sha256:284dce4e701ab4cb9b9c9fbf61104cae2eab0b9eef1b986f128fc1bbc42ef185",
102
124
  },
103
125
  "stripe_analytics": {
104
126
  "verdict": "write",
105
- "reason": "Mixes retrieval with durable query-run creation, so the whole tool is a write.",
106
- },
107
- "stripe_report": {
108
- "verdict": "write",
109
- "reason": "May create durable report-run state, so retrieval paths cannot make the whole tool read-only.",
127
+ "reason": "The hosted schema includes execute_query_run and execute_query_template alongside retrieval; asynchronous query-run creation makes the whole tool a write even though it analyzes existing data.",
128
+ "schemaDigest": "sha256:c6b22ff19b5fe2d359b20f99b67591ba74cf504870123711579556b6937916b9",
110
129
  },
111
- "send_stripe_mcp_feedback": {
130
+ "send_stripe_feedback": {
112
131
  "verdict": "write",
113
- "reason": "Submits feedback to Stripe, creating provider-side state.",
132
+ "reason": "Submits feedback to Stripe, creating provider-side state; replaces send_stripe_mcp_feedback.",
133
+ "schemaDigest": "sha256:4ac352f04a8d974bdb56503f4826e7fbc19458e0f0d0c369f17e82d5fb6a7a86",
114
134
  },
115
135
  },
116
136
  };
@@ -152,7 +172,7 @@ Mode: ${mode}. Connector purpose: ${purpose}
152
172
  ${copy.warning}${connect}${key}${shared}${key_limits}${copy.rate}${key_tail}
153
173
  ${instructionsSection(instructions)}`;
154
174
  }
155
- const COMMON = keys("title", "authScope", "purpose", "instructions", "maxResultBytes");
175
+ const COMMON = keys("title", "authScope", "purpose", "instructions", "maxResultBytes", "readOnly");
156
176
  /** The closed options stripe() accepts, selected by `auth.type`; see `assertKnownOptions`. */
157
177
  const STRIPE_OPTIONS = variants(["auth", "type"], {
158
178
  // The never-typed keys stay out of the shape, so the walk refuses them by name.
@@ -3,7 +3,8 @@ export const skill = {
3
3
  "content": "- 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",
4
4
  "instructionsHeading": "Account instructions",
5
5
  "fragments": {
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",
6
+ "setup": "## Choose OAuth or an API key\n\nImport `stripe` from `@zackbart/connecta/providers/stripe`. Choose one of two setup paths:\n\n```ts\n// OAuth: Stripe's hosted MCP. Authorize the connection with Stripe.\nstripe(\"billing\", { purpose: \"Payments\", auth: { type: \"oauth\" } });\nstripe(\"billing\", { purpose: \"Revenue reports\", auth: { type: \"oauth\" }, readOnly: true });\n\n// API key: Connecta's REST connector. Paste the key into this connection in the operator UI.\nstripe(\"billing\", { purpose: \"Payments\", auth: { type: \"apiKey\" }, mode: \"production\" });\nstripe(\"billing\", { purpose: \"Revenue reports\", auth: { type: \"apiKey\" }, mode: \"production\", readOnly: true });\n```\n\nFor an API key, set `mode` to `\"production\"` or `\"sandbox\"` to match the key.\nUse a secret (`sk_`) or restricted (`rk_`) key. For read-only access, prefer a\nrestricted `rk_` key configured with only read permissions: Stripe enforces\nthose permissions too. Connecta's `readOnly: true` omits non-read tools and\nrefuses their calls on every endpoint. Omit it for the default writable connector;\nendpoint restrictions still apply. OAuth needs no `mode` and can reach both live\nand sandbox accounts through Stripe's account selectors.\n\n",
7
+ "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 reviewed hosted catalog exposes `list_available_accounts_or_orgs` to return `stripe_context` and `livemode`, and `manage_stripe_accounts` to return a Dashboard URL for managing session access; discover what this session actually exposes. 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- `convert_currency`, `send_money`, and `transfer_money` move funds. Before dispatch, confirm the intended account and its exact `stripe_context`/`livemode` pair, the source and destination or recipient, the currency, and the amount in minor units. `livemode: true` affects real balances; never switch modes to get past a refusal. `setup_recipient` creates a recipient for outbound payments and is also reviewed as destructive, matching Stripe's hint.\n- Money-movement tools can require Stripe human confirmation. Make the host-approved initial call without `human_confirmation.approval_token`. If Stripe returns an approval request and URL, present that URL as a clickable link and wait for the human to confirm approval. Only then call the same tool with the exact original arguments plus the supplied `human_confirmation.approval_token`; never invent a token, change the amount or recipient, or automatically retry an ambiguous money movement. This Stripe confirmation is separate from the host's tool approval.\n- `atlas_company_formation` creates or advances an Atlas formation application. Its `list` and `retrieve` actions share the same destructive tool boundary as `create` and `advance`; obtain host approval and preserve the selected `stripe_context` and `livemode` for every action.\n- `send_stripe_feedback` submits feedback to Stripe and is a write. Discover that name rather than the retired `send_stripe_mcp_feedback`.\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
8
  "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
9
  "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
10
  "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",
@@ -40,6 +40,8 @@ export interface TithelyOptions {
40
40
  callAdmission?: ConnectorCallAdmissionPolicy;
41
41
  /** Connector-specific inline result limit; omit to inherit the deployment. */
42
42
  maxResultBytes?: number;
43
+ /** Serve only read-classified tools on every endpoint. Defaults to false. */
44
+ readOnly?: boolean;
43
45
  /** Default page size for list tools. Defaults to 25; this connector's cap is 99. */
44
46
  defaultPageSize?: number;
45
47
  /** API base override for a proxy or test double; replaces the environment's endpoint. */
@@ -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";
@@ -15,6 +15,8 @@ interface VercelCommonOptions {
15
15
  callAdmission?: ConnectorCallAdmissionPolicy;
16
16
  /** Connector-specific inline result limit; omit to inherit the deployment. */
17
17
  maxResultBytes?: number;
18
+ /** Serve only read-classified tools on every endpoint. Defaults to false. */
19
+ readOnly?: boolean;
18
20
  }
19
21
  /**
20
22
  * Vercel's hosted MCP server over OAuth. Vercel admits only reviewed and
@@ -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
  */
@@ -207,7 +207,7 @@ Connecta's REST connector to the Vercel API with one access token. Account purpo
207
207
  ${scope}${key}${shared}${instructionsSection(instructions)}`;
208
208
  }
209
209
  const COMMON = {
210
- ...keys("title", "authScope", "purpose", "instructions", "maxResultBytes"),
210
+ ...keys("title", "authScope", "purpose", "instructions", "maxResultBytes", "readOnly"),
211
211
  callAdmission: CALL_ADMISSION,
212
212
  };
213
213
  /** The closed options vercel() accepts, selected by `auth.type`; see `assertKnownOptions`. */