@zackbart/connecta 0.30.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +21 -16
  3. package/bin/connecta.mjs +1 -0
  4. package/bin/version.mjs +1 -1
  5. package/dist/auth-elicitation.js +1 -1
  6. package/dist/call-recovery.js +25 -0
  7. package/dist/catalog-service.js +7 -2
  8. package/dist/config-schema.d.ts +14 -7
  9. package/dist/config-schema.js +29 -10
  10. package/dist/config-value-sources.js +6 -3
  11. package/dist/config.d.ts +26 -8
  12. package/dist/config.js +38 -16
  13. package/dist/connector-access.d.ts +2 -1
  14. package/dist/connectors/guarded-fetch.d.ts +18 -1
  15. package/dist/connectors/guarded-fetch.js +21 -2
  16. package/dist/describe-config.d.ts +6 -3
  17. package/dist/describe-config.js +3 -2
  18. package/dist/errors.d.ts +3 -1
  19. package/dist/errors.js +1 -0
  20. package/dist/execute.js +16 -14
  21. package/dist/index.js +4 -3
  22. package/dist/invocation.js +39 -4
  23. package/dist/meta-tools.js +17 -19
  24. package/dist/operator-record.d.ts +2 -2
  25. package/dist/operator-record.js +2 -0
  26. package/dist/operator-ui/contract.d.ts +5 -4
  27. package/dist/operator-ui/generated.js +2 -2
  28. package/dist/operator-ui/model.d.ts +1 -1
  29. package/dist/operator-ui/view.d.ts +9 -0
  30. package/dist/operator-ui/view.js +12 -0
  31. package/dist/program-writes.js +4 -49
  32. package/dist/provider.d.ts +6 -5
  33. package/dist/provider.js +3 -4
  34. package/dist/providers/_shared/google/workspace.js +1 -1
  35. package/dist/providers/_shared/rest/dispatch.js +41 -0
  36. package/dist/providers/_shared/rest/operation-index.d.ts +133 -0
  37. package/dist/providers/_shared/rest/operation-index.js +527 -0
  38. package/dist/providers/_shared/rest/tools.d.ts +141 -0
  39. package/dist/providers/_shared/rest/tools.js +569 -0
  40. package/dist/providers/_shared/rest/value-safety.d.ts +151 -0
  41. package/dist/providers/_shared/rest/value-safety.js +580 -0
  42. package/dist/providers/breeze/skill.generated.js +2 -2
  43. package/dist/providers/cloudflare/graphql.js +385 -0
  44. package/dist/providers/cloudflare/index.d.ts +47 -51
  45. package/dist/providers/cloudflare/index.js +188 -1997
  46. package/dist/providers/cloudflare/openapi.generated.js +3347 -0
  47. package/dist/providers/cloudflare/rest.d.ts +37 -0
  48. package/dist/providers/cloudflare/rest.js +1056 -0
  49. package/dist/providers/cloudflare/skill.generated.js +7 -4
  50. package/dist/providers/cloudflare/value-safety.js +1247 -0
  51. package/dist/providers/docs/index.d.ts +1 -1
  52. package/dist/providers/docs/index.js +2 -2
  53. package/dist/providers/drive/index.d.ts +1 -1
  54. package/dist/providers/drive/index.js +1 -1
  55. package/dist/providers/forms/index.d.ts +1 -1
  56. package/dist/providers/forms/index.js +2 -2
  57. package/dist/providers/github/skill.generated.js +2 -2
  58. package/dist/providers/gmail/index.d.ts +1 -1
  59. package/dist/providers/gmail/index.js +274 -23
  60. package/dist/providers/gmail/skill.generated.js +2 -2
  61. package/dist/providers/linear/index.js +1 -1
  62. package/dist/providers/mixpanel/index.d.ts +27 -12
  63. package/dist/providers/mixpanel/index.js +58 -6
  64. package/dist/providers/mixpanel/operations.js +20 -0
  65. package/dist/providers/mixpanel/rest.js +603 -0
  66. package/dist/providers/mixpanel/skill.generated.js +3 -2
  67. package/dist/providers/mixpanel/value-safety.js +16 -0
  68. package/dist/providers/notion/index.d.ts +35 -31
  69. package/dist/providers/notion/index.js +76 -1499
  70. package/dist/providers/notion/openapi.generated.js +75 -0
  71. package/dist/providers/notion/rest.d.ts +24 -0
  72. package/dist/providers/notion/rest.js +1186 -0
  73. package/dist/providers/notion/skill.generated.js +5 -4
  74. package/dist/providers/notion/value-safety.js +31 -0
  75. package/dist/providers/planning-center/index.d.ts +27 -0
  76. package/dist/providers/planning-center/index.js +286 -24
  77. package/dist/providers/planning-center/skill.generated.js +24 -4
  78. package/dist/providers/sheets/index.d.ts +1 -1
  79. package/dist/providers/sheets/index.js +3 -3
  80. package/dist/providers/slides/index.d.ts +1 -1
  81. package/dist/providers/slides/index.js +3 -3
  82. package/dist/providers/stripe/index.d.ts +26 -20
  83. package/dist/providers/stripe/index.js +103 -130
  84. package/dist/providers/stripe/openapi.generated.js +649 -0
  85. package/dist/providers/stripe/rest.d.ts +14 -0
  86. package/dist/providers/stripe/rest.js +395 -0
  87. package/dist/providers/stripe/skill.generated.js +7 -5
  88. package/dist/providers/stripe/value-safety.js +705 -0
  89. package/dist/providers/vercel/index.d.ts +31 -22
  90. package/dist/providers/vercel/index.js +96 -853
  91. package/dist/providers/vercel/openapi.generated.js +456 -0
  92. package/dist/providers/vercel/rest.d.ts +20 -0
  93. package/dist/providers/vercel/rest.js +1002 -0
  94. package/dist/providers/vercel/skill.generated.js +4 -5
  95. package/dist/providers/vercel/value-safety.js +328 -0
  96. package/dist/providers/wispr-flow/index.d.ts +14 -0
  97. package/dist/providers/wispr-flow/index.js +117 -0
  98. package/dist/providers/wispr-flow/skill.generated.js +8 -0
  99. package/dist/routes/mcp.js +30 -10
  100. package/dist/routes/operator.js +14 -7
  101. package/dist/routes/ui-config.js +2 -2
  102. package/dist/server.js +3 -0
  103. package/dist/skills.js +5 -4
  104. package/dist/tool-safety.js +13 -5
  105. package/dist/types.d.ts +1 -1
  106. package/dist/ui.d.ts +1 -1
  107. package/dist/ui.js +1 -1
  108. package/dist/usage-guide.js +3 -3
  109. package/dist/version.d.ts +1 -1
  110. package/dist/version.js +1 -1
  111. package/dist/write-outcome.js +47 -0
  112. package/documentation/README.md +1 -0
  113. package/documentation/architecture.md +155 -28
  114. package/documentation/auth.md +86 -27
  115. package/documentation/code-mode.md +81 -72
  116. package/documentation/deploying.md +10 -5
  117. package/documentation/integrating.md +72 -9
  118. package/documentation/meta-tools.md +52 -38
  119. package/documentation/operating.md +11 -8
  120. package/documentation/operator-tests.md +2 -3
  121. package/documentation/operator-ui.md +9 -3
  122. package/documentation/provider-auth-migration.md +308 -0
  123. package/documentation/provider-migration-0.29.md +18 -6
  124. package/examples/worker/AGENTS.md +5 -0
  125. package/examples/worker/README.md +2 -2
  126. package/examples/worker/src/connecta.config.ts +5 -0
  127. package/package.json +7 -2
  128. package/templates/node/AGENTS.md +6 -4
  129. package/templates/node/README.md +6 -4
  130. package/templates/node/package.json +1 -1
  131. package/templates/node/src/connecta.config.ts +5 -0
  132. package/dist/providers/vercel/mcp-ownership.js +0 -212
  133. package/dist/tool-safety.d.ts +0 -9
@@ -1,11 +1,10 @@
1
1
  // Generated by scripts/generate-providers.mjs; do not edit.
2
2
  export const skill = {
3
- "content": "`. Every named account-scoped tool accepts a `teamId` override; pass `null` to target the token owner's personal account.\n## REST complement routing\n\nHosted MCP is the default. Configure this complement with `surface: \"api\"`\nand a separate connector id and access-token credential. OAuth and API-token\nstores are independent; failures never switch identity or replay a write.\nTeam defaults route calls and do not constrain the token's permissions.\n\nUse hosted `list_teams`, `list_projects`, `get_project`, `list_deployments`,\n`get_deployment`, `get_deployment_build_logs` or `list_deployment_events`,\n`get_runtime_logs`, `list_project_domains`, `add_project_domain`, and\n`cancel_deployment` for the corresponding operations. Follow their live schemas,\nnot the removed REST argument shapes. Read deployment status before logs and\nnarrow log windows.\n\nThis complement retains `verify_project_domain`, `remove_project_domain`,\nand `delete_deployment` because the reviewed category\npages do not publish equivalent tools. Hosted `request_promote` moves an existing\nbuild to production and does not rebuild it (the rolling-releases category\nprovides the equivalent that the deployments category does not list). New deployments use hosted\n`create_deployment` or `deploy_to_vercel`.\n\n## Environment values\n\n`list_project_env_vars` never decrypts or returns values. Set `upsert: false` on\n`upsert_project_env_var` for create-only writes that refuse to overwrite. `upsert_project_env_var`,\n`update_project_env_var`, and `delete_project_env_var` return metadata without\nvalues. Create/update values are write input only. The hosted connector excludes\n`filter_project_envs`, `get_project_env`, `create_project_env`, and\n`edit_project_env`, whose published contracts lack these response protections.\nEnvironment changes apply to future deployments.\n\n## REST gaps and uploads\n\n`vercel_api_get` and `vercel_api_mutate` reach uncovered endpoints. They refuse\npaths owned by the canonical hosted or value-safe named tools, including\nproject creation/update, pause/unpause and protection bypass. Ownership covers\nthe published tool categories, not just the old reviewed classifier names;\nunreviewed live tools remain fail-closed writes. Do not use a\nraw hatch to restore a removed duplicate. `upload_file` on MCP owns deployment\nfile uploads; `vercel_api_upload` retains other raw-body endpoints and explicit\nheaders. It never reads a local file. Paths include the API version and cannot\nbe absolute URLs. Query parameters are name/value pairs; `personalAccount: true`\nomits this connection's default team. Rate-limit failures carry vendor retry timing.\n# Vercel MCP usage\n\nOfficial MCP surface: tool names, descriptions, argument schemas, and result\nschemas come from Vercel's live server. Connecta preserves retained vendor contracts and\napplies release-reviewed classification. Reviewed writes stay writes even when Vercel claims they only read.\n\nAccount purpose: \n\n- Hosted MCP owns discovery, deployments, logs, project-domain listing/adding,\n cancellation and file uploads. Use the separately configured REST complement\n for value-safe project environment variables, uncovered lifecycle/domain\n operations and REST gaps. It has its own connector id and credential.\n- Discover the live catalog before assuming a tool exists. Vercel can change\n the surface independently of a Connecta release, and account features may\n affect what the authorization can reach.\n- Complete authorization and grant access to the owning team before acting. Listing this server does not prove team access. A configured API `teamId` is a routing default, not an authorization boundary.\n- MCP pagination and arguments follow the live MCP schemas; do not apply API pagination or argument shapes to them.\n- Resolve team, project, deployment, run, thread, and order ids with the list\n and get tools. Do not guess opaque ids.\n- Diagnose deployments with `get_deployment`, then build logs, runtime error\n clusters, and runtime logs. Narrow time windows before raising result limits.\n- Purchase tools change billing. Read a quote first and carry its price,\n idempotency key, and requested term into the confirmed purchase unchanged.\n- `get_access_to_vercel_url` creates a temporary access grant. Treat the URL\n it returns as a credential and do not expose it outside the requested task.\n- `deploy_to_vercel` and `import-claude-design-from-url` can create or update\n live projects. Read the target and deployment mode before invoking them.\n- An `auth_required` failure means this connector's OAuth grant is missing or\n expired. Run `authorize_connector` for this connector id, then retry.\n",
3
+ "content": "- Resolve team, project, and deployment ids with list reads; never guess one. Team ids begin `team_`, project ids `prj_`, and deployment ids `dpl_`.\n- Diagnose a deployment by reading its state first (`readyState`, `errorCode`, `errorMessage`), then its build logs, then runtime logs for a deployment that built. Narrow time windows before raising limits.\n- Environment variable changes apply only to future deployments; a new deployment is a separate write. Promoting an existing deployment to production does not rebuild it.\n- Treat every create, update, delete, promotion, cancellation, and purchase as a write. For guest calls, classification, and routing, fetch `skills({ name: \"usage\" })`.\n",
4
4
  "instructionsHeading": "Account instructions",
5
5
  "fragments": {
6
- "guide_0": "`. Every named account-scoped tool accepts a `teamId` override; pass `null` to target the token owner's personal account.",
7
- "guide_1": "\n## REST complement routing\n\nHosted MCP is the default. Configure this complement with `surface: \"api\"`\nand a separate connector id and access-token credential. OAuth and API-token\nstores are independent; failures never switch identity or replay a write.\nTeam defaults route calls and do not constrain the token's permissions.\n\nUse hosted `list_teams`, `list_projects`, `get_project`, `list_deployments`,\n`get_deployment`, `get_deployment_build_logs` or `list_deployment_events`,\n`get_runtime_logs`, `list_project_domains`, `add_project_domain`, and\n`cancel_deployment` for the corresponding operations. Follow their live schemas,\nnot the removed REST argument shapes. Read deployment status before logs and\nnarrow log windows.\n\nThis complement retains `verify_project_domain`, `remove_project_domain`,\nand `delete_deployment` because the reviewed category\npages do not publish equivalent tools. Hosted `request_promote` moves an existing\nbuild to production and does not rebuild it (the rolling-releases category\nprovides the equivalent that the deployments category does not list). New deployments use hosted\n`create_deployment` or `deploy_to_vercel`.\n\n## Environment values\n\n`list_project_env_vars` never decrypts or returns values. Set `upsert: false` on\n`upsert_project_env_var` for create-only writes that refuse to overwrite. `upsert_project_env_var`,\n`update_project_env_var`, and `delete_project_env_var` return metadata without\nvalues. Create/update values are write input only. The hosted connector excludes\n`filter_project_envs`, `get_project_env`, `create_project_env`, and\n`edit_project_env`, whose published contracts lack these response protections.\nEnvironment changes apply to future deployments.\n\n## REST gaps and uploads\n\n`vercel_api_get` and `vercel_api_mutate` reach uncovered endpoints. They refuse\npaths owned by the canonical hosted or value-safe named tools, including\nproject creation/update, pause/unpause and protection bypass. Ownership covers\nthe published tool categories, not just the old reviewed classifier names;\nunreviewed live tools remain fail-closed writes. Do not use a\nraw hatch to restore a removed duplicate. `upload_file` on MCP owns deployment\nfile uploads; `vercel_api_upload` retains other raw-body endpoints and explicit\nheaders. It never reads a local file. Paths include the API version and cannot\nbe absolute URLs. Query parameters are name/value pairs; `personalAccount: true`\nomits this connection's default team. Rate-limit failures carry vendor retry timing.\n",
8
- "guide_2": "# Vercel MCP usage\n\nOfficial MCP surface: tool names, descriptions, argument schemas, and result\nschemas come from Vercel's live server. Connecta preserves retained vendor contracts and\napplies release-reviewed classification. Reviewed writes stay writes even when Vercel claims they only read.\n\nAccount purpose: ",
9
- "guide_3": "\n\n- Hosted MCP owns discovery, deployments, logs, project-domain listing/adding,\n cancellation and file uploads. Use the separately configured REST complement\n for value-safe project environment variables, uncovered lifecycle/domain\n operations and REST gaps. It has its own connector id and credential.\n- Discover the live catalog before assuming a tool exists. Vercel can change\n the surface independently of a Connecta release, and account features may\n affect what the authorization can reach.\n- Complete authorization and grant access to the owning team before acting. Listing this server does not prove team access. A configured API `teamId` is a routing default, not an authorization boundary.\n- MCP pagination and arguments follow the live MCP schemas; do not apply API pagination or argument shapes to them.\n- Resolve team, project, deployment, run, thread, and order ids with the list\n and get tools. Do not guess opaque ids.\n- Diagnose deployments with `get_deployment`, then build logs, runtime error\n clusters, and runtime logs. Narrow time windows before raising result limits.\n- Purchase tools change billing. Read a quote first and carry its price,\n idempotency key, and requested term into the confirmed purchase unchanged.\n- `get_access_to_vercel_url` creates a temporary access grant. Treat the URL\n it returns as a credential and do not expose it outside the requested task.\n- `deploy_to_vercel` and `import-claude-design-from-url` can create or update\n live projects. Read the target and deployment mode before invoking them.\n- An `auth_required` failure means this connector's OAuth grant is missing or\n expired. Run `authorize_connector` for this connector id, then retry.\n"
6
+ "oauth": "\n\nTool names, descriptions, argument schemas, and result schemas come from Vercel's live server. Connecta applies release-reviewed classification; reviewed writes stay writes even when Vercel claims they only read.\n\n- Discover the live catalog before assuming a tool exists. Vercel can change the surface independently of a Connecta release, and account features may affect what the authorization can reach.\n- Complete authorization and grant access to the owning team before acting. Listing this server does not prove team access.\n- MCP pagination and arguments follow the live MCP schemas; do not apply REST pagination or argument shapes to them.\n- Diagnose deployments with `get_deployment`, then build logs, runtime error clusters, and runtime logs.\n- `get_project_env` and `filter_project_envs` can return decrypted environment values, so Connecta reviews them as writes, and a read-only endpoint refuses them. Prefer reading variable keys and targets without values.\n- Purchase tools change billing. Read a quote first and carry its price, idempotency key, and requested term into the confirmed purchase unchanged.\n- `get_access_to_vercel_url` creates a temporary access grant. Treat the URL it returns as a credential and do not expose it outside the requested task.\n- `deploy_to_vercel` and `import-claude-design-from-url` can create or update live projects. Read the target and deployment mode before invoking them.\n- An `auth_required` failure means this connector's OAuth grant is missing or expired. Run `authorize_connector` for this connector id, then retry.\n",
7
+ "key": "\n\n- This is Connecta's REST connector to `api.vercel.com`, holding one operator-managed access token. A default team routes calls; it does not narrow what the token can reach.\n- `vercel_api_search` finds API operations, not records: it searches the pinned API index and names the tool that calls each operation. `vercel_api_details` returns one operation's parameters with types, enums, and required names. Neither sends a request.\n- `vercel_api_read` calls GET operations, plus reviewed POSTs that only read: `POST /v2/observability/query`, registrar availability, price, and search, and `POST /v8/artifacts`. `vercel_api_write` calls every other POST, PUT, PATCH, and DELETE, with `method` and `path` stated; a read-only endpoint refuses it.\n- Paths are concrete and carry the version search returned: `/v13/deployments/dpl_123`, not `/deployments/dpl_123`. Pass `query` and `body` as JSON objects named as `vercel_api_details` lists them. Every call is checked against the pinned index before it is sent: an unknown path returns the nearest operations, and an unknown or missing parameter returns `validation.issues` with the accepted names. Nothing reached Vercel, so fix the call from that answer.\n- Results arrive as `{ status, data, page? }`. On a list, `page.next` is the cursor and `page.param` the query parameter to pass it as: `until` (a millisecond timestamp from `pagination.next`) on most lists, `cursor` on newer ones. Lists belong inside `execute_code`; pass `select` (dot paths such as `deployments.uid` and `deployments.state`) to keep only the fields the question needs.\n- `list_teams` returns team ids and slugs and the connection's default team. `get_deployment_build_logs` reads a deployment's build events without following live output. `get_runtime_logs` collects the runtime log stream for at most `waitMs` or `maxRows`, then stops and says why. Generic reads refuse `follow` and the runtime-log stream, which never end on their own.\n- `vercel_api_upload` sends explicit text or base64 bytes to an octet-stream operation, such as `POST /v2/files` before a deployment (Connecta computes `x-vercel-digest`). `vercel_api_write` sends JSON only.\n- Environment values never come back. `list_project_env_vars` never decrypts. `upsert_project_env_var` (`upsert: false` for create-only), `update_project_env_var`, and `delete_project_env_var` return metadata without values; values are write input only. Generic reads refuse `GET /v1/projects/{idOrName}/env/{id}`, `GET /v10/projects/{idOrName}/env`, `GET /v1/env/{id}`, and any `decrypt`, and Connecta replaces the value of every environment variable in any result with `\"[redacted]\"`, including project reads, shared variables, and audit events.\n- Other secrets read as `[redacted]` in every result: credential-named fields and their whole contents (`token`, `secret`, `password`, `jwt`, `credentials`, `Authorization`, …), protection-bypass secrets (each bypass's metadata stays under a numbered placeholder), deploy hook URLs, drain and webhook headers and signing secrets (including the one a new webhook or drain returns), drain and webhook destination URLs and external route, rewrite, and redirect destinations beyond their scheme and host, the args and values of every header, cookie, and query rule (route transforms and conditions, firewall conditions), team invite codes, synced Global Config items, and credentials inside other URLs. Failures of every reviewed operation, and of any operation whose request takes a credential-named field, say only the status and a reviewed code. Read or rotate withheld secrets in the Vercel dashboard. A `[redacted]` field is never the real value; do not send it back.\n- Refused by design: endpoints that mint credentials (access tokens, AI Gateway API keys, project OIDC and trace tokens, Connect and SSO tokens, Edge Config tokens, KMS-signed tokens, Flags SDK keys, protection-bypass secrets, installation credential rotation), Connect authorization requests, project transfer requests (the code lets another team claim the project), KMS signing, a domain move-out (its answer is a transfer token), Global Config items, backups, and tokens, and domain transfer auth codes. Each refusal names its reason.\n- The hosted OAuth connection's documentation search, runtime error clusters, toolbar threads, agent runs, web analytics summaries, purchase quotes, `deploy_to_vercel`, and access grants for protected URLs do not exist with a token. Configure an OAuth connector for those.\n- A `429` carries Vercel's reset time; Vercel meters endpoints separately, so wait before retrying the same operation. An `auth_required` failure means the token is missing, expired, or outside this team: an operator must replace it in this connection in the operator UI, and `authorize_connector` returns that handoff. A rejected argument comes back in Vercel's own words; read it rather than replacing the token.\n",
8
+ "shared": "- Resolve team, project, and deployment ids with list reads; never guess one. Team ids begin `team_`, project ids `prj_`, and deployment ids `dpl_`.\n- Diagnose a deployment by reading its state first (`readyState`, `errorCode`, `errorMessage`), then its build logs, then runtime logs for a deployment that built. Narrow time windows before raising limits.\n- Environment variable changes apply only to future deployments; a new deployment is a separate write. Promoting an existing deployment to production does not rebuild it.\n- Treat every create, update, delete, promotion, cancellation, and purchase as a write. For guest calls, classification, and routing, fetch `skills({ name: \"usage\" })`.\n"
10
9
  }
11
10
  };
@@ -0,0 +1,328 @@
1
+ // Vercel's reviewed value-safety table (decision 0005, "Value safety"), on the
2
+ // shared mechanism in `../_shared/rest/value-safety.ts`.
3
+ //
4
+ // No tool response, success or error, returns a credential (API, service, or
5
+ // bypass tokens, signing secrets, deploy-hook and other bearer URLs, private
6
+ // keys, authorization verifiers, auth headers) or a stored secret value
7
+ // (environment variables, Global Config items), unless a reviewed named tool
8
+ // exists to return it. Three layers, strongest first:
9
+ //
10
+ // 1. Refuse by operation. `scripts/value-safety.mjs` flags every operation in
11
+ // the pinned spec whose response may carry one (committed as
12
+ // `value-safety.candidates.json`); each needs a verdict here, and
13
+ // `value-safety.node.test.ts` runs the shared harness over this table.
14
+ // 2. Reviewed field paths for `redact` verdicts, applied to every success body
15
+ // on every path (generic tools, HEAD data, named tools, logs, uploads)
16
+ // before cursors or `select` read it. Every reviewed operation, and every
17
+ // one whose request accepts a credential-named field, also answers
18
+ // failures with fixed messages, never the vendor's text.
19
+ // 3. The shared key-name heuristic, defense in depth only, over every body:
20
+ // any subtree under a credential-named key, labelled key/value records
21
+ // whose label is credential-named, environment-variable containers, and
22
+ // URLs with userinfo or credential query parameters.
23
+ //
24
+ // Residual risk, as with Infisical: a secret a person typed into a free-text
25
+ // name, description, log line, or identifier is out of scope.
26
+ import { redact, refuse as refusal, safe, under, } from "../_shared/rest/value-safety.js";
27
+ /** A refusal's reason reads as a sentence about the operation: "It mints …". */
28
+ const refuse = (reason) => refusal(`It ${reason}`);
29
+ /** A project object: env values, deploy hooks, and protection-bypass secrets. */
30
+ const PROJECT = [
31
+ "env[].value",
32
+ "env[].vsmValue",
33
+ "env[].legacyValue",
34
+ "link.deployHooks[].url",
35
+ "protectionBypass@keys",
36
+ // Header, cookie, and query conditions on internal and firewall routes.
37
+ "internalRoutes[].has[?header].value",
38
+ "internalRoutes[].missing[?header].value",
39
+ "security.firewallRoutes[].has[?header].value",
40
+ "security.firewallRoutes[].missing[?header].value",
41
+ ];
42
+ /** Project metadata whose names contain a credential word. */
43
+ const PROJECT_ALLOW = [
44
+ "protectionBypass",
45
+ "oidcTokenConfig",
46
+ "security.firewallBypassIps",
47
+ "usageStatus.bypassThrottleUntil",
48
+ "dismissedToasts[].value",
49
+ ];
50
+ const DRAIN = [
51
+ "delivery.secret",
52
+ "delivery.headers{}",
53
+ "origin:delivery.endpoint",
54
+ "origin:delivery.endpoint.traces",
55
+ "secret",
56
+ "headers{}",
57
+ "origin:url",
58
+ ];
59
+ const SHARED_ENV = ["data[].value", "created[].value", "updated[].value", "failed[].error.value"];
60
+ const PROJECT_ENV = ["value", "legacyValue", "vsmValue", "[].value", "[].legacyValue"];
61
+ const CONNECTOR_ALLOW = ["appTokens", "userTokens"];
62
+ const PROJECT_REASON = "Project objects embed environment values, deploy hook URLs, and protection-bypass secrets.";
63
+ const DRAIN_REASON = "Drains store destination credentials (headers, URLs whose path or query can be a bearer secret) and a signing secret.";
64
+ const WEBHOOK = ["secret", "origin:url"];
65
+ const WEBHOOK_REASON = "Webhooks carry a signing secret and a destination URL whose path can be a bearer secret.";
66
+ /** Route rules: a transform that sets a credential-named header carries the credential in `args`. */
67
+ /**
68
+ * Route rules: every header, cookie, or query transform's `args` and every
69
+ * header, cookie, or query condition's `value` go, whatever the target key's
70
+ * form (a string, or a predicate object such as `{ eq: "Authorization" }`);
71
+ * a header-name list would always miss one (`X-Auth-Key`).
72
+ */
73
+ const ROUTE_TRANSFORMS = [
74
+ "transforms[].args",
75
+ "has[?header].value",
76
+ "missing[?header].value",
77
+ // Headers a route sets, and the locale cookie it reads (its name is credential vocabulary).
78
+ "headers{}",
79
+ "locale.cookie",
80
+ ];
81
+ const DEPLOYMENT = [
82
+ ...under(["routes[]", "services[].routes[]", "services[].rewrites[]", "services[].redirects[]"], ROUTE_TRANSFORMS),
83
+ "services[].headers[].headers[].value",
84
+ "services[].headers[].has[?header].value",
85
+ "services[].headers[].missing[?header].value",
86
+ ];
87
+ const DEPLOYMENT_REASON = "Deployment routes can set credential-named headers (transforms with target.key Authorization) whose args are the credential.";
88
+ const ROUTE_REASON = "Route transforms can set credential-named headers whose args are the credential.";
89
+ const ENV_REASON = "Environment-variable bodies carry decrypted or encrypted values.";
90
+ const NO_SECRET_REASON = "Answers status or nothing; the flag is the operation's name, not its response.";
91
+ const EDGE_ITEMS = "returns Global Config item values, which may be secrets; read them from your application with the Edge Config SDK.";
92
+ const MINT = "mints a credential and returns it in cleartext.";
93
+ const MODEL_CONFIG = "AI Gateway routing configuration; its descriptions mention tokens as model usage units.";
94
+ const KMS_POLICY = "Issuer policies name environments and claims; signing keys stay with Vercel.";
95
+ /** A firewall rule's header, cookie, and query conditions: their values can be credentials (an `X-Auth-Key` match). */
96
+ const FIREWALL_CONDITIONS = [
97
+ "conditionGroup[].conditions[?header].value",
98
+ "conditionGroup[].conditions[?header].values",
99
+ ];
100
+ /** Every firewall config shape: rules, rulesets, and top-level conditions, active, draft, and versions. */
101
+ const FIREWALL = under(["rules[]", "rulesets[]", "conditions[]"], FIREWALL_CONDITIONS);
102
+ const FIREWALL_REASON = "Firewall rules match header, cookie, and query values, which can be credentials a rule checks for (an X-Auth-Key).";
103
+ const TEAM_INVITE = "Teams carry inviteCode, which lets its holder join the team (visible to owners).";
104
+ /**
105
+ * The reviewed verdict for every flagged operation, keyed `METHOD /path`.
106
+ * `refuse` is never sent by the generic tools; named tools that need one of
107
+ * these operations project it themselves (value-safe environment variables).
108
+ */
109
+ const VALUE_SAFETY = {
110
+ "PATCH /aliases/{id}/protection-bypass": refuse("creates a shareable link that bypasses Deployment Protection."),
111
+ "POST /api-keys": refuse("mints an AI Gateway API key and returns it in cleartext."),
112
+ "POST /v1/connect/authorize/{connector}": refuse("starts a Connect authorization for a caller-chosen subject and returns its verifier and device code."),
113
+ "POST /v1/connect/connectors": safe("Connector capability metadata; appTokens and userTokens describe grants, not tokens.", CONNECTOR_ALLOW),
114
+ "GET /v1/connect/connectors/{connector}": safe("Connector capability metadata.", CONNECTOR_ALLOW),
115
+ "POST /v1/connect/connectors/{connector}/managed/eject": safe("Connector capability metadata.", CONNECTOR_ALLOW),
116
+ "PATCH /v1/connect/connectors/{connector}/trigger-destinations": safe("Connector capability metadata.", CONNECTOR_ALLOW),
117
+ "GET /v2/connect/connectors": safe("Connector capability metadata.", under(["connectors[]"], CONNECTOR_ALLOW)),
118
+ "PATCH /v2/connect/connectors/{connector}": safe("Connector capability metadata.", under(["connector"], CONNECTOR_ALLOW)),
119
+ "POST /v1/connect/token/{connector}": refuse("mints a token for a Connect connector."),
120
+ "GET /v1/drains": redact(DRAIN_REASON, under(["drains[]"], DRAIN)),
121
+ "POST /v1/drains": redact(DRAIN_REASON, DRAIN),
122
+ "POST /v1/drains/test": redact("Answers a delivery test; treated as part of the drain family.", DRAIN),
123
+ "GET /v1/drains/{id}": redact(DRAIN_REASON, DRAIN),
124
+ "PATCH /v1/drains/{id}": redact(DRAIN_REASON, DRAIN),
125
+ "DELETE /v1/drains/{id}": safe(NO_SECRET_REASON),
126
+ "GET /v1/log-drains": redact(DRAIN_REASON, under(["drains[]", "[]"], DRAIN)),
127
+ "POST /v1/log-drains": redact(DRAIN_REASON, DRAIN),
128
+ "GET /v1/log-drains/{id}": redact(DRAIN_REASON, DRAIN),
129
+ "DELETE /v1/log-drains/{id}": safe(NO_SECRET_REASON),
130
+ "GET /v2/integrations/log-drains": redact(DRAIN_REASON, under(["[]"], DRAIN)),
131
+ "POST /v2/integrations/log-drains": redact(DRAIN_REASON, DRAIN),
132
+ "DELETE /v1/integrations/log-drains/{id}": safe(NO_SECRET_REASON),
133
+ "GET /v1/webhooks": redact(WEBHOOK_REASON, under(["[]", "webhooks[]"], WEBHOOK)),
134
+ "POST /v1/webhooks": redact(WEBHOOK_REASON, WEBHOOK),
135
+ "GET /v1/webhooks/{id}": redact(WEBHOOK_REASON, WEBHOOK),
136
+ "DELETE /v1/webhooks/{id}": safe(NO_SECRET_REASON),
137
+ "GET /v1/env": redact(ENV_REASON, SHARED_ENV),
138
+ "POST /v1/env": redact(ENV_REASON, SHARED_ENV),
139
+ "PATCH /v1/env": redact(ENV_REASON, SHARED_ENV),
140
+ "DELETE /v1/env": redact(ENV_REASON, SHARED_ENV),
141
+ "GET /v1/env/{id}": refuse("returns a shared environment variable's decrypted value."),
142
+ "PATCH /v1/env/{id}/unlink/{projectId}": redact(ENV_REASON, ["value"]),
143
+ "GET /v1/global-config": safe("Global Config metadata: ids, slugs, sizes, and digests, without items."),
144
+ "POST /v1/global-config": safe("Answers the created Global Config's metadata."),
145
+ "GET /v1/global-config/{edgeConfigId}": safe("Global Config metadata without items."),
146
+ "PUT /v1/global-config/{edgeConfigId}": safe("Answers the updated Global Config's metadata."),
147
+ "DELETE /v1/global-config/{edgeConfigId}": safe(NO_SECRET_REASON),
148
+ "GET /v1/global-config/{edgeConfigId}/backups": safe("Lists backup versions and sizes, without items."),
149
+ "GET /v1/global-config/{edgeConfigId}/backups/{edgeConfigBackupVersionId}": refuse(EDGE_ITEMS),
150
+ "POST /v1/global-config/{edgeConfigId}/backups/{edgeConfigBackupVersionId}/restore": safe(NO_SECRET_REASON),
151
+ "GET /v1/global-config/{edgeConfigId}/item/{edgeConfigItemKey}": refuse(EDGE_ITEMS),
152
+ "GET /v1/global-config/{edgeConfigId}/items": refuse(EDGE_ITEMS),
153
+ "PATCH /v1/global-config/{edgeConfigId}/items": safe("Answers a status; item values are write input only."),
154
+ "GET /v1/global-config/{edgeConfigId}/schema": safe("A JSON Schema for items, without values."),
155
+ "POST /v1/global-config/{edgeConfigId}/schema": safe("Answers the schema, without values."),
156
+ "DELETE /v1/global-config/{edgeConfigId}/schema": safe(NO_SECRET_REASON),
157
+ "POST /v1/global-config/{edgeConfigId}/token": refuse(MINT),
158
+ "GET /v1/global-config/{edgeConfigId}/token/{token}": refuse("returns a Global Config read token."),
159
+ "GET /v1/global-config/{edgeConfigId}/tokens": refuse("returns every Global Config read token in cleartext."),
160
+ "DELETE /v1/global-config/{edgeConfigId}/tokens": safe(NO_SECRET_REASON),
161
+ "GET /v1/installations/{integrationConfigurationId}/resources/{resourceId}/experimentation/global-config": refuse(EDGE_ITEMS),
162
+ "PUT /v1/installations/{integrationConfigurationId}/resources/{resourceId}/experimentation/global-config": redact("Answers the synced Global Config, items included; item values can be secrets.", ["items"]),
163
+ "HEAD /v1/installations/{integrationConfigurationId}/resources/{resourceId}/experimentation/global-config": safe("Headers only; the heuristic still covers them."),
164
+ "POST /v1/installations/{integrationConfigurationId}/credentials/revoke": safe(NO_SECRET_REASON),
165
+ "POST /v1/installations/{integrationConfigurationId}/credentials/rotate": refuse("rotates an installation credential and returns the new access token."),
166
+ "PUT /v1/installations/{integrationConfigurationId}/resources/{resourceId}/secrets": safe("Answers nothing; secrets are write input only."),
167
+ "POST /v1/integrations/sso/token": refuse("exchanges an integration SSO code for access, ID, and refresh tokens."),
168
+ "POST /v1/kms/issuers/{issuerId}/keys": safe("Answers public key material and key metadata; private keys stay with Vercel."),
169
+ "POST /v1/kms/issuers/{issuerId}/keys/{keyId}/activate": safe("Answers key metadata."),
170
+ "POST /v1/kms/issuers/{issuerId}/keys/{keyId}/revoke": safe("Answers issuer metadata; tokenClaims are policy, not tokens."),
171
+ "POST /v1/kms/issuers/{issuerId}/sign/message": refuse("signs a message with the issuer's private key, acting as the organization."),
172
+ "POST /v1/kms/issuers/{issuerId}/sign/token": refuse("signs a token with the issuer's private key."),
173
+ "GET /v1/microfrontends/groups/{groupId}/projects": redact(PROJECT_REASON, under(["projects[]"], PROJECT), under(["projects[]"], PROJECT_ALLOW)),
174
+ "POST /v1/projects/traces/session": refuse("mints a trace session token for a deployment."),
175
+ "POST /v1/projects/{idOrName}/avatar": redact(PROJECT_REASON, PROJECT, PROJECT_ALLOW),
176
+ "DELETE /v1/projects/{idOrName}/avatar": redact(PROJECT_REASON, PROJECT, PROJECT_ALLOW),
177
+ "DELETE /v1/projects/{idOrName}/env": redact(ENV_REASON, PROJECT_ENV),
178
+ "GET /v1/projects/{idOrName}/env/{id}": refuse("returns one environment variable's decrypted value. Read its metadata with list_project_env_vars."),
179
+ "PATCH /v1/projects/{idOrName}/protection-bypass": refuse("generates Protection Bypass for Automation secrets."),
180
+ "POST /v1/projects/{idOrName}/token": refuse("mints a project OIDC token."),
181
+ "GET /v1/projects/{projectIdOrName}/feature-flags/sdk-keys": safe("Lists SDK key metadata with masked partialKeyValue."),
182
+ "PUT /v1/projects/{projectIdOrName}/feature-flags/sdk-keys": refuse("mints a Flags SDK key and returns it in cleartext."),
183
+ "DELETE /v1/projects/{projectIdOrName}/feature-flags/sdk-keys/{hashKey}": safe(NO_SECRET_REASON),
184
+ "PATCH /v1/projects/{projectId}/microfrontends": redact(PROJECT_REASON, PROJECT, PROJECT_ALLOW),
185
+ "GET /v1/projects/{projectId}/routes": redact(ROUTE_REASON, under(["routes[].route"], ROUTE_TRANSFORMS)),
186
+ "POST /v1/projects/{projectId}/routes": redact(ROUTE_REASON, under(["route.route"], ROUTE_TRANSFORMS)),
187
+ "POST /v1/projects/{projectId}/routes/generate": redact(ROUTE_REASON, [
188
+ ...under(["route"], ROUTE_TRANSFORMS),
189
+ "route.actions[].headers[].value",
190
+ "route.conditions[?header].value",
191
+ ]),
192
+ "PATCH /v1/projects/{projectId}/routes/{routeId}": redact(ROUTE_REASON, under(["route.route"], ROUTE_TRANSFORMS)),
193
+ "GET /v1/registrar/domains/{domain}/auth-code": refuse("returns the domain's transfer authorization code, which lets its holder move the domain to another registrar."),
194
+ "GET /v1/security/firewall/bypass": safe("IP bypass rules are firewall configuration: addresses, not secrets."),
195
+ "POST /v1/security/firewall/bypass": safe("IP bypass rules are firewall configuration."),
196
+ "DELETE /v1/security/firewall/bypass": safe("IP bypass rules are firewall configuration."),
197
+ "GET /v1/security/firewall/config": redact(FIREWALL_REASON, under(["active", "draft", "versions[]"], FIREWALL)),
198
+ "PUT /v1/security/firewall/config": redact(FIREWALL_REASON, under(["active", "draft", "versions[]"], FIREWALL)),
199
+ "POST /v1/security/firewall/config/generate-rule": redact(FIREWALL_REASON, under(["rule"], FIREWALL_CONDITIONS)),
200
+ "GET /v1/security/firewall/config/{configVersion}": redact(FIREWALL_REASON, FIREWALL),
201
+ "POST /v1/security/firewall/config/{configVersion}/activate": redact(FIREWALL_REASON, FIREWALL),
202
+ "POST /v1/storage/stores/integration/direct": safe("store.secrets lists secret names and lengths, never values.", [
203
+ "store.secrets",
204
+ "store.secretRotationRequestedBy",
205
+ "store.secretRotationRequestedReason",
206
+ ]),
207
+ "GET /v1/vcr/repository/{idOrName}/images/{imageIdOrDigest}": redact("Image layer environment entries are build-time NAME=value pairs.", ["image.layers[].env"]),
208
+ "GET /v10/projects": redact(PROJECT_REASON, under(["projects[]", "[]"], PROJECT), under(["projects[]", "[]"], PROJECT_ALLOW)),
209
+ "GET /v10/projects/{idOrName}/env": refuse("returns environment values beside their metadata. Use list_project_env_vars, which never decrypts or returns a value."),
210
+ "POST /v10/projects/{idOrName}/env": redact(ENV_REASON, [
211
+ "created.value",
212
+ "created.vsmValue",
213
+ "created.legacyValue",
214
+ "created[].value",
215
+ "created[].vsmValue",
216
+ "created[].legacyValue",
217
+ "failed[].error.value",
218
+ ]),
219
+ "POST /v11/projects": redact(PROJECT_REASON, PROJECT, PROJECT_ALLOW),
220
+ "PATCH /v12/deployments/{id}/cancel": redact(DEPLOYMENT_REASON, DEPLOYMENT),
221
+ "POST /v13/deployments": redact(DEPLOYMENT_REASON, DEPLOYMENT),
222
+ "GET /v13/deployments/{idOrUrl}": redact(DEPLOYMENT_REASON, DEPLOYMENT),
223
+ "GET /v2/deployments/{id}/aliases": redact("Aliases carry protection-bypass secrets as map keys.", ["aliases[].protectionBypass@keys"], ["aliases[].protectionBypass"]),
224
+ "POST /v2/deployments/{id}/aliases": safe("Answers the alias assignment."),
225
+ "GET /v2/user": safe("ipBypass lists firewall IP rules, not secrets.", ["user.resourceConfig.security.ipBypass"]),
226
+ "PATCH /v3/domains/{domain}": redact("A move-out answers a transfer token; Connecta refuses move-out by body, and redacts any token.", ["token"]),
227
+ "GET /v3/events": redact("Audit events carry environment variables (with or without key or type) and credential metadata in payloads.", [
228
+ "events[].payload.newEnvVar.value",
229
+ "events[].payload.oldEnvVar.value",
230
+ "events[?env].payload.value",
231
+ "events[?env].payload.vsmValue",
232
+ "events[].payload.apiKey",
233
+ "events[].payload.credential",
234
+ "events[].payload.inviteCode",
235
+ "events[].payload.gitCredentialSource",
236
+ ]),
237
+ "POST /v3/user/tokens": refuse("mints a Vercel access token."),
238
+ "DELETE /v3/user/tokens/{tokenId}": safe(NO_SECRET_REASON),
239
+ "GET /v4/aliases": redact("Aliases carry protection-bypass secrets as map keys.", ["aliases[].protectionBypass@keys"], ["aliases[].protectionBypass"]),
240
+ "GET /v4/aliases/{idOrAlias}": redact("Aliases carry protection-bypass secrets as map keys.", ["protectionBypass@keys"], ["protectionBypass"]),
241
+ "GET /v5/user/tokens/{tokenId}": safe("Answers token metadata (`token` is an object of ids, prefix, suffix, and scopes).", ["token"]),
242
+ "GET /v6/user/tokens": safe("Lists token metadata, never token values.", ["tokens"]),
243
+ "GET /v9/projects/{idOrName}": redact(PROJECT_REASON, PROJECT, PROJECT_ALLOW),
244
+ "PATCH /v9/projects/{idOrName}": redact(PROJECT_REASON, PROJECT, PROJECT_ALLOW),
245
+ "PATCH /v9/projects/{idOrName}/env/{id}": redact(ENV_REASON, PROJECT_ENV),
246
+ "DELETE /v9/projects/{idOrName}/env/{id}": redact(ENV_REASON, PROJECT_ENV),
247
+ // Flagged by description or a transfer/claim/invite family (round 3).
248
+ "POST /projects/{idOrName}/transfer-request": refuse("returns a code that lets another team claim the project for 24 hours."),
249
+ "PUT /projects/transfer-request/{code}": safe("Accepts a transfer with a code the caller holds; answers transfer results."),
250
+ "GET /ai-gateway/virtual-model-configs": safe(MODEL_CONFIG),
251
+ "POST /ai-gateway/virtual-model-configs": safe(MODEL_CONFIG),
252
+ "PATCH /ai-gateway/virtual-model-configs": safe(MODEL_CONFIG),
253
+ "GET /ai-gateway/virtual-model-configs/list": safe(MODEL_CONFIG),
254
+ "GET /ai-gateway/virtual-model-configs/{vmcSlug}": safe(MODEL_CONFIG),
255
+ "PATCH /ai-gateway/virtual-model-configs/{vmcSlug}": safe(MODEL_CONFIG),
256
+ "GET /v1/ai-gateway/routers": safe(MODEL_CONFIG),
257
+ "POST /storage/stores/blob": safe("store.kind names the store type; no token is returned."),
258
+ "GET /storage/stores/{id}": safe("A store's metadata; projectsMetadata lists environment variable names, not values."),
259
+ "GET /v1/installations/{integrationConfigurationId}/resources/{resourceId}": safe("customClaims are OIDC claim configuration, not tokens."),
260
+ "GET /v1/integrations/configuration/{id}/products": redact("protocols.authentication describes supported auth flows, not credentials; a product's log and trace drain protocols carry destination headers.", [
261
+ "products[].protocols.logDrain.headers{}",
262
+ "products[].protocols.traceDrain.headers{}",
263
+ "origin:products[].protocols.logDrain.endpoint",
264
+ "origin:products[].protocols.traceDrain.endpoint",
265
+ ]),
266
+ "GET /v1/kms/issuers": safe(KMS_POLICY),
267
+ "POST /v1/kms/issuers": safe(KMS_POLICY),
268
+ "GET /v1/kms/issuers/{issuerId}": safe(KMS_POLICY),
269
+ "PATCH /v1/kms/issuers/{issuerId}": safe(KMS_POLICY),
270
+ "POST /v1/kms/issuers/{issuerId}/policies": safe(KMS_POLICY),
271
+ "PATCH /v1/kms/issuers/{issuerId}/policies/{kind}/{policyKey}": safe(KMS_POLICY),
272
+ "GET /v1/registrar/domains/{domain}/transfer": safe("Answers a transfer-in status only."),
273
+ "POST /v1/registrar/domains/{domain}/transfer": safe("Answers an order id; the auth code is request input only."),
274
+ "DELETE /v1/teams/{teamId}/invites/{inviteId}": safe(NO_SECRET_REASON),
275
+ "GET /v2/teams": redact(TEAM_INVITE, ["teams[].inviteCode"]),
276
+ "GET /v2/teams/{teamId}": redact(TEAM_INVITE, ["inviteCode"]),
277
+ "PATCH /v2/teams/{teamId}": redact(TEAM_INVITE, ["inviteCode"]),
278
+ "GET /v3/teams/{teamId}/members": safe("emailInviteCodes lists pending invitations (email, role, expiry) without codes.", ["emailInviteCodes"]),
279
+ "POST /v2/teams/{teamId}/members": safe("Answers the invited email, role, and permissions; no invite link or code."),
280
+ "POST /v7/domains": safe("Answers the added domain's metadata."),
281
+ "POST /v9/domains/{domain}/claim": safe("Claims by verified TXT record and answers domain metadata; issues nothing."),
282
+ };
283
+ /**
284
+ * Vercel's reviewed table. Environment-variable endpoints answer bodies that
285
+ * are environment variables whether or not each carries a `key`, so their
286
+ * values go whole; `permissions` maps list action scopes under resource
287
+ * names, which the heuristic leaves.
288
+ */
289
+ export const VERCEL_VALUE_SAFETY = {
290
+ title: "Vercel",
291
+ operations: VALUE_SAFETY,
292
+ envBodies: /^\/v\d+\/(?:env(?:\/|$)|projects\/\{[^}]+\}\/env(?:\/|$))/,
293
+ scopeMaps: ["permissions"],
294
+ fields: Object.fromEntries([
295
+ ["env", "Environment names and variable-name lists; values inside environment containers go structurally."],
296
+ ["environments", "Environment slugs or ids a policy or variable applies to."],
297
+ ["environmentVariables", "Environment variable names a store connects, never values."],
298
+ ["type", "A variable's or record's type (plain, encrypted, sensitive, secret)."],
299
+ ["visibility", "A variable's visibility setting."],
300
+ ["keys", "Rate-limit key kinds (ip, ja4) and changed variable names."],
301
+ ["logHeaders", "Header names a firewall rule logs."],
302
+ ["log_headers", "Header names the firewall logs."],
303
+ ["signingKeys", "KMS signing key metadata and public key material; private keys stay with Vercel."],
304
+ ["bindings", "Service-to-service binding names and targets."],
305
+ ["canaryResponseHeader", "Whether a canary response sets a header; a boolean."],
306
+ ["inferenceRegion", "An AI Gateway routing region."],
307
+ ["providerTimeouts", "Provider timeouts in milliseconds."],
308
+ ["redirectUri", "A connector's registered OAuth redirect URI, public by design."],
309
+ ["supportsRevocation", "Whether a connector can revoke tokens; a boolean."],
310
+ ["claims", "OIDC claim matchers a policy requires, not tokens."],
311
+ ["customClaims", "OIDC claim configuration, not tokens."],
312
+ ["limited", "Whether a record is limited by the token's privileges; a boolean."],
313
+ ["enforced", "Whether SAML is enforced; a boolean."],
314
+ ["kind", "A store or record kind."],
315
+ ["budget", "An AI Gateway key's spend budget."],
316
+ ["origin", "How a token was issued (manual, oauth)."],
317
+ ["scope", "A token's scope (user, team, project)."],
318
+ ["issuedBefore", "A revocation cutoff timestamp."],
319
+ ["issuerUrl", "The OIDC issuer URL, public by design."],
320
+ ["oidcSubject", "The OIDC subject claim, an identifier."],
321
+ ["next", "A pagination cursor; it grants nothing."],
322
+ ["partialKeyValue", "A masked SDK key preview."],
323
+ ["appUrlRegistrationSupport", "Whether a partner accepts app URLs; a boolean."],
324
+ ["disjunctiveProductionSecretPolicy", "A team policy setting."],
325
+ ["sensitiveEnvironmentVariablePolicy", "A team policy setting."],
326
+ ["strictPasswordProtectionSettings", "A team policy setting."],
327
+ ].map(([name, reason]) => [name, { verdict: "keep", reason }])),
328
+ };
@@ -0,0 +1,14 @@
1
+ import { type RemoteMcpAuth } from "../../connectors/remote-mcp.js";
2
+ import { type ProviderOptions } from "../../provider.js";
3
+ /** Wispr Flow's hosted Streamable HTTP endpoint, without a trailing slash. */
4
+ export declare const WISPR_FLOW_MCP_ENDPOINT = "https://api.wisprflow.ai/connect/mcp";
5
+ type WisprFlowOAuth = Extract<RemoteMcpAuth, {
6
+ type: "oauth";
7
+ }>;
8
+ export interface WisprFlowOptions extends ProviderOptions {
9
+ /** OAuth only; defaults to browser authorization with the advertised scopes. */
10
+ auth?: WisprFlowOAuth;
11
+ }
12
+ /** A maintained Wispr Flow Notetaker hosted-MCP connection. */
13
+ export declare const wisprFlow: import("../../provider.js").ProviderFactory<WisprFlowOptions>;
14
+ export {};
@@ -0,0 +1,117 @@
1
+ import { remoteMcp } from "../../connectors/remote-mcp.js";
2
+ import { keys, optionsOf, variants } from "../../config-schema.js";
3
+ import { defineProvider, PROVIDER_COMMON } from "../../provider.js";
4
+ import { skill } from "./skill.generated.js";
5
+ /** Wispr Flow's hosted Streamable HTTP endpoint, without a trailing slash. */
6
+ export const WISPR_FLOW_MCP_ENDPOINT = "https://api.wisprflow.ai/connect/mcp";
7
+ /**
8
+ * Reviewed public-source superset, 2026-10-13. Scalekit documents 14 reads;
9
+ * Claude's connector directory adds four task tools. Wispr's own help page
10
+ * describes read-only access, so the task writes remain classified even if
11
+ * this account never exposes them. No authenticated catalog or schemas were
12
+ * captured. Vendor names, schemas and results remain live.
13
+ */
14
+ const CLASSIFICATION = {
15
+ tools: {
16
+ get_account_info: { verdict: "read", reason: "Returns the signed-in identity and calendar display-name aliases." },
17
+ get_calendar_event: { verdict: "read", reason: "Retrieves an existing calendar event and its attendees." },
18
+ get_meeting: {
19
+ verdict: "read",
20
+ reason: "Retrieves recorded notes, the summary, attendees and requested transcript ranges.",
21
+ },
22
+ get_meeting_by_calendar_id: {
23
+ verdict: "read",
24
+ reason: "Finds the existing recording linked to a calendar event and reads its content.",
25
+ },
26
+ get_meeting_participants_enriched: {
27
+ verdict: "read",
28
+ reason: "Reads participant information and identifies the signed-in attendee.",
29
+ },
30
+ get_scratchpad_note: { verdict: "read", reason: "Returns a bounded range of an existing Scratchpad note." },
31
+ get_upcoming_meeting: {
32
+ verdict: "read",
33
+ reason: "Reads an upcoming event and an available pre-read without generating one.",
34
+ },
35
+ list_meeting_series: {
36
+ verdict: "read",
37
+ reason: "Lists recorded occurrences of an existing recurring meeting series.",
38
+ },
39
+ list_upcoming_meetings: {
40
+ verdict: "read",
41
+ reason: "Lists upcoming calendar events and already-available pre-reads.",
42
+ },
43
+ resolve_calendar_link: {
44
+ verdict: "read",
45
+ reason: "Matches a pasted calendar or conference URL to an existing event.",
46
+ },
47
+ resolve_share_link: {
48
+ verdict: "read",
49
+ reason: "Reads an accessible shared note without creating a link or changing sharing permissions.",
50
+ },
51
+ search_calendar_events: { verdict: "read", reason: "Finds existing calendar events by date, attendees or text." },
52
+ search_meetings: {
53
+ verdict: "read",
54
+ reason: "Finds recorded meetings by title, summary, notes, dates or linked attendees.",
55
+ },
56
+ search_scratchpad_notes: { verdict: "read", reason: "Finds existing Scratchpad notes by text." },
57
+ get_task: { verdict: "read", reason: "Retrieves an existing task without changing its state." },
58
+ search_tasks: { verdict: "read", reason: "Finds existing tasks without changing them." },
59
+ add_task: {
60
+ verdict: "write",
61
+ reason: "Creates a new task; the reviewed operation appends rather than replacing an existing task.",
62
+ },
63
+ update_task: { verdict: "destructive", reason: "Changes the state or fields of an existing task." },
64
+ },
65
+ };
66
+ const OPTIONS = optionsOf()({
67
+ ...PROVIDER_COMMON,
68
+ auth: variants("type", {
69
+ oauth: optionsOf()({
70
+ ...keys("type", "clientMetadataUrl", "scope"),
71
+ client: optionsOf()(keys("issuer", "clientId", "clientSecret", "tokenEndpointAuthMethod")),
72
+ }).shape,
73
+ }),
74
+ });
75
+ function accountSummary(purpose) {
76
+ const summary = `Account-wide Notetaker access: ${purpose}`;
77
+ const bytes = new TextEncoder().encode(summary);
78
+ if (bytes.byteLength <= 120)
79
+ return summary;
80
+ // Streaming decode drops an incomplete final code point; reserve three bytes for the ellipsis.
81
+ return `${new TextDecoder().decode(bytes.subarray(0, 117), { stream: true }).trimEnd()}…`;
82
+ }
83
+ /** A maintained Wispr Flow Notetaker hosted-MCP connection. */
84
+ export const wisprFlow = defineProvider({
85
+ name: "wispr-flow",
86
+ title: "Wispr Flow",
87
+ kind: "mcp",
88
+ readme: "Wispr Flow",
89
+ skill,
90
+ classify: CLASSIFICATION,
91
+ options: OPTIONS,
92
+ bundle: {
93
+ baselineGzip: 169009,
94
+ maxGzip: 229009,
95
+ note: "./providers/wispr-flow initial measured baseline is 169,009 B gzip for the hosted-MCP wrapper; the cap retains the existing baseline + 60,000 B provider headroom policy.",
96
+ },
97
+ create(id, options, provider) {
98
+ const ownership = options.authScope === "personal"
99
+ ? "Each person authorizes separately and may reach a different account."
100
+ : "One shared authorization selects the account for everyone using this connector.";
101
+ return remoteMcp(id, {
102
+ url: WISPR_FLOW_MCP_ENDPOINT,
103
+ ...provider.connectorOptions,
104
+ title: options.title ?? "Wispr Flow",
105
+ description: `Wispr Flow Notetaker meetings, Scratchpad notes, calendar events and tasks: ${options.purpose}`,
106
+ auth: { type: "oauth", scope: "openid offline_access", ...options.auth },
107
+ requireHttps: true,
108
+ classify: provider.classify,
109
+ usageGuide: provider.usageGuide({
110
+ context: [
111
+ `Account-wide Notetaker access: ${options.purpose}. Personal and work accounts hold separate content. ${ownership} Confirm the identity with get_account_info. Dictation history and Notetaker chat history are unavailable.`,
112
+ ],
113
+ summary: accountSummary(options.purpose),
114
+ }),
115
+ });
116
+ },
117
+ });
@@ -0,0 +1,8 @@
1
+ // Generated by scripts/generate-providers.mjs; do not edit.
2
+ export const skill = {
3
+ "content": "- This connection reaches Notetaker meetings, summaries, notes, transcripts, participants and their email addresses, and tasks; Scratchpad notes; calendar events and linked recordings; upcoming meetings with available pre-reads; recurring series; accessible shared notes; and account identity. Meeting results name folders you own or that are shared with you; meetings outside folders list none. Another account needs another authorization. Use `get_account_info` to check which identity this grant reaches.\n- OAuth browser authorization is the only supported auth method. There is no API key. Prefer Google, Apple, Microsoft or enterprise SSO and finish sign-in within about five minutes. Email-and-password sign-in may not complete authorization; Wispr's help pages disagree. Consent covers the account's existing content, without per-meeting consent.\n- Search first, then retrieve by the returned id: `search_meetings` → `get_meeting`, `search_scratchpad_notes` → `get_scratchpad_note`, or `search_calendar_events` → `get_calendar_event`. Resolve pasted URLs with `resolve_calendar_link` or `resolve_share_link` instead of guessing ids. Use `get_meeting_by_calendar_id` for the recording linked to an event; an event without a recording is not missing meeting content. `get_upcoming_meeting` is for future events. For `list_meeting_series`, \"no earlier occurrence\" can mean a one-off meeting or a recording without a linked calendar event, not a failure.\n- `search_meetings` matches titles, summaries and notes, not transcripts. Set `field` to `title`, `content` or `both`; use `query`, `since` and `until` to narrow the request. `attendee_emails` with `attendee_match` set to `any` or `all` only finds recordings linked to calendar events. A missing attendee-filter match does not prove a person never attended. Results include an excerpt and up to five attendees; retrieve the meeting for full content and attendees.\n- List and search tools page with `cursor` and `limit`, normally 25 items and at most 200. Pass the returned `next_cursor` verbatim while `has_more` is true. `list_upcoming_meetings` allows at most 50 items and `window_hours` defaults to 24 with a maximum of 168. A `truncated` result needs a narrower query, even after paging. Reduce results inside `execute_code` before returning them.\n- Notes and transcripts are character-bounded. Read the continuation marker and repeat with `view_content.start_char` or `view_transcript.start_char` set to its exact offset. Request `view_transcript: {}` when source detail matters, then continue through the required ranges. Enterprise note and transcript reads are recorded in audit logs; do not read transcripts speculatively. Summaries are complete. Transcript retention deletes transcripts and audio, not summaries; an unavailable transcript may have expired.\n- Shared-note links require your own meeting-access permissions. If a link copied from email is rejected, ask the user to open it in a browser and pass the final Wispr Flow URL. Transcripts require ownership or an explicit email invitation plus the owner's organization allowing transcript sharing. Domain, team or anyone-with-link access yields summaries only. Reauthorization cannot widen those permissions. Return the provided `share_link` when asked for a link; never fabricate one or assume the link changes who can read it.\n- Timestamps are UTC. Present them in the user's time zone. For a whole local day, supply `since` at local midnight with its UTC offset and `until` at the next local midnight; the upper bound is exclusive. Upcoming pre-reads may be absent because the desktop app generates them on demand; these reads do not generate them.\n- Both official help articles describe read-only access and list tasks or action items as readable, while Wispr's own Claude directory listing advertises \"Create, update, and search action items\" and names `add_task` and `update_task`. Discover this account's actual tools before using them. If exposed, `get_task` and `search_tasks` read, `add_task` creates a new task and is a write, and `update_task` changes existing task state and is destructive. Unknown tools without an explicit, uncontradicted read annotation fail closed.\n- Mac and Windows accounts need Notetaker and MCP access. Personal or organization HIPAA BAA coverage removes all MCP tools. Enterprise administrators can disable MCP or require an approved IP network; Scratchpad and cloud-access policies may further restrict results. An empty catalog or policy denial requires checking account access, not repeated authorization.\n- For \"Too many requests\" or repeated searches that temporarily fail without explanation, wait about one minute before retrying and avoid speculative fan-out. There is no maintained per-runtime admission default; the operator may configure `callAdmission` for this deployment.\n- An `auth_required` failure means this connector's Wispr Flow authorization is missing or expired: run `authorize_connector` for this connector id, then retry the same call unchanged. For guest calls, classification and routing, fetch `skills({ name: \"usage\" })`.\n",
4
+ "instructionsHeading": "Account instructions",
5
+ "fragments": {
6
+ "content": "- This connection reaches Notetaker meetings, summaries, notes, transcripts, participants and their email addresses, and tasks; Scratchpad notes; calendar events and linked recordings; upcoming meetings with available pre-reads; recurring series; accessible shared notes; and account identity. Meeting results name folders you own or that are shared with you; meetings outside folders list none. Another account needs another authorization. Use `get_account_info` to check which identity this grant reaches.\n- OAuth browser authorization is the only supported auth method. There is no API key. Prefer Google, Apple, Microsoft or enterprise SSO and finish sign-in within about five minutes. Email-and-password sign-in may not complete authorization; Wispr's help pages disagree. Consent covers the account's existing content, without per-meeting consent.\n- Search first, then retrieve by the returned id: `search_meetings` → `get_meeting`, `search_scratchpad_notes` → `get_scratchpad_note`, or `search_calendar_events` → `get_calendar_event`. Resolve pasted URLs with `resolve_calendar_link` or `resolve_share_link` instead of guessing ids. Use `get_meeting_by_calendar_id` for the recording linked to an event; an event without a recording is not missing meeting content. `get_upcoming_meeting` is for future events. For `list_meeting_series`, \"no earlier occurrence\" can mean a one-off meeting or a recording without a linked calendar event, not a failure.\n- `search_meetings` matches titles, summaries and notes, not transcripts. Set `field` to `title`, `content` or `both`; use `query`, `since` and `until` to narrow the request. `attendee_emails` with `attendee_match` set to `any` or `all` only finds recordings linked to calendar events. A missing attendee-filter match does not prove a person never attended. Results include an excerpt and up to five attendees; retrieve the meeting for full content and attendees.\n- List and search tools page with `cursor` and `limit`, normally 25 items and at most 200. Pass the returned `next_cursor` verbatim while `has_more` is true. `list_upcoming_meetings` allows at most 50 items and `window_hours` defaults to 24 with a maximum of 168. A `truncated` result needs a narrower query, even after paging. Reduce results inside `execute_code` before returning them.\n- Notes and transcripts are character-bounded. Read the continuation marker and repeat with `view_content.start_char` or `view_transcript.start_char` set to its exact offset. Request `view_transcript: {}` when source detail matters, then continue through the required ranges. Enterprise note and transcript reads are recorded in audit logs; do not read transcripts speculatively. Summaries are complete. Transcript retention deletes transcripts and audio, not summaries; an unavailable transcript may have expired.\n- Shared-note links require your own meeting-access permissions. If a link copied from email is rejected, ask the user to open it in a browser and pass the final Wispr Flow URL. Transcripts require ownership or an explicit email invitation plus the owner's organization allowing transcript sharing. Domain, team or anyone-with-link access yields summaries only. Reauthorization cannot widen those permissions. Return the provided `share_link` when asked for a link; never fabricate one or assume the link changes who can read it.\n- Timestamps are UTC. Present them in the user's time zone. For a whole local day, supply `since` at local midnight with its UTC offset and `until` at the next local midnight; the upper bound is exclusive. Upcoming pre-reads may be absent because the desktop app generates them on demand; these reads do not generate them.\n- Both official help articles describe read-only access and list tasks or action items as readable, while Wispr's own Claude directory listing advertises \"Create, update, and search action items\" and names `add_task` and `update_task`. Discover this account's actual tools before using them. If exposed, `get_task` and `search_tasks` read, `add_task` creates a new task and is a write, and `update_task` changes existing task state and is destructive. Unknown tools without an explicit, uncontradicted read annotation fail closed.\n- Mac and Windows accounts need Notetaker and MCP access. Personal or organization HIPAA BAA coverage removes all MCP tools. Enterprise administrators can disable MCP or require an approved IP network; Scratchpad and cloud-access policies may further restrict results. An empty catalog or policy denial requires checking account access, not repeated authorization.\n- For \"Too many requests\" or repeated searches that temporarily fail without explanation, wait about one minute before retrying and avoid speculative fan-out. There is no maintained per-runtime admission default; the operator may configure `callAdmission` for this deployment.\n- An `auth_required` failure means this connector's Wispr Flow authorization is missing or expired: run `authorize_connector` for this connector id, then retry the same call unchanged. For guest calls, classification and routing, fetch `skills({ name: \"usage\" })`.\n"
7
+ }
8
+ };