@redocly/revel 0.138.0-next.7 → 0.138.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 (275) hide show
  1. package/CHANGELOG.md +288 -0
  2. package/dist/api-schemas/mcp-fetch-count.d.ts +24 -0
  3. package/dist/api-schemas/mcp-fetch-count.js +1 -0
  4. package/dist/bin.js +0 -0
  5. package/dist/cli/build/analytics/collect-analytics.js +1 -1
  6. package/dist/cli/build/analytics/collectors/get-processed-config-stats-usage.js +1 -1
  7. package/dist/cli/build/index.js +1 -1
  8. package/dist/cli/develop.js +1 -1
  9. package/dist/cli/eject/ejectTheme.js +1 -1
  10. package/dist/cli/translations/collect-config-translations.js +1 -1
  11. package/dist/client/App.js +1 -1
  12. package/dist/client/app/ReviewComments/ReviewComments.js +2 -2
  13. package/dist/client/app/l10n/hooks/useTranslationKeys.js +1 -1
  14. package/dist/client/app/markdoc/custom-components/openapi/openapi-example.js +1 -1
  15. package/dist/client/app/markdoc/custom-components/openapi/openapi-response-sample.js +1 -1
  16. package/dist/client/app/markdoc/custom-components/openapi/replay-openapi.js +1 -1
  17. package/dist/client/app/markdoc/hooks/use-store.d.ts +2 -3
  18. package/dist/client/app/pages/Invite/Invite.js +1 -1
  19. package/dist/client/app/search/useSearch.js +1 -1
  20. package/dist/client/app/utils/loadAndNavigate.js +1 -1
  21. package/dist/client/polyfills.js +1 -1
  22. package/dist/client/runtime/loader.js +1 -1
  23. package/dist/client/utils/catalog/parse-filter-query.js +1 -1
  24. package/dist/compiled/svgo/svgo-node.js +39 -39
  25. package/dist/constants/api.d.ts +1 -0
  26. package/dist/constants/api.js +1 -1
  27. package/dist/constants/common.d.ts +6 -0
  28. package/dist/constants/common.js +1 -1
  29. package/dist/constants/l10n/langs/ar.js +1 -1
  30. package/dist/constants/l10n/langs/de.js +1 -1
  31. package/dist/constants/l10n/langs/en.js +1 -1
  32. package/dist/constants/l10n/langs/es.js +1 -1
  33. package/dist/constants/l10n/langs/fr.js +1 -1
  34. package/dist/constants/l10n/langs/hi.js +1 -1
  35. package/dist/constants/l10n/langs/it.js +1 -1
  36. package/dist/constants/l10n/langs/ja.js +1 -1
  37. package/dist/constants/l10n/langs/ko.js +1 -1
  38. package/dist/constants/l10n/langs/pl.js +1 -1
  39. package/dist/constants/l10n/langs/pt-BR.js +1 -1
  40. package/dist/constants/l10n/langs/pt.js +1 -1
  41. package/dist/constants/l10n/langs/ru.js +1 -1
  42. package/dist/constants/l10n/langs/uk.js +1 -1
  43. package/dist/constants/l10n/langs/zh.js +1 -1
  44. package/dist/markdoc/helpers/get-inner-text.js +2 -2
  45. package/dist/markdoc/helpers/guards/is-prompt-node.d.ts +13 -0
  46. package/dist/markdoc/helpers/guards/is-prompt-node.js +1 -0
  47. package/dist/server/config/env-config.d.ts +0 -4
  48. package/dist/server/config/env-schema.d.ts +0 -10
  49. package/dist/server/config/env-schemas/feature-flags.d.ts +0 -8
  50. package/dist/server/config/env-schemas/feature-flags.js +1 -1
  51. package/dist/server/constants/entitlements.js +1 -1
  52. package/dist/server/entitlements/entitlements-provider.js +1 -1
  53. package/dist/server/esbuild/esbuild.js +3 -3
  54. package/dist/server/esbuild/plugins/on-rebuild.js +1 -1
  55. package/dist/server/esbuild/plugins/styled-components-ssr.js +1 -1
  56. package/dist/server/esbuild/plugins/yaml-loader.js +1 -1
  57. package/dist/server/fs/content-fs.js +1 -1
  58. package/dist/server/fs/utils/is-l10n-root-file.d.ts +3 -0
  59. package/dist/server/fs/utils/is-l10n-root-file.js +1 -0
  60. package/dist/server/plugins/analytics/amplitude/index.js +1 -1
  61. package/dist/server/plugins/analytics/heap/browser-hooks.js +1 -1
  62. package/dist/server/plugins/api-docs/index.js +1 -1
  63. package/dist/server/plugins/api-docs/loader.js +3 -3
  64. package/dist/server/plugins/api-docs/spec-download.api.js +1 -1
  65. package/dist/server/plugins/api-docs/utils/apply-nav-item-to-root.js +1 -1
  66. package/dist/server/plugins/api-docs/utils/decorators.d.ts +11 -0
  67. package/dist/server/plugins/api-docs/utils/decorators.js +1 -1
  68. package/dist/server/plugins/arazzo-docs/arazzo-doc-loader.js +2 -2
  69. package/dist/server/plugins/catalog-entities/extensions/extractors/api-description/openapi-entities-extractor.js +1 -1
  70. package/dist/server/plugins/catalog-entities/utils/extract-parts-from-channels-message-ref.js +1 -1
  71. package/dist/server/plugins/config-parser/format-custom-tags.js +1 -1
  72. package/dist/server/plugins/config-parser/loaders/content-slugs-loader.js +1 -1
  73. package/dist/server/plugins/config-parser/loaders/versions-config-loader.js +1 -1
  74. package/dist/server/plugins/config-parser/resolve-mutual-exclusion.js +1 -1
  75. package/dist/server/plugins/config-parser/safe-parse.js +1 -1
  76. package/dist/server/plugins/default-theme/resolve-logo.js +1 -1
  77. package/dist/server/plugins/dev-onboarding/api/adapters/apigee/adapter.js +2 -2
  78. package/dist/server/plugins/dev-onboarding/api/adapters/gravitee/client.js +2 -2
  79. package/dist/server/plugins/dev-onboarding/api/combine.js +1 -1
  80. package/dist/server/plugins/dev-onboarding/template/components/ApiKeyOverview.js +3 -3
  81. package/dist/server/plugins/dev-onboarding/template/components/AppOverview.js +1 -1
  82. package/dist/server/plugins/dev-onboarding/template/components/useSortableData.js +1 -1
  83. package/dist/server/plugins/markdown/attribute-resolvers/code-walkthrough/file-chunk-parser.d.ts +1 -1
  84. package/dist/server/plugins/markdown/attribute-resolvers/index.js +1 -1
  85. package/dist/server/plugins/markdown/attribute-resolvers/resolve-link.js +1 -1
  86. package/dist/server/plugins/markdown/attribute-resolvers/resolve-svg-content.js +1 -1
  87. package/dist/server/plugins/markdown/attribute-resolvers/resolve-tag-raw-content.d.ts +4 -0
  88. package/dist/server/plugins/markdown/attribute-resolvers/resolve-tag-raw-content.js +2 -0
  89. package/dist/server/plugins/markdown/compiler.js +1 -1
  90. package/dist/server/plugins/markdown/create-source-lines-reader.d.ts +7 -0
  91. package/dist/server/plugins/markdown/create-source-lines-reader.js +1 -0
  92. package/dist/server/plugins/markdown/html/normalize-html-headings.d.ts +9 -0
  93. package/dist/server/plugins/markdown/html/normalize-html-headings.js +1 -0
  94. package/dist/server/plugins/markdown/markdoc/markdoc-options.js +1 -1
  95. package/dist/server/plugins/markdown/markdoc/plugins/utils.d.ts +1 -1
  96. package/dist/server/plugins/markdown/markdoc/plugins/utils.js +1 -1
  97. package/dist/server/plugins/markdown/markdown-static-data-loader.js +1 -1
  98. package/dist/server/plugins/markdown/search/get-ai-search-documents.js +2 -2
  99. package/dist/server/plugins/markdown/search/search-resolver.js +1 -1
  100. package/dist/server/plugins/markdown/search/to-markdown.js +13 -13
  101. package/dist/server/plugins/markdown/search/walk-sections.d.ts +3 -1
  102. package/dist/server/plugins/markdown/search/walk-sections.js +3 -3
  103. package/dist/server/plugins/markdown/utils/sanitize-malformed-md-content.js +2 -2
  104. package/dist/server/plugins/mcp/audit/audit-log.d.ts +1 -1
  105. package/dist/server/plugins/mcp/audit/audit-log.js +1 -1
  106. package/dist/server/plugins/mcp/audit/code-credentials.d.ts +9 -0
  107. package/dist/server/plugins/mcp/audit/code-credentials.js +2 -0
  108. package/dist/server/plugins/mcp/codemode/capabilities/fetch/index.js +5 -5
  109. package/dist/server/plugins/mcp/codemode/capabilities/types.d.ts +3 -1
  110. package/dist/server/plugins/mcp/codemode/capabilities/utils.d.ts +2 -1
  111. package/dist/server/plugins/mcp/codemode/capabilities/utils.js +3 -3
  112. package/dist/server/plugins/mcp/codemode/code-audit.d.ts +4 -3
  113. package/dist/server/plugins/mcp/codemode/code-execution-audit.js +1 -1
  114. package/dist/server/plugins/mcp/custom-tools/scan-custom-tools.js +1 -1
  115. package/dist/server/plugins/mcp/docs-mcp/tool-schemas.js +1 -1
  116. package/dist/server/plugins/mcp/docs-mcp/tools/core/utils.js +1 -1
  117. package/dist/server/plugins/mcp/docs-mcp/tools/core/whoami.js +1 -1
  118. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/list-apis.js +1 -1
  119. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/utils.d.ts +12 -3
  120. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/utils.js +1 -1
  121. package/dist/server/plugins/mcp/docs-mcp/utils/resolve-refs.d.ts +2 -2
  122. package/dist/server/plugins/mcp/docs-mcp/utils/resolve-refs.js +1 -1
  123. package/dist/server/plugins/mcp/gateway-mcp/allowed-http-methods.d.ts +11 -0
  124. package/dist/server/plugins/mcp/gateway-mcp/allowed-http-methods.js +1 -0
  125. package/dist/server/plugins/mcp/gateway-mcp/api-call-operations.d.ts +32 -0
  126. package/dist/server/plugins/mcp/gateway-mcp/api-call-operations.js +1 -0
  127. package/dist/server/plugins/mcp/gateway-mcp/callable-apis.d.ts +7 -4
  128. package/dist/server/plugins/mcp/gateway-mcp/callable-apis.js +1 -1
  129. package/dist/server/plugins/mcp/gateway-mcp/caller-api-calls.d.ts +9 -0
  130. package/dist/server/plugins/mcp/gateway-mcp/caller-api-calls.js +1 -0
  131. package/dist/server/plugins/mcp/gateway-mcp/credential-validation.js +1 -1
  132. package/dist/server/plugins/mcp/gateway-mcp/elicitation.d.ts +1 -0
  133. package/dist/server/plugins/mcp/gateway-mcp/elicitation.js +5 -5
  134. package/dist/server/plugins/mcp/gateway-mcp/eligibility.d.ts +8 -4
  135. package/dist/server/plugins/mcp/gateway-mcp/eligibility.js +1 -1
  136. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-audit.d.ts +2 -1
  137. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-audit.js +1 -1
  138. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-bridge.d.ts +11 -1
  139. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-bridge.js +1 -1
  140. package/dist/server/plugins/mcp/gateway-mcp/fetch/redact.d.ts +11 -0
  141. package/dist/server/plugins/mcp/gateway-mcp/fetch/redact.js +1 -1
  142. package/dist/server/plugins/mcp/gateway-mcp/fetch-capability.js +1 -1
  143. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-count-client.d.ts +4 -0
  144. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-count-client.js +1 -0
  145. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-count-secret.d.ts +4 -0
  146. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-count-secret.js +1 -0
  147. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-count.d.ts +11 -0
  148. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-count.js +1 -0
  149. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-counter-repository.d.ts +13 -0
  150. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-counter-repository.js +13 -0
  151. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/{limits.d.ts → fetch-limiter/fetch-limits.d.ts} +1 -1
  152. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-limits.js +1 -0
  153. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/{apply-edge-rate-limit.d.ts → request-limiter/apply-edge-rate-limit.d.ts} +1 -1
  154. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/request-limiter/apply-edge-rate-limit.js +1 -0
  155. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/{edge-limiter.d.ts → request-limiter/edge-limiter.d.ts} +3 -3
  156. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/request-limiter/edge-limiter.js +1 -0
  157. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/request-limiter/rate-limit-telemetry.js +1 -0
  158. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/request-limiter/request-limits.d.ts +6 -0
  159. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/request-limiter/request-limits.js +1 -0
  160. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/utils/ip-key.js +1 -0
  161. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/{window-counter.d.ts → utils/window-counter.d.ts} +1 -1
  162. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/utils/window-counter.js +1 -0
  163. package/dist/server/plugins/mcp/gateway-mcp/route-pattern.d.ts +18 -0
  164. package/dist/server/plugins/mcp/gateway-mcp/route-pattern.js +1 -0
  165. package/dist/server/plugins/mcp/gateway-mcp/secret-store.d.ts +9 -3
  166. package/dist/server/plugins/mcp/gateway-mcp/secret-store.js +1 -1
  167. package/dist/server/plugins/mcp/gateway-mcp/subject.js +1 -1
  168. package/dist/server/plugins/mcp/gateway-mcp/upstream-auth-scheme.d.ts +2 -0
  169. package/dist/server/plugins/mcp/gateway-mcp/upstream-auth-scheme.js +2 -2
  170. package/dist/server/plugins/mcp/gateway-mcp/upstream-auth.d.ts +0 -2
  171. package/dist/server/plugins/mcp/gateway-mcp/upstream-auth.js +1 -1
  172. package/dist/server/plugins/mcp/handlers/credentials-page/constants.d.ts +7 -0
  173. package/dist/server/plugins/mcp/handlers/credentials-page/constants.js +1 -0
  174. package/dist/server/plugins/mcp/handlers/credentials-page/form-contract.d.ts +5 -0
  175. package/dist/server/plugins/mcp/handlers/credentials-page/form-contract.js +1 -0
  176. package/dist/server/plugins/mcp/handlers/credentials-page/form-page.d.ts +14 -0
  177. package/dist/server/plugins/mcp/handlers/credentials-page/form-page.js +62 -0
  178. package/dist/server/plugins/mcp/handlers/credentials-page/layout.d.ts +17 -0
  179. package/dist/server/plugins/mcp/handlers/credentials-page/layout.js +11 -0
  180. package/dist/server/plugins/mcp/handlers/credentials-page/outcome-pages.d.ts +10 -0
  181. package/dist/server/plugins/mcp/handlers/credentials-page/outcome-pages.js +11 -0
  182. package/dist/server/plugins/mcp/handlers/credentials-page/site-branding.d.ts +10 -0
  183. package/dist/server/plugins/mcp/handlers/credentials-page/site-branding.js +1 -0
  184. package/dist/server/plugins/mcp/handlers/credentials-page/styles.d.ts +4 -0
  185. package/dist/server/plugins/mcp/handlers/credentials-page/styles.js +186 -0
  186. package/dist/server/plugins/mcp/handlers/credentials-page/types.d.ts +32 -0
  187. package/dist/server/plugins/mcp/handlers/credentials-page/types.js +0 -0
  188. package/dist/server/plugins/mcp/handlers/docs-mcp-handler.js +1 -1
  189. package/dist/server/plugins/mcp/handlers/handle-mcp-request.js +1 -1
  190. package/dist/server/plugins/mcp/handlers/mcp-credentials-handler.d.ts +0 -1
  191. package/dist/server/plugins/mcp/handlers/mcp-credentials-handler.js +1 -1
  192. package/dist/server/plugins/mcp/index.js +1 -1
  193. package/dist/server/plugins/mcp/servers/docs-server.js +1 -1
  194. package/dist/server/plugins/mcp/servers/mcp-server.js +2 -2
  195. package/dist/server/plugins/mcp/types.d.ts +16 -1
  196. package/dist/server/plugins/mcp/utils/mode.d.ts +4 -1
  197. package/dist/server/plugins/mcp/utils/mode.js +1 -1
  198. package/dist/server/plugins/mcp/utils/xmcp-utils.d.ts +3 -5
  199. package/dist/server/plugins/mcp/utils/xmcp-utils.js +1 -1
  200. package/dist/server/plugins/nav-utils.js +1 -1
  201. package/dist/server/plugins/scorecard-classic/loaders/scorecard.js +1 -1
  202. package/dist/server/plugins/scorecard-classic/template/components.js +1 -1
  203. package/dist/server/plugins/scorecard-classic/template/useData.js +1 -1
  204. package/dist/server/plugins/scorecard-classic/template/views.js +1 -1
  205. package/dist/server/plugins/search/engines/flexsearch/index.js +2 -2
  206. package/dist/server/plugins/sidebars/index.js +2 -2
  207. package/dist/server/plugins/sidebars/tabs.js +1 -1
  208. package/dist/server/plugins/sidebars/utils.js +1 -1
  209. package/dist/server/providers/database/client.d.ts +5 -0
  210. package/dist/server/providers/database/client.js +1 -1
  211. package/dist/server/providers/database/copy-migrations.js +1 -1
  212. package/dist/server/providers/database/database-connections-manager.js +1 -1
  213. package/dist/server/providers/database/has-project-database.d.ts +3 -0
  214. package/dist/server/providers/database/has-project-database.js +1 -0
  215. package/dist/server/store.d.ts +2 -0
  216. package/dist/server/store.js +1 -1
  217. package/dist/server/tools/notifiers/formatter.js +1 -1
  218. package/dist/server/tools/notifiers/helpers/privacy/mask-email.js +1 -1
  219. package/dist/server/tools/notifiers/logger.js +1 -1
  220. package/dist/server/types/plugins/markdown.d.ts +2 -0
  221. package/dist/server/types/web-server.d.ts +1 -0
  222. package/dist/server/utils/codeframes/codeframes.js +2 -2
  223. package/dist/server/utils/entitlements/read-entitlement-number.d.ts +3 -0
  224. package/dist/server/utils/entitlements/read-entitlement-number.js +1 -0
  225. package/dist/server/utils/fs.js +1 -1
  226. package/dist/server/utils/is-api-download-link.d.ts +1 -0
  227. package/dist/server/utils/is-api-download-link.js +1 -1
  228. package/dist/server/utils/rbac.js +1 -1
  229. package/dist/server/utils/redact-url.d.ts +3 -2
  230. package/dist/server/utils/redact-url.js +1 -1
  231. package/dist/server/utils/sandbox-context.d.ts +7 -4
  232. package/dist/server/utils/sandbox-context.js +1 -1
  233. package/dist/server/web-server/auth.d.ts +0 -1
  234. package/dist/server/web-server/auth.js +4 -4
  235. package/dist/server/web-server/dev-server.js +1 -1
  236. package/dist/server/web-server/http.d.ts +2 -1
  237. package/dist/server/web-server/http.js +2 -2
  238. package/dist/server/web-server/jwt/token-types.d.ts +1 -0
  239. package/dist/server/web-server/jwt/token-types.js +1 -1
  240. package/dist/server/web-server/middleware/catalogAuthMiddleware.js +1 -1
  241. package/dist/server/web-server/middleware/dynamic-middleware/hono-compose.js +1 -1
  242. package/dist/server/web-server/middleware/responseHeadersMiddleware.js +1 -1
  243. package/dist/server/web-server/routes/auth.d.ts +1 -1
  244. package/dist/server/web-server/routes/auth.js +1 -1
  245. package/dist/server/web-server/routes/catalog/helpers/create-entity-schema.d.ts +1756 -1756
  246. package/dist/server/web-server/routes/catalog/helpers/create-entity-update-schema.d.ts +295 -295
  247. package/dist/server/web-server/routes/cors-proxy.js +2 -2
  248. package/dist/server/web-server/routes/index.js +1 -1
  249. package/dist/server/web-server/routes/mcp-routes/mcp-consent-page.d.ts +24 -0
  250. package/dist/server/web-server/routes/mcp-routes/mcp-consent-page.js +35 -0
  251. package/dist/server/web-server/routes/mcp-routes/mcp-fetch-count.d.ts +8 -0
  252. package/dist/server/web-server/routes/mcp-routes/mcp-fetch-count.js +1 -0
  253. package/dist/server/web-server/routes/mcp-routes/mcp-oauth-store.d.ts +15 -0
  254. package/dist/server/web-server/routes/mcp-routes/mcp-oauth-store.js +1 -0
  255. package/dist/server/web-server/routes/mcp-routes/mcp-oauth.d.ts +3 -1
  256. package/dist/server/web-server/routes/mcp-routes/mcp-oauth.js +1 -1
  257. package/dist/server/web-server/routes/mcp-routes/mcp-routes.js +1 -1
  258. package/dist/server/web-server/routes/mcp-routes/mcp-server-card.js +1 -1
  259. package/dist/server/web-server/routes/resolve-route.js +1 -1
  260. package/dist/server/workers/worker-pool.js +1 -1
  261. package/dist/types/entitlements.d.ts +2 -0
  262. package/dist/utils/time/debounce.js +1 -1
  263. package/package.json +10 -11
  264. package/dist/client/app/utils/withLoadProgress.d.ts +0 -2
  265. package/dist/client/app/utils/withLoadProgress.js +0 -1
  266. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/apply-edge-rate-limit.js +0 -1
  267. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/edge-limiter.js +0 -1
  268. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/ip-key.js +0 -1
  269. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/limits.js +0 -1
  270. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/rate-limit-telemetry.js +0 -1
  271. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/window-counter.js +0 -1
  272. package/dist/server/plugins/mcp/handlers/mcp-credentials-page.d.ts +0 -12
  273. package/dist/server/plugins/mcp/handlers/mcp-credentials-page.js +0 -225
  274. /package/dist/server/plugins/mcp/gateway-mcp/rate-limit/{rate-limit-telemetry.d.ts → request-limiter/rate-limit-telemetry.d.ts} +0 -0
  275. /package/dist/server/plugins/mcp/gateway-mcp/rate-limit/{ip-key.d.ts → utils/ip-key.d.ts} +0 -0
@@ -1 +1 @@
1
- import{Agent as Q}from"undici";import{envConfig as Z}from"../../../../config/env-config.js";import{logger as N}from"../../../../tools/notifiers/logger.js";import{assertUrlAllowed as I}from"./ssrf-guard.js";import{createSsrfSafeLookup as q}from"../../../../web-server/utils/ssrf-safe-lookup.js";import{SsrfBlockedError as W}from"../../../../web-server/utils/ssrf-blocklist.js";import{GatewayFetchBlockedError as R,GatewayFetchSystemError as ee,GatewayFetchUserError as g}from"./fetch-errors.js";import{redactHeaders as J,redactSecretsFromString as te}from"./redact.js";import{extractUpstreamDetail as re}from"./upstream-detail.js";import{normalizeFetchPath as ne}from"./normalize-path.js";import{reportGatewayFetch as K}from"./fetch-telemetry.js";import{logGatewayFetchAudit as V}from"./fetch-audit.js";const X="x-forwarded-for",oe=3e4,se=1024*1024,ae=new Set([401,403,407]);let C;function ce(){if(!C){const e=Z.isDevelopMode;e&&N.warn("Gateway MCP SSRF guard: loopback upstreams are ALLOWED (develop mode only, never in runtime)."),C=new Q({connect:{lookup:q({allowLoopback:e})}})}return C}function _e({allowedHosts:e,apis:t=[],authorization:r,onAuthRequired:n,fetchImpl:c=fetch,dispatcher:a=ce(),timeoutMs:h=oe,maxResponseBytes:l=se,maxFetchesPerExec:w,clientIp:k}){const f=[...e,...t.flatMap(o=>o.matchers)],$=[...new Set(t.map(o=>o.name).filter(Boolean))].join(", "),m=t.flatMap(o=>Object.values(o.secrets??{})),D=[r,...m.map(o=>o.value)].filter(o=>!!o),x=m.map(o=>o.header);let O=0;return async o=>{const y=o.method??"GET",T=Date.now();let B,b,A,E={},H,_,j;try{if(w!==void 0){const u=Math.max(0,w);if(O+=1,O>u)throw new g(`Outbound fetch budget exceeded (max ${u} fetches per execute); split the work across calls`)}const d=le(o.url,f,$);B=d.hostname,b=ne(d.pathname);const v=t.filter(u=>u.matchers.some(z=>z.test(d.hostname))),s=v.length===1?v[0]:void 0;A=s?.slug;const S=pe(o.securityScheme,s),F=S?[S]:s?ye(s,d.pathname,y):void 0,L=s&&F?Se(s,F):void 0,P=L?s?.secrets?.[L]:void 0,M=be({callerHeaders:o.headers,secret:P,authorization:r,clientIp:k,credentialHeaders:s?.credentialHeaders});E=M.headers,H=P&&!M.callerHeader?L:void 0;const G=new AbortController;j=setTimeout(()=>G.abort(),h);const i=await c(d,{method:y,headers:E,body:o.body,redirect:"manual",signal:G.signal,dispatcher:a});_=i.status;let U="";try{U=await we(i,l)}finally{if(ae.has(i.status)&&s&&n){const u=re(U,i.headers.get("content-type"),D);n({api:s.slug,name:s.name,status:i.status,...L?{scheme:L}:{},...F?.length?{schemes:[...F]}:{},...H?{sentScheme:H}:{},...M.callerHeader?{callerHeader:M.callerHeader}:{},...u?{detail:u}:{}})}}let p=Ae(i.headers);if(fe(i.status)){p=J(p,x);const{location:u}=p;u&&!he(u,d,f)&&delete p.location}return K({host:d.hostname,method:y,outcome:"success",durationMs:Date.now()-T,path:b,api:A,status:i.status,bytes:Buffer.byteLength(U,"utf8")}),V({rawUrl:d.href,method:y,host:d.hostname,outcome:"success",durationMs:Date.now()-T,injectedSecretValues:D,path:b,api:A,status:i.status,injectedScheme:H,requestHeaders:E,requestBody:o.body,responseHeaders:p,responseBody:U}),{status:i.status,statusText:i.statusText,ok:i.ok,headers:p,bodyText:U}}catch(d){const{outcome:v,userSafe:s}=ie(d),S=B??(s instanceof R?s.host:void 0)??ue(o.url)??"invalid-url";throw K({host:S,method:y,outcome:v,durationMs:Date.now()-T,path:b,api:A,status:_}),V({rawUrl:o.url,method:y,host:S,outcome:v,durationMs:Date.now()-T,injectedSecretValues:D,path:b,api:A,status:_,injectedScheme:H,requestHeaders:E,requestBody:o.body}),s||(N.error(`Gateway MCP fetch failed [${y} ${S}]: ${te(me(d),D)} headers=${JSON.stringify(J(E,x))}`),new ee("Upstream fetch failed due to an internal error"))}finally{j!==void 0&&clearTimeout(j)}}}function de(e){let t=e;for(let r=0;r<5&&t instanceof Error;r++){if(t instanceof g||t instanceof W)return t;t=t.cause}}function ie(e){const t=de(e);return{outcome:t instanceof R||t instanceof W?"blocked":t?"user_error":"system_error",userSafe:t}}function le(e,t,r){try{return I(e,t)}catch(n){throw n instanceof R&&r?new R(`${n.message}. \`fetch\` reaches only the documented hosts of the callable APIs (${r}); other documented APIs can be read through the tools but not called.`,n.host):n}}function ue(e){try{return new URL(e).hostname||void 0}catch{return}}function fe(e){return e>=300&&e<400}function he(e,t,r){try{return!!I(new URL(e,t).href,r)}catch{return!1}}function me(e){if(!(e instanceof Error))return String(e);const t=e.cause;return t instanceof Error?`${e.message}: ${t.message}`:e.message}async function we(e,t){const r=Number(e.headers.get("content-length"));if(Number.isFinite(r)&&r>t)throw Y(t,r);if(!e.body)return"";const n=e.body.getReader(),c=new TextDecoder;let a=0,h="";try{for(;;){const{done:l,value:w}=await n.read();if(l)break;if(a+=w.byteLength,a>t)throw Y(t,a);h+=c.decode(w,{stream:!0})}return h+c.decode()}finally{await n.cancel().catch(()=>{})}}function Y(e,t){return new g(`Response is too large (over ${e} bytes, got ${t}); request less data`)}function ye(e,t,r){const n=e.routes?.find(c=>c.method===r&&c.pattern.test(t));return n?n.schemes:ge(e)}function Se(e,t){const r=t.filter(n=>e.secrets?.[n]);return r.length===0?t[0]:r.reduce((n,c)=>{const a=e.secrets?.[n]?.savedAt??0;return(e.secrets?.[c]?.savedAt??0)>a?c:n})}function pe(e,t){if(e==null)return;if(typeof e!="string")throw new g("securityScheme must be a security scheme name (a string)");if(!t)throw new g("securityScheme cannot be applied: the URL host does not identify exactly one callable API");const r=t.schemePriority??[];if(r.length>0&&!r.includes(e))throw new g(`Unknown securityScheme "${e}" for ${t.name}; valid values: ${r.join(", ")}`);return e}function ge(e){const t=Object.keys(e.secrets??{});if(t.length===0)return e.defaultScheme?[e.defaultScheme]:[];const r=(e.schemePriority??[]).filter(n=>t.includes(n));return[...r,...t.filter(n=>!r.includes(n))]}function be({callerHeaders:e,secret:t,authorization:r,clientIp:n,credentialHeaders:c}){const a={},h=new Set,l=t??(r?{header:"Authorization",value:r}:void 0),w=l?new Set((c??[]).map(f=>f.toLowerCase()).filter(f=>f!==l.header.toLowerCase())):new Set;for(const[f,$]of Object.entries(e??{})){const m=f.toLowerCase();m==="host"||m==="content-length"||m!==X&&(w.has(m)||(h.add(m),a[f]=$))}if(n&&(a[X]=n),!l)return{headers:a};const k=l.header.toLowerCase();return h.has(k)?{headers:a,callerHeader:k}:(a[l.header]=l.value,{headers:a})}function Ae(e){const t={};return e.forEach((r,n)=>{t[n.toLowerCase()]=r}),t}export{_e as buildFetchBridge,ce as getDefaultAgent,we as readCappedBody};
1
+ import{Agent as ie}from"undici";import{envConfig as le}from"../../../../config/env-config.js";import{logger as K}from"../../../../tools/notifiers/logger.js";import{auditHeaderNames as de}from"../../audit/audit-log.js";import{assertUrlAllowed as X}from"./ssrf-guard.js";import{createSsrfSafeLookup as ue}from"../../../../web-server/utils/ssrf-safe-lookup.js";import{SsrfBlockedError as J}from"../../../../web-server/utils/ssrf-blocklist.js";import{GatewayFetchBlockedError as p,GatewayFetchSystemError as fe,GatewayFetchUserError as S}from"./fetch-errors.js";import{redactHeaders as he,redactSecretValuesFromHeaders as me,redactSecretsFromString as _,secretEchoForms as pe}from"./redact.js";import{extractUpstreamDetail as we}from"./upstream-detail.js";import{normalizeFetchPath as ye}from"./normalize-path.js";import{reportGatewayFetch as Z}from"./fetch-telemetry.js";import{logGatewayFetchAudit as q}from"./fetch-audit.js";import{matchingApiCallOperations as Se}from"../api-call-operations.js";import{operationHttpMethod as be}from"../route-pattern.js";const ee="x-forwarded-for",ge=3e4,Ae=1024*1024,Ee=/%2f|%5c/i,ve=new Set([401,403,407]),$e=["authorization","proxy-authorization","cookie","x-api-key"];let x;function ke(){if(!x){const e=le.isDevelopMode;e&&K.warn("Gateway MCP SSRF guard: loopback upstreams are ALLOWED (develop mode only, never in runtime)."),x=new ie({connect:{lookup:ue({allowLoopback:e})}})}return x}function nt({allowedHosts:e,apis:t=[],authorization:r,onAuthRequired:n,fetchImpl:c=fetch,dispatcher:d=ke(),timeoutMs:f=ge,maxResponseBytes:b=Ae,maxFetchesPerExec:i,beforeSend:oe,clientIp:ae,allowedHttpMethods:P}){const O=[...e,...t.flatMap(a=>a.matchers)],se=[...new Set(t.map(a=>a.name).filter(Boolean))].join(", "),j=t.flatMap(a=>Object.values(a.secrets??{})),w=[...r?[{header:"Authorization",value:r}]:[],...j].flatMap(pe),ce=j.map(a=>a.header),I=t.flatMap(a=>a.credentialQueryParams??[]);let N=0,B=!1;return async a=>{const l=be(a.method??"GET"),D=Date.now();let G,A,E,v={},$,U,H;try{if(i!==void 0){const o=Math.max(0,i);if(N+=1,N>o)throw new S(`Outbound fetch budget exceeded (max ${o} fetches per execute); split the work across calls`)}if(P&&!P.includes(l))throw new p(`${l} is not allowed: this project permits only ${P.join(", ")} API calls`,te(a.url));const s=Re(a.url,O,se);if(G=s.hostname,A=ye(s.pathname),Ee.test(s.pathname))throw new p(`${l} ${s.pathname} is refused: the path holds an encoded path separator (%2F or %5C), which could reach an operation other than the documented one`,s.hostname);const y=t.filter(o=>o.matchers.some(T=>T.test(s.hostname))),m=y.flatMap(o=>o.operations?Se(o.operations,s.pathname,l).map(T=>({api:o,callable:T.callable,visible:T.visible,variables:T.variables})):[{api:o,callable:!0,visible:!0,variables:Number.POSITIVE_INFINITY}]),k=Math.min(...m.map(o=>o.variables)),M=m.filter(o=>o.variables===k);if(y.length>0&&(m.length===0||M.some(o=>!o.visible)))throw new p(`${l} ${s.pathname} is not a documented operation of ${y.map(o=>o.name).join(", ")}; \`fetch\` calls only the operations the API description documents`,s.hostname);const V=M.find(o=>!o.callable);if(V)throw new p(`${l} ${s.pathname} is not callable: this operation of ${V.api.name} is excluded from agent calls`,s.hostname);const h=new Set(M.map(o=>o.api)).size===1?M[0].api:void 0;E=h?.slug;const z=He(a.securityScheme,h),F=z?[z]:h?Pe(h,s.pathname,l):void 0,C=h&&F?Ue(h,F):void 0,Q=C?h?.secrets?.[C]:void 0,L=Q??(r?{header:"Authorization",value:r}:void 0);v=je({callerHeaders:a.headers,injected:L,clientIp:ae});const W=xe(a.headers,s,y,L);if(W&&(L||B))throw new S(Oe(W,!!L));await oe?.(),B||=!!L,$=Q?C:void 0;const Y=new AbortController;H=setTimeout(()=>Y.abort(),f);const u=await c(s,{method:l,headers:v,body:a.body,redirect:"manual",signal:Y.signal,dispatcher:d});U=u.status;let R="";try{R=await Fe(u,b)}finally{if(ve.has(u.status)&&h&&n){const o=we(R,u.headers.get("content-type"),w);n({api:h.slug,name:h.name,status:u.status,...C?{scheme:C}:{},...F?.length?{schemes:[...F]}:{},...$?{sentScheme:$}:{},...o?{detail:o}:{}})}}let g=me(Ie(u.headers),w);if(Te(u.status)){g=he(g,ce);const{location:o}=g;o&&!De(o,s,O)&&delete g.location}return Z({host:s.hostname,method:l,outcome:"success",durationMs:Date.now()-D,path:A,api:E,status:u.status,bytes:Buffer.byteLength(R,"utf8")}),q({rawUrl:s.href,method:l,host:s.hostname,outcome:"success",durationMs:Date.now()-D,injectedSecretValues:w,credentialQueryParams:I,path:A,api:E,status:u.status,injectedScheme:$,requestHeaders:v,requestBody:a.body,responseHeaders:g,responseBody:R}),{status:u.status,statusText:_(u.statusText,w),ok:u.ok,headers:g,bodyText:_(R,w)}}catch(s){const{outcome:y,userSafe:m}=Le(s),k=G??(m instanceof p?m.host:void 0)??te(a.url)??"invalid-url";throw Z({host:k,method:l,outcome:y,durationMs:Date.now()-D,path:A,api:E,status:U}),q({rawUrl:a.url,method:l,host:k,outcome:y,durationMs:Date.now()-D,injectedSecretValues:w,credentialQueryParams:I,path:A,api:E,status:U,injectedScheme:$,requestHeaders:v,requestBody:a.body}),m||(K.error(`Gateway MCP fetch failed [${l} ${k}]: ${_(Me(s),w)} headers=${de(v)}`),new fe("Upstream fetch failed due to an internal error"))}finally{H!==void 0&&clearTimeout(H)}}}function Ce(e){let t=e;for(let r=0;r<5&&t instanceof Error;r++){if(t instanceof S||t instanceof J)return t;t=t.cause}}function Le(e){const t=Ce(e);return{outcome:t instanceof p||t instanceof J?"blocked":t?"user_error":"system_error",userSafe:t}}function Re(e,t,r){try{return X(e,t)}catch(n){throw n instanceof p&&r?new p(`${n.message}. \`fetch\` reaches only the documented hosts of the callable APIs (${r}); other documented APIs can be read through the tools but not called.`,n.host):n}}function te(e){try{return new URL(e).hostname||void 0}catch{return}}function Te(e){return e>=300&&e<400}function De(e,t,r){try{return!!X(new URL(e,t).href,r)}catch{return!1}}function Me(e){if(!(e instanceof Error))return String(e);const t=e.cause;return t instanceof Error?`${e.message}: ${t.message}`:e.message}async function Fe(e,t){const r=Number(e.headers.get("content-length"));if(Number.isFinite(r)&&r>t)throw re(t,r);if(!e.body)return"";const n=e.body.getReader(),c=new TextDecoder;let d=0,f="";try{for(;;){const{done:b,value:i}=await n.read();if(b)break;if(d+=i.byteLength,d>t)throw re(t,d);f+=c.decode(i,{stream:!0})}return f+c.decode()}finally{await n.cancel().catch(()=>{})}}function re(e,t){return new S(`Response is too large (over ${e} bytes, got ${t}); request less data`)}function Pe(e,t,r){const n=e.routes?.find(c=>c.method===r&&c.pattern.test(t));return n?n.schemes:_e(e)}function Ue(e,t){const r=t.filter(n=>e.secrets?.[n]);return r.length===0?t[0]:r.reduce((n,c)=>{const d=e.secrets?.[n]?.savedAt??0;return(e.secrets?.[c]?.savedAt??0)>d?c:n})}function He(e,t){if(e==null)return;if(typeof e!="string")throw new S("securityScheme must be a security scheme name (a string)");if(!t)throw new S("securityScheme cannot be applied: the URL host does not identify exactly one callable API");const r=t.schemePriority??[];if(r.length>0&&!r.includes(e))throw new S(`Unknown securityScheme "${e}" for ${t.name}; valid values: ${r.join(", ")}`);return e}function _e(e){const t=Object.keys(e.secrets??{});if(t.length===0)return e.defaultScheme?[e.defaultScheme]:[];const r=(e.schemePriority??[]).filter(n=>t.includes(n));return[...r,...t.filter(n=>!r.includes(n))]}function xe(e,t,r,n){const c=ne([...$e,...r.flatMap(i=>i.credentialHeaders??[]),...n?[n.header]:[]]),d=Object.keys(e??{}).find(i=>c.has(i.toLowerCase()));if(d)return{kind:"header",name:d};const f=ne(r.flatMap(i=>i.credentialQueryParams??[])),b=[...t.searchParams.keys()].find(i=>f.has(i.toLowerCase()));return b?{kind:"query parameter",name:b}:void 0}function ne(e){return new Set(e.map(t=>t.toLowerCase()))}function Oe({kind:e,name:t},r){return r?`Remove the "${t}" ${e}: this request already sends the stored credential.`:`Remove the "${t}" ${e}: this script already sent a stored credential, so it cannot also send its own.`}function je({callerHeaders:e,injected:t,clientIp:r}){const n={};for(const[c,d]of Object.entries(e??{})){const f=c.toLowerCase();f==="host"||f==="content-length"||f!==ee&&(n[c]=d)}return r&&(n[ee]=r),t&&(n[t.header]=t.value),n}function Ie(e){const t={};return e.forEach((r,n)=>{t[n.toLowerCase()]=r}),t}export{nt as buildFetchBridge,ke as getDefaultAgent,Fe as readCappedBody};
@@ -1,4 +1,15 @@
1
1
  export declare const REDACTED = "\u00AB[redacted]\u00BB";
2
+ /**
3
+ * Every form in which an upstream can echo an injected credential: the full header value, the
4
+ * token without its auth scheme, the decoded `user:password` of a Basic value, and the cookie
5
+ * value without its name, each also JSON-escaped and URL-encoded. Parts under 8 characters are
6
+ * left out so they do not redact unrelated text.
7
+ */
8
+ export declare function secretEchoForms({ header, value }: {
9
+ header: string;
10
+ value: string;
11
+ }): string[];
2
12
  export declare function redactSecretsFromString(text: string, secretValues: readonly string[]): string;
13
+ export declare function redactSecretValuesFromHeaders(headers: Record<string, string>, secretValues: readonly string[]): Record<string, string>;
3
14
  export declare function redactHeaders(headers: Record<string, string>, extraSensitiveNames?: readonly string[]): Record<string, string>;
4
15
  //# sourceMappingURL=redact.d.ts.map
@@ -1 +1 @@
1
- const s="\xAB[redacted]\xBB",c=["authorization","proxy-authorization","cookie","set-cookie"];function a(r,n){let t=r;for(const e of n)e.trim()&&(t=t.split(e).join(s));return t}function u(r,n=[]){const t=new Set([...c,...n].map(o=>o.toLowerCase())),e={};for(const[o,i]of Object.entries(r))e[o]=t.has(o.toLowerCase())?s:i;return e}export{s as REDACTED,u as redactHeaders,a as redactSecretsFromString};
1
+ const i="\xAB[redacted]\xBB",f=["authorization","proxy-authorization","cookie","set-cookie"],m=8,a=/^([A-Za-z][\w-]*) +(\S+)$/;function p({header:t,value:e}){if(!e.trim())return[];const n=u(t,e).filter(r=>r.trim().length>=8);return[...new Set([e,...n].flatMap(E))]}function u(t,e){if(t.toLowerCase()==="cookie"){const c=e.indexOf("=");return c>0?[e.slice(c+1)]:[]}const n=a.exec(e);if(!n)return[];const[,r,o]=n,s=r.toLowerCase()==="basic"?d(o):void 0;return s?[o,s]:[o]}function d(t){const e=Buffer.from(t,"base64").toString("utf8");return Buffer.from(e,"utf8").toString("base64")===t&&e.includes(":")?e:void 0}function E(t){const e=JSON.stringify(t).slice(1,-1);return[t,e,e.replace(/\//g,"\\/"),encodeURIComponent(t)]}function l(t,e){const n=e.filter(o=>o.trim()).sort((o,s)=>s.length-o.length);let r=t;for(const o of n)r=r.split(o).join(i);return r}function S(t,e){const n={};for(const[r,o]of Object.entries(t))n[r]=l(o,e);return n}function x(t,e=[]){const n=new Set([...f,...e].map(o=>o.toLowerCase())),r={};for(const[o,s]of Object.entries(t))r[o]=n.has(o.toLowerCase())?i:s;return r}export{i as REDACTED,x as redactHeaders,S as redactSecretValuesFromHeaders,l as redactSecretsFromString,p as secretEchoForms};
@@ -1 +1 @@
1
- import{getCallableApis as l}from"./callable-apis.js";import{buildAllowedHostMatchers as a}from"./fetch/allowed-hosts.js";import{buildFetchBridge as m}from"./fetch/fetch-bridge.js";import{credentialHeadersOf as o,orderedSchemesOf as c,resolveUpstreamApiAuth as h}from"./upstream-auth-scheme.js";async function f(e,s){const i=await Promise.all(l(e).map(async r=>{const t=await h(e,r.relativePath);return{slug:r.relativePath,name:r.name,matchers:a(r.servers??[]),secrets:e.upstreamSecrets?.[r.relativePath],defaultScheme:t.defaultScheme,routes:t.routes,credentialHeaders:o(t.schemes),schemePriority:c(t.schemes)}}));return m({allowedHosts:a([]),apis:i,authorization:e.upstreamAuthorization,onAuthRequired:s,maxFetchesPerExec:e.gatewayFetchBudgetPerExec,clientIp:e.clientIp})}export{f as buildExecuteHostFetch};
1
+ import{isDefined as l}from"../../../../utils/guards/is-defined.js";import{filterApiDescriptionByAccess as c,readApiDescriptionFromFs as u}from"../docs-mcp/tools/openapi/utils.js";import{isDefinitionHidden as d}from"../utils/xmcp-utils.js";import{getAllowedHttpMethods as m}from"./allowed-http-methods.js";import{getCallableApis as f,getExcludedOperations as h}from"./callable-apis.js";import{apiCallOperationsOf as p}from"./api-call-operations.js";import{buildAllowedHostMatchers as n}from"./fetch/allowed-hosts.js";import{buildFetchBridge as w}from"./fetch/fetch-bridge.js";import{GatewayFetchUserError as b}from"./fetch/fetch-errors.js";import{sendFetchCount as g}from"./rate-limit/fetch-limiter/fetch-count-client.js";import{credentialHeadersOf as v,orderedSchemesOf as y,resolveUpstreamApiAuth as P}from"./upstream-auth-scheme.js";async function z(e,t){const i=await Promise.all(f(e).map(async r=>{const a=await F(e,r.relativePath),o=a?.visible;if(o&&d(o.info,"apiCalls"))return;const s=await P(e,r.relativePath,async()=>o);return{slug:r.relativePath,name:r.name,matchers:n(r.servers??[]),secrets:e.upstreamSecrets?.[r.relativePath],defaultScheme:s.defaultScheme,routes:s.routes,credentialHeaders:v(s.schemes),credentialQueryParams:s.credentialQueryParams,schemePriority:y(s.schemes),operations:a?.visible?p(a.full,a.visible,r.servers??[],h(e,r.relativePath)):[]}}));return w({allowedHosts:n([]),apis:i.filter(l),authorization:e.upstreamAuthorization,onAuthRequired:t,maxFetchesPerExec:e.gatewayFetchBudgetPerExec,beforeSend:A(e),clientIp:e.clientIp,allowedHttpMethods:m(e.config?.mcp)})}function A(e){const{fetchCountRequest:t,internalBaseUrl:i}=e;if(!(!t||!i))return async()=>{const r=await g(i,t);if(r)throw new b(r)}}async function F(e,t){try{const i=await u(t,e.outdir||"");if(!i)return;const r=c(i,{user:e.user,rbac:e.config?.access?.rbac||{},requiresLogin:!!e.config?.access?.requiresLogin});return{full:i,visible:r}}catch{return}}export{z as buildExecuteHostFetch};
@@ -0,0 +1,4 @@
1
+ import type { FetchCountRequest } from '../../../types.js';
2
+ /** The refusal when a limit is reached; when the check itself fails, the fetch is allowed. */
3
+ export declare function sendFetchCount(serverBaseUrl: string, { caller, secret }: FetchCountRequest): Promise<string | undefined>;
4
+ //# sourceMappingURL=fetch-count-client.d.ts.map
@@ -0,0 +1 @@
1
+ import{withPathPrefix as i}from"@redocly/theme/core/utils";import{ServerRoutes as a}from"../../../../../../constants/api.js";import{logger as e}from"../../../../../tools/notifiers/logger.js";const c=5e3;async function d(r,{caller:n,secret:o}){try{const t=await fetch(`${r}${i(a.MCP_FETCH_COUNT)}`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({caller:n}),signal:AbortSignal.timeout(c)});if(t.status===429)return await t.text();t.ok||e.warn(`Fetch limit check answered ${t.status}; the fetch was allowed uncounted`);return}catch(t){e.warn(`Fetch limit check failed; the fetch was allowed uncounted: ${String(t)}`);return}}export{d as sendFetchCount};
@@ -0,0 +1,4 @@
1
+ /** The secret of this server process; only its own execute workers receive it. */
2
+ export declare function fetchCountSecret(): string;
3
+ export declare function isFetchCountSecret(value: string | null | undefined): boolean;
4
+ //# sourceMappingURL=fetch-count-secret.d.ts.map
@@ -0,0 +1 @@
1
+ import{randomBytes as o,timingSafeEqual as f}from"node:crypto";const n=o(32).toString("base64url");function i(){return n}function u(t){if(typeof t!="string")return!1;const e=Buffer.from(t),r=Buffer.from(n);return e.length===r.length&&f(e,r)}export{i as fetchCountSecret,u as isFetchCountSecret};
@@ -0,0 +1,11 @@
1
+ import type { FetchCountRequest } from '../../../types.js';
2
+ import type { FetchCounterRepository } from './fetch-counter-repository.js';
3
+ import type { GatewayFetchLimits } from './fetch-limits.js';
4
+ type FetchLimits = Pick<GatewayFetchLimits, 'ratePerMin' | 'quotaPerDay'>;
5
+ /**
6
+ * Counts one fetch atomically and returns the refusal when a window is full. A signed-in user
7
+ * is counted by subject, an anonymous caller by IP; a caller with neither is not counted.
8
+ */
9
+ export declare function applyFetchCount(store: FetchCounterRepository, caller: FetchCountRequest['caller'], limits: FetchLimits, now: number): Promise<string | undefined>;
10
+ export {};
11
+ //# sourceMappingURL=fetch-count.d.ts.map
@@ -0,0 +1 @@
1
+ import{logger as u}from"../../../../../tools/notifiers/logger.js";const d=[{seconds:60,limitOf:"ratePerMin",per:"minute"},{seconds:86400,limitOf:"quotaPerDay",per:"day"}];function a(c,r,i){const n=Math.floor(i/1e3);return d.map(e=>{const t=Math.floor(n/e.seconds);return{key:["mcp","fetch-rl",c,String(e.seconds),String(t)],per:e.per,limit:r[e.limitOf],retryAfterSeconds:Math.max(1,(t+1)*e.seconds-n),ttlInSeconds:e.seconds}})}async function l(c,r){const i=await c.getMany(r.map(e=>e.key)),n=r.filter((e,t)=>(i[t]?.value?.count??0)>=e.limit);return n.length>0?n.reduce((e,t)=>e.retryAfterSeconds>=t.retryAfterSeconds?e:t):r.reduce((e,t)=>e.retryAfterSeconds<=t.retryAfterSeconds?e:t)}async function y(c,r,i,n){const e=r.subject??(r.ip&&`ip:${r.ip}`);if(!e)return;const t=a(e,i,n);try{if(await c.incrementBelowLimits(t))return;const{limit:o,per:s,retryAfterSeconds:f}=await l(c,t);return`Outbound fetch limit reached (${o} fetches per ${s}); retry in ${f}s`}catch(o){u.warn(`Fetch limit store failed; the fetch was allowed uncounted: ${String(o)}`);return}}export{y as applyFetchCount};
@@ -0,0 +1,13 @@
1
+ import type { KvKey } from '@redocly/config';
2
+ import type { RepositoryInstanceOptions } from '../../../../../providers/database/types.js';
3
+ import { KvRepository } from '../../../../../persistence/kv/repositories/kv-repository.js';
4
+ export declare class FetchCounterRepository extends KvRepository {
5
+ static create(options: RepositoryInstanceOptions): Promise<FetchCounterRepository>;
6
+ /** Adds one to every slot in one statement unless a slot is at its limit; tells whether it did. */
7
+ incrementBelowLimits(slots: readonly {
8
+ key: KvKey;
9
+ limit: number;
10
+ ttlInSeconds: number;
11
+ }[]): Promise<boolean>;
12
+ }
13
+ //# sourceMappingURL=fetch-counter-repository.d.ts.map
@@ -0,0 +1,13 @@
1
+ import{sql as t}from"drizzle-orm";import{createDatabaseRepositoryInstance as s}from"../../../../../providers/database/instance-cache-resolver.js";import{KvRepository as u}from"../../../../../persistence/kv/repositories/kv-repository.js";import{createKvDbRecord as _}from"../../../../../persistence/kv/mappers/create-kv-db-record.js";class o extends u{static create(a){return s(a,"fetch counter",o)}async incrementBelowLimits(a){const d=t.join(a.map(({key:c,limit:n,ttlInSeconds:r})=>{const e=_({key:c,value:{count:1},ttlInSeconds:r});return t`(${e.encodedKey}, ${e.value}, ${e.expiresAt}, ${e.createdAt}, ${e.updatedAt}, ${n})`}),t`, `);return(await this.databaseClient.dbClient.all(t`
2
+ WITH slot (encoded_key, value, expires_at, created_at, updated_at, max_count) AS (VALUES ${d})
3
+ INSERT INTO kv (encoded_key, value, expires_at, created_at, updated_at)
4
+ SELECT encoded_key, value, expires_at, created_at, updated_at FROM slot
5
+ WHERE NOT EXISTS (
6
+ SELECT 1 FROM slot AS s JOIN kv ON kv.encoded_key = s.encoded_key
7
+ WHERE json_extract(kv.value, '$.count') >= s.max_count
8
+ )
9
+ ON CONFLICT (encoded_key) DO UPDATE SET
10
+ value = json_object('count', json_extract(kv.value, '$.count') + 1),
11
+ updated_at = excluded.updated_at
12
+ RETURNING encoded_key
13
+ `)).length>0}}export{o as FetchCounterRepository};
@@ -4,4 +4,4 @@ export type GatewayFetchLimits = {
4
4
  budgetPerExec: number;
5
5
  };
6
6
  export declare function resolveGatewayFetchLimits(): GatewayFetchLimits;
7
- //# sourceMappingURL=limits.d.ts.map
7
+ //# sourceMappingURL=fetch-limits.d.ts.map
@@ -0,0 +1 @@
1
+ import{FEATURE as e,GATEWAY_FETCH_LIMIT_DEFAULTS as t}from"../../../../../../constants/common.js";import{readEntitlementNumber as r}from"../../../../../utils/entitlements/read-entitlement-number.js";function i(){return{ratePerMin:r(e.gatewayFetchRatePerMin,t.ratePerMin),quotaPerDay:r(e.gatewayFetchQuotaPerDay,t.quotaPerDay),budgetPerExec:r(e.gatewayFetchBudgetPerExec,t.budgetPerExec)}}export{i as resolveGatewayFetchLimits};
@@ -1,3 +1,3 @@
1
- import type { Store } from '../../../../store.js';
1
+ import type { Store } from '../../../../../store.js';
2
2
  export declare function applyEdgeRateLimit(request: Request, store: Store): Promise<Response | undefined>;
3
3
  //# sourceMappingURL=apply-edge-rate-limit.d.ts.map
@@ -0,0 +1 @@
1
+ import{envConfig as o}from"../../../../../config/env-config.js";import{isRealmOrReef as s}from"../../../../../utils/is-realm-or-reef.js";import{KvRepository as m}from"../../../../../persistence/kv/repositories/kv-repository.js";import{logger as p}from"../../../../../tools/notifiers/logger.js";import{ipRateLimitKey as f}from"../utils/ip-key.js";import{checkEdgeRateLimit as c}from"./edge-limiter.js";import{resolveMcpRequestLimits as d}from"./request-limits.js";import{reportMcpRateLimited as l}from"./rate-limit-telemetry.js";async function w(n,a){if(!s())return;const i=f(n);if(!i)return;let e,r;try{const t=await m.create({baseDbDir:a.serverOutDir,sqldRemoteDatabaseUrl:o.SQLD_REMOTE_DATABASE_URL,sqldRemoteDatabaseAuthToken:o.SQLD_REMOTE_DATABASE_AUTH_TOKEN});r=d(),e=await c({kv:t,ip:i,limits:r,now:Date.now()})}catch(t){p.warn(`gateway rate-limit init error, failing open: ${String(t)}`);return}if(!e.allowed)return l({scope:e.scope,retryAfterSeconds:e.retryAfterSeconds,limit:e.scope==="rate"?r.ratePerMin:r.quotaPerDay}),new Response(JSON.stringify({jsonrpc:"2.0",error:{code:-32029,message:`Rate limit exceeded (${e.scope}); retry after ${e.retryAfterSeconds}s`},id:null}),{status:429,headers:{"Content-Type":"application/json","Retry-After":String(e.retryAfterSeconds)}})}export{w as applyEdgeRateLimit};
@@ -1,5 +1,5 @@
1
- import type { KvRepository } from '../../../../persistence/kv/repositories/kv-repository.js';
2
- import type { GatewayFetchLimits } from './limits.js';
1
+ import type { KvRepository } from '../../../../../persistence/kv/repositories/kv-repository.js';
2
+ import type { McpRequestLimits } from './request-limits.js';
3
3
  export type EdgeDecision = {
4
4
  allowed: true;
5
5
  } | {
@@ -10,7 +10,7 @@ export type EdgeDecision = {
10
10
  export declare function checkEdgeRateLimit(params: {
11
11
  kv: KvRepository;
12
12
  ip: string;
13
- limits: GatewayFetchLimits;
13
+ limits: McpRequestLimits;
14
14
  now: number;
15
15
  }): Promise<EdgeDecision>;
16
16
  //# sourceMappingURL=edge-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ import{checkAndIncrement as c}from"../utils/window-counter.js";const s=60,d=86400;async function w(i){const{kv:e,ip:t,limits:o,now:r}=i;await e.sync();const n=await c({kv:e,ip:t,windowSeconds:s,limit:o.ratePerMin,now:r});if(!n.allowed)return{allowed:!1,retryAfterSeconds:n.retryAfterSeconds,scope:"rate"};const a=await c({kv:e,ip:t,windowSeconds:d,limit:o.quotaPerDay,now:r});return a.allowed?{allowed:!0}:{allowed:!1,retryAfterSeconds:a.retryAfterSeconds,scope:"quota"}}export{w as checkEdgeRateLimit};
@@ -0,0 +1 @@
1
+ import{telemetry as o}from"../../../../../telemetry/index.js";import{logger as i}from"../../../../../tools/notifiers/logger.js";import{McpServerType as m}from"../../../constants.js";import{serverAttributes as p}from"../../../utils/telemetry-attributes.js";function d({scope:e,retryAfterSeconds:r,limit:t}){try{o.sendMcpRateLimitedMessage([{...p(m.Docs),scope:e,retry_after_seconds:r,limit:t}])}catch{}i.warn(`Gateway MCP rate limited [scope=${e}] retryAfterSeconds=${r} limit=${t}`)}export{d as reportMcpRateLimited};
@@ -0,0 +1,6 @@
1
+ export type McpRequestLimits = {
2
+ ratePerMin: number;
3
+ quotaPerDay: number;
4
+ };
5
+ export declare function resolveMcpRequestLimits(): McpRequestLimits;
6
+ //# sourceMappingURL=request-limits.d.ts.map
@@ -0,0 +1 @@
1
+ import{FEATURE as e,MCP_REQUEST_LIMIT_DEFAULTS as r}from"../../../../../../constants/common.js";import{readEntitlementNumber as t}from"../../../../../utils/entitlements/read-entitlement-number.js";function i(){return{ratePerMin:t(e.mcpRequestRatePerMin,r.ratePerMin),quotaPerDay:t(e.mcpRequestQuotaPerDay,r.quotaPerDay)}}export{i as resolveMcpRequestLimits};
@@ -0,0 +1 @@
1
+ import{isIP as l}from"node:net";import{getFeedbackClientIp as a}from"../../../../../web-server/utils/feedback-ip.js";import{extractMappedIpv4 as c}from"../../../../../web-server/utils/ssrf-blocklist.js";function I(t){const n=a(t);if(n)return f(n)}function f(t){if(l(t)===6){const n=c(t);return n||`${u(t)}::/64`}return t}function u(t){const n=t.split("%")[0],[e,i=""]=n.split("::"),r=e?e.split(":"):[],o=i?i.split(":"):[],s=8-r.length-o.length;return[...r,...Array(Math.max(0,s)).fill("0"),...o].slice(0,4).map(p=>parseInt(p||"0",16).toString(16)).join(":")}export{I as ipRateLimitKey};
@@ -1,4 +1,4 @@
1
- import type { KvRepository } from '../../../../persistence/kv/repositories/kv-repository.js';
1
+ import type { KvRepository } from '../../../../../persistence/kv/repositories/kv-repository.js';
2
2
  export type WindowCheck = {
3
3
  allowed: false;
4
4
  retryAfterSeconds: number;
@@ -0,0 +1 @@
1
+ import{logger as d}from"../../../../../tools/notifiers/logger.js";function g(e,t,r){return["mcp","request-rl",e,String(t),String(r)]}async function S(e){const{kv:t,ip:r,windowSeconds:n,limit:l,now:w}=e,o=Math.floor(w/1e3),c=Math.floor(o/n),i=g(r,n,c);try{const s=(await t.get(i))?.count??0;if(s>=l){const u=(c+1)*n-o;return{allowed:!1,retryAfterSeconds:Math.max(1,u)}}return await t.set(i,{count:s+1},{ttlInSeconds:n}),{allowed:!0}}catch(a){return d.warn(`gateway rate-limit store error, failing open: ${String(a)}`),{allowed:!0}}}export{S as checkAndIncrement};
@@ -0,0 +1,18 @@
1
+ import type { OpenAPIServer } from '@redocly/api-docs';
2
+ /** Regex for request pathnames of the OpenAPI `path` below the base path that `prefix` matches. */
3
+ export declare function pathPattern(prefix: string, path: string): RegExp | undefined;
4
+ /**
5
+ * Regex group matching any of the servers' base paths: required when every server has one,
6
+ * optional when some server has none, `''` when none has one.
7
+ */
8
+ export declare function basePathPrefix(servers: readonly OpenAPIServer[]): string;
9
+ /** Number of `{variable}` segments in an OpenAPI path; fewer means a more specific path. */
10
+ export declare function pathVariableCount(path: string): number;
11
+ /**
12
+ * Identity of an OpenAPI path template for comparison: variable names and a trailing slash do not
13
+ * change which requests the path matches, so `/users/{id}/` and `/users/{userId}` are equal.
14
+ */
15
+ export declare function pathTemplateKey(path: string): string;
16
+ /** The upper-case HTTP method sent for an OpenAPI operation key or a request method; `x-query` sends `QUERY`. */
17
+ export declare function operationHttpMethod(key: string): string;
18
+ //# sourceMappingURL=route-pattern.d.ts.map
@@ -0,0 +1 @@
1
+ const r=/\{(?<!\{[^}]*?\{)[^}]*\}/g;function u(e,t){if(t.startsWith("/"))return new RegExp(`^${e}${a(t)}/?$`)}function s(e){const t=e.map(c=>p(c.url)),n=[...new Set(t.filter(Boolean).map(a))];if(n.length===0)return"";const o=`(?:${n.join("|")})`;return t.every(Boolean)?o:`${o}?`}function l(e){return e.match(r)?.length??0}function f(e){return e.replace(r,"{}").replace(/(?<!\/)\/+$/,"")||"/"}function h(e){const t=e.toUpperCase();return t==="X-QUERY"?"QUERY":t}function a(e){return e.split(r).map(i).join("[^/]+").replace(/(?<!\/)\/+$/,"")}function p(e){if(!e)return"";const t=e.replace(/^[a-z][a-z0-9+.-]*:\/\//i,""),n=t.indexOf("/");return n===-1?"":t.slice(n).split(/[?#]/)[0].replace(/(?<!\/)\/+$/,"")}function i(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}export{s as basePathPrefix,h as operationHttpMethod,u as pathPattern,f as pathTemplateKey,l as pathVariableCount};
@@ -1,5 +1,6 @@
1
1
  import type { KvService } from '@redocly/config';
2
2
  import type { UpstreamAuthScheme } from './upstream-auth-scheme.js';
3
+ export declare const ELICITATION_TTL_MINUTES = 15;
3
4
  export declare const ELICITATION_TTL_MS: number;
4
5
  export type StoredSecret = {
5
6
  header: string;
@@ -11,7 +12,6 @@ export type ElicitationScheme = {
11
12
  name: string;
12
13
  label?: string;
13
14
  auth: UpstreamAuthScheme;
14
- hasExistingSecret: boolean;
15
15
  operations?: string[];
16
16
  };
17
17
  export type ElicitationTarget = {
@@ -21,17 +21,23 @@ export type ElicitationTarget = {
21
21
  scheme: string;
22
22
  schemeLabel?: string;
23
23
  auth: UpstreamAuthScheme;
24
- hasExistingSecret?: boolean;
25
24
  schemes?: ElicitationScheme[];
25
+ /** The schemes the failing operation accepts, when it accepts more than one. */
26
+ acceptedSchemes?: string[];
27
+ expiresAt: number;
26
28
  };
29
+ export type StoredSchemes = Record<string, {
30
+ savedAt?: number;
31
+ }>;
27
32
  export declare class SecretStore {
28
33
  #private;
29
34
  setSecret(kv: KvService, subject: string, api: string, scheme: string, secret: StoredSecret): Promise<void>;
30
35
  getSecretsForSubject(kv: KvService, subject: string): Promise<UpstreamSecrets>;
36
+ getStoredSchemes(kv: KvService, subject: string, api: string): Promise<StoredSchemes>;
31
37
  hasSecret(kv: KvService, subject: string, api: string, scheme: string): Promise<boolean>;
32
38
  deleteSecret(kv: KvService, subject: string, api: string, scheme: string): Promise<void>;
33
39
  deleteSubject(kv: KvService, subject: string): Promise<void>;
34
- createElicitation(kv: KvService, target: ElicitationTarget): Promise<string>;
40
+ createElicitation(kv: KvService, target: Omit<ElicitationTarget, 'expiresAt'>): Promise<string>;
35
41
  peekElicitation(kv: KvService, token: string): Promise<ElicitationTarget | undefined>;
36
42
  consumeElicitation(kv: KvService, token: string): Promise<ElicitationTarget | undefined>;
37
43
  }
@@ -1 +1 @@
1
- import{createCipheriv as h,createDecipheriv as m,createHash as d,hkdfSync as o,randomBytes as l,randomUUID as p}from"node:crypto";import{JWT_SECRET_KEY as f}from"../../../constants/common.js";import{assertHeaderSafe as y}from"./credential-validation.js";import{secretTtlSeconds as S}from"./subject.js";const g=900*1e3,I="mcp-upstream-secrets",T="mcp-elicitation",w="mcp-elicitation";class A{async setSecret(t,e,r,s,i){y(i.header,i.value);const n={...i,savedAt:Date.now()},a={api:r,scheme:s,...this.#o(this.#c(e),n,this.#n(r,s))};await t.set(this.#t(e,r,s),a,{ttlInSeconds:S(e)})}async getSecretsForSubject(t,e){const r=this.#c(e),{items:s}=await t.list({prefix:this.#i(e)}),i={};for(const n of s){if(!n.value?.scheme)continue;const{api:a,scheme:c}=n.value;try{const u=this.#s(r,n.value,this.#n(a,c));i[a]={...i[a],[c]:u}}catch{continue}}return i}async hasSecret(t,e,r,s){return!!await t.get(this.#t(e,r,s))}async deleteSecret(t,e,r,s){await t.delete(this.#t(e,r,s))}async deleteSubject(t,e){const{items:r}=await t.list({prefix:this.#i(e)}),s=r.flatMap(i=>i.value?[i.key]:[]);s.length!==0&&await t.transaction(async i=>{for(const n of s)await i.delete(n)})}async createElicitation(t,e){const r=p();return await t.set(this.#e(r),this.#o(this.#r(),e),{ttlInSeconds:g/1e3}),r}async peekElicitation(t,e){const r=await t.get(this.#e(e));if(r)try{return this.#s(this.#r(),r)}catch{return}}async consumeElicitation(t,e){const r=this.#e(e),s=await t.transaction(async i=>{const n=await i.get(r);return n&&await i.delete(r),n});if(s)try{return this.#s(this.#r(),s)}catch{return}}#i(t){return["mcp","secret",this.#a(t)]}#t(t,e,r){return["mcp","secret",this.#a(t),e,r]}#n(t,e){return`${t}\0${e}`}#e(t){return["mcp","elicitation",t]}#a(t){return d("sha256").update(t).digest("hex")}#c(t){return Buffer.from(o("sha256",f,t,I,32))}#r(){return Buffer.from(o("sha256",f,w,T,32))}#o(t,e,r){const s=l(12),i=h("aes-256-gcm",t,s);r&&i.setAAD(Buffer.from(r,"utf8"));const n=Buffer.concat([i.update(JSON.stringify(e),"utf8"),i.final()]);return{iv:s.toString("base64"),tag:i.getAuthTag().toString("base64"),data:n.toString("base64")}}#s(t,e,r){const s=m("aes-256-gcm",t,Buffer.from(e.iv,"base64"));r&&s.setAAD(Buffer.from(r,"utf8")),s.setAuthTag(Buffer.from(e.tag,"base64"));const i=Buffer.concat([s.update(Buffer.from(e.data,"base64")),s.final()]).toString("utf8");return JSON.parse(i)}}const N=new A;export{g as ELICITATION_TTL_MS,A as SecretStore,N as secretStore};
1
+ import{createCipheriv as d,createDecipheriv as l,createHash as p,hkdfSync as f,randomBytes as y,randomUUID as S}from"node:crypto";import{JWT_SECRET_KEY as u}from"../../../constants/common.js";import{assertHeaderSafe as I}from"./credential-validation.js";import{secretTtlSeconds as T}from"./subject.js";const g=15,h=g*60*1e3,w="mcp-upstream-secrets",A="mcp-elicitation",E="mcp-elicitation";class v{async setSecret(t,e,r,s,n){I(n.header,n.value);const i={...n,savedAt:Date.now()},a={api:r,scheme:s,...this.#f(this.#o(e),i,this.#a(r,s))};await t.set(this.#e(e,r,s),a,{ttlInSeconds:T(e)})}async getSecretsForSubject(t,e){return this.#i(t,e,this.#t(e))}async getStoredSchemes(t,e,r){const s=[...this.#t(e),r],n=(await this.#i(t,e,s))[r]??{};return Object.fromEntries(Object.entries(n).map(([i,{savedAt:a}])=>[i,{savedAt:a}]))}async#i(t,e,r){const s=this.#o(e),{items:n}=await t.list({prefix:r}),i={};for(const a of n){if(!a.value?.scheme)continue;const{api:c,scheme:o}=a.value;try{const m=this.#n(s,a.value,this.#a(c,o));i[c]={...i[c],[o]:m}}catch{continue}}return i}async hasSecret(t,e,r,s){return!!await t.get(this.#e(e,r,s))}async deleteSecret(t,e,r,s){await t.delete(this.#e(e,r,s))}async deleteSubject(t,e){const{items:r}=await t.list({prefix:this.#t(e)}),s=r.flatMap(n=>n.value?[n.key]:[]);s.length!==0&&await t.transaction(async n=>{for(const i of s)await n.delete(i)})}async createElicitation(t,e){const r=S(),s={...e,expiresAt:Date.now()+h};return await t.set(this.#r(r),this.#f(this.#s(),s),{ttlInSeconds:h/1e3}),r}async peekElicitation(t,e){const r=await t.get(this.#r(e));if(r)try{return this.#n(this.#s(),r)}catch{return}}async consumeElicitation(t,e){const r=this.#r(e),s=await t.transaction(async n=>{const i=await n.get(r);return i&&await n.delete(r),i});if(s)try{return this.#n(this.#s(),s)}catch{return}}#t(t){return["mcp","secret",this.#c(t)]}#e(t,e,r){return["mcp","secret",this.#c(t),e,r]}#a(t,e){return`${t}\0${e}`}#r(t){return["mcp","elicitation",t]}#c(t){return p("sha256").update(t).digest("hex")}#o(t){return Buffer.from(f("sha256",u,t,w,32))}#s(){return Buffer.from(f("sha256",u,E,A,32))}#f(t,e,r){const s=y(12),n=d("aes-256-gcm",t,s);r&&n.setAAD(Buffer.from(r,"utf8"));const i=Buffer.concat([n.update(JSON.stringify(e),"utf8"),n.final()]);return{iv:s.toString("base64"),tag:n.getAuthTag().toString("base64"),data:i.toString("base64")}}#n(t,e,r){const s=l("aes-256-gcm",t,Buffer.from(e.iv,"base64"));r&&s.setAAD(Buffer.from(r,"utf8")),s.setAuthTag(Buffer.from(e.tag,"base64"));const n=Buffer.concat([s.update(Buffer.from(e.data,"base64")),s.final()]).toString("utf8");return JSON.parse(n)}}const K=new v;export{g as ELICITATION_TTL_MINUTES,h as ELICITATION_TTL_MS,v as SecretStore,K as secretStore};
@@ -1 +1 @@
1
- import{randomBytes as r}from"node:crypto";const i=32,S=/^[A-Za-z0-9_-]{43}$/,c=1440*60,u=720*60*60,s="anon:",d="user:",f="This API requires credentials and the caller is anonymous; call createAnonymousSession first, then retry with sessionHandle",a="The sessionHandle is not a valid handle; call createAnonymousSession to obtain one";function N(){return r(i).toString("base64url")}function E(e){return S.test(e)}function l(e){if(!e?.isAuthenticated)return;const n=e.claims,o=(typeof n?.sub=="string"&&n.sub?n.sub:void 0)??(e.email||void 0);if(o)return`${d}${e.idpId??"local"}:${o}`}function A(e,n){const t=l(e);return t?{kind:"resolved",subject:t}:n===void 0?{kind:"anonymous"}:E(n)?{kind:"resolved",subject:`${s}${n}`}:{kind:"invalid",message:a}}function T(e){return e.startsWith(s)?c:u}export{s as ANONYMOUS_SUBJECT_PREFIX,a as MALFORMED_SESSION_HANDLE_MESSAGE,f as MISSING_SESSION_HANDLE_MESSAGE,d as USER_SUBJECT_PREFIX,N as createSessionHandle,E as isValidSessionHandle,A as resolveSecretSubject,T as secretTtlSeconds,l as userSubject};
1
+ import{randomBytes as r}from"node:crypto";const i=32,S=/^[A-Za-z0-9_-]{43}$/,c=1440*60,u=10080*60,s="anon:",d="user:",f="This API requires credentials and the caller is anonymous; call createAnonymousSession first, then retry with sessionHandle",a="The sessionHandle is not a valid handle; call createAnonymousSession to obtain one";function N(){return r(i).toString("base64url")}function E(e){return S.test(e)}function l(e){if(!e?.isAuthenticated)return;const n=e.claims,o=(typeof n?.sub=="string"&&n.sub?n.sub:void 0)??(e.email||void 0);if(o)return`${d}${e.idpId??"local"}:${o}`}function A(e,n){const t=l(e);return t?{kind:"resolved",subject:t}:n===void 0?{kind:"anonymous"}:E(n)?{kind:"resolved",subject:`${s}${n}`}:{kind:"invalid",message:a}}function T(e){return e.startsWith(s)?c:u}export{s as ANONYMOUS_SUBJECT_PREFIX,a as MALFORMED_SESSION_HANDLE_MESSAGE,f as MISSING_SESSION_HANDLE_MESSAGE,d as USER_SUBJECT_PREFIX,N as createSessionHandle,E as isValidSessionHandle,A as resolveSecretSubject,T as secretTtlSeconds,l as userSubject};
@@ -26,6 +26,8 @@ export type UpstreamApiAuth = {
26
26
  defaultScheme: string;
27
27
  routes: UpstreamAuthRoute[];
28
28
  descriptions?: Record<string, string>;
29
+ /** Query parameter names of the `apiKey` schemes with `in: query`. */
30
+ credentialQueryParams: string[];
29
31
  };
30
32
  type SecuritySchemeLoader = typeof getApiDescriptionFromFs;
31
33
  export declare const DEFAULT_SCHEME_NAME = "default";
@@ -1,2 +1,2 @@
1
- import{getApiDescriptionFromFs as S}from"../docs-mcp/tools/openapi/utils.js";import{OPERATION_METHODS as b}from"../docs-mcp/tools/openapi/constants.js";const A=120,l="default",g={kind:"raw"},h=["oauth2ClientCredentials","bearer","apiKey","cookie","raw"],E=new Set(b),j=/\{(?<!\{[^}]*?\{)[^}]*\}/g;function c(){return{schemes:{[l]:g},defaultScheme:l,routes:[]}}async function M(e,t,n=S){const r=Object.values(e.apiDescriptionsMap??{}).find(i=>i.relativePath===t);if(!r)return c();try{const i=await n({relativePath:r.relativePath,outdir:e.outdir||"",user:e.user,rbac:e.config?.access?.rbac||{},requiresLogin:!!e.config?.access?.requiresLogin});return i?w(i,r.servers??[]):c()}catch{return c()}}function w(e,t){const n=x(e),r=Object.keys(n);if(r.length===0)return c();const i=m(e.security,n)[0]??a(r,n)[0],o=C(e,r);return{schemes:n,defaultScheme:i,routes:H(e,n,t),...Object.keys(o).length>0?{descriptions:o}:{}}}function L(e){return[...new Set(Object.values(e).map(P))]}function N(e){return a(Object.keys(e),e)}function U(e,t){const n=new Set;for(const r of e.routes)r.schemes.includes(t)&&n.add(`${r.method} ${r.path}`);return[...n]}function P(e){return e.kind==="apiKey"?e.header:e.kind==="cookie"?"Cookie":"Authorization"}function C(e,t){const n={};for(const r of t){const i=e.components?.securitySchemes?.[r],o=O(i)?i.description:void 0;if(typeof o!="string")continue;const s=o.split(`
2
- `)[0].trim();s&&(n[r]=s.slice(0,A))}return n}function x(e){const t={};for(const[n,r]of Object.entries(e.components?.securitySchemes??{})){if(!O(r))continue;const i=T(r);i&&(t[n]=i)}return t}function T(e){if(e.type==="oauth2"){const t=e.flows?.clientCredentials;return t?{kind:"oauth2ClientCredentials",tokenUrl:t.tokenUrl,scopes:Object.keys(t.scopes??{})}:{kind:"bearer"}}if(e.type==="http"&&e.scheme==="bearer")return{kind:"bearer"};if(e.type==="apiKey"&&e.in==="header")return{kind:"apiKey",header:e.name||"X-Api-Key"};if(e.type==="apiKey"&&e.in==="cookie"&&e.name)return{kind:"cookie",name:e.name}}function a(e,t){return[...e].sort((n,r)=>h.indexOf(t[n].kind)-h.indexOf(t[r].kind)||e.indexOf(n)-e.indexOf(r))}function m(e,t){const n=[];for(const r of e??[])for(const i of Object.keys(r))t[i]&&!n.includes(i)&&n.push(i);return n}function H(e,t,n){const r=_(n),i=[];for(const[o,s]of Object.entries(e.paths??{})){if(!s||typeof s!="object")continue;const f=R(r,o);if(f)for(const[p,k]of Object.entries(s)){if(!E.has(p.toLowerCase()))continue;const u=k?.security??e.security;if(u===void 0)continue;const d=m(u,t);d.length===0&&u.length>0||i.push({pattern:f,path:o,method:p.toUpperCase(),schemes:a(d,t)})}}return i}function R(e,t){if(t.startsWith("/"))return new RegExp(`^${e}${y(t)}/?$`)}function y(e){return e.split(j).map(v).join("[^/]+").replace(/(?<!\/)\/+$/,"")}function _(e){const t=[...new Set(e.map(n=>$(n.url)).filter(Boolean).map(y))];return t.length>0?`(?:${t.join("|")})?`:""}function $(e){if(!e)return"";const t=e.replace(/^[a-z][a-z0-9+.-]*:\/\//i,""),n=t.indexOf("/");return n===-1?"":t.slice(n).split(/[?#]/)[0].replace(/(?<!\/)\/+$/,"")}function v(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function O(e){return typeof e=="object"&&e!==null&&"type"in e}export{l as DEFAULT_SCHEME_NAME,L as credentialHeadersOf,U as operationsForScheme,N as orderedSchemesOf,M as resolveUpstreamApiAuth};
1
+ import{getApiDescriptionFromFs as k}from"../docs-mcp/tools/openapi/utils.js";import{OPERATION_METHODS as b}from"../docs-mcp/tools/openapi/constants.js";import{basePathPrefix as S,operationHttpMethod as A,pathPattern as C}from"./route-pattern.js";const P=120,l="default",j={kind:"raw"},y=["oauth2ClientCredentials","bearer","apiKey","cookie","raw"],E=new Set(b);function c(){return{schemes:{[l]:j},defaultScheme:l,routes:[],credentialQueryParams:[]}}async function q(e,n,t=k){const r=Object.values(e.apiDescriptionsMap??{}).find(o=>o.relativePath===n);if(!r)return c();try{const o=await t({relativePath:r.relativePath,outdir:e.outdir||"",user:e.user,rbac:e.config?.access?.rbac||{},requiresLogin:!!e.config?.access?.requiresLogin});return o?w(o,r.servers??[]):c()}catch{return c()}}function w(e,n){const t=K(e),r=Object.keys(t),o=g(e);if(r.length===0)return{...c(),credentialQueryParams:o};const s=h(e.security,t)[0]??a(r,t)[0],i=v(e,r);return{schemes:t,defaultScheme:s,routes:T(e,t,n),credentialQueryParams:o,...Object.keys(i).length>0?{descriptions:i}:{}}}function g(e){const n=new Set;for(const t of Object.values(e.components?.securitySchemes??{}))f(t)&&t.type==="apiKey"&&t.in==="query"&&t.name&&n.add(t.name);return[...n]}function L(e){return[...new Set(Object.values(e).map(H))]}function R(e){return a(Object.keys(e),e)}function N(e,n){const t=new Set;for(const r of e.routes)r.schemes.includes(n)&&t.add(`${r.method} ${r.path}`);return[...t]}function H(e){return e.kind==="apiKey"?e.header:e.kind==="cookie"?"Cookie":"Authorization"}function v(e,n){const t={};for(const r of n){const o=e.components?.securitySchemes?.[r],s=f(o)?o.description:void 0;if(typeof s!="string")continue;const i=s.split(`
2
+ `)[0].trim();i&&(t[r]=i.slice(0,P))}return t}function K(e){const n={};for(const[t,r]of Object.entries(e.components?.securitySchemes??{})){if(!f(r))continue;const o=M(r);o&&(n[t]=o)}return n}function M(e){if(e.type==="oauth2"){const n=e.flows?.clientCredentials;return n?{kind:"oauth2ClientCredentials",tokenUrl:n.tokenUrl,scopes:Object.keys(n.scopes??{})}:{kind:"bearer"}}if(e.type==="openIdConnect")return{kind:"bearer"};if(e.type==="http"&&e.scheme?.toLowerCase()==="bearer")return{kind:"bearer"};if(e.type==="apiKey"&&e.in==="header")return{kind:"apiKey",header:e.name||"X-Api-Key"};if(e.type==="apiKey"&&e.in==="cookie"&&e.name)return{kind:"cookie",name:e.name}}function a(e,n){return[...e].sort((t,r)=>y.indexOf(n[t].kind)-y.indexOf(n[r].kind)||e.indexOf(t)-e.indexOf(r))}function h(e,n){const t=[];for(const r of e??[])for(const o of Object.keys(r))n[o]&&!t.includes(o)&&t.push(o);return t}function T(e,n,t){const r=S(t),o=[];for(const[s,i]of Object.entries(e.paths??{})){if(!i||typeof i!="object")continue;const p=C(r,s);if(p)for(const[d,O]of Object.entries(i)){if(!E.has(d.toLowerCase()))continue;const u=O?.security??e.security;if(u===void 0)continue;const m=h(u,n);m.length===0&&u.length>0||o.push({pattern:p,path:s,method:A(d),schemes:a(m,n)})}}return o}function f(e){return typeof e=="object"&&e!==null&&"type"in e}export{l as DEFAULT_SCHEME_NAME,L as credentialHeadersOf,N as operationsForScheme,R as orderedSchemesOf,q as resolveUpstreamApiAuth};
@@ -8,8 +8,6 @@ export type UpstreamAuthRequiredEvent = {
8
8
  schemes?: string[];
9
9
  /** Scheme whose stored credential the bridge actually sent; absent when nothing was injected. */
10
10
  sentScheme?: string;
11
- /** Lower-cased credential header the sandbox code set itself, which suppressed the injection. */
12
- callerHeader?: string;
13
11
  /** The upstream's own explanation of the failure, taken from the response body. */
14
12
  detail?: string;
15
13
  };
@@ -1 +1 @@
1
- const i="com.redocly/upstream-auth-required";function m(e){return`${e.api}\0${e.scheme??""}`}function h(e,n){const t=new Map;for(const o of n){const s=m(o),a=t.get(s);(!a||u(o)>u(a))&&t.set(s,o)}const r=[...t.values()];return r.length===0?e:{...e,_meta:{...e._meta,[i]:r}}}function u(e){return e.status!==401?0:e.sentScheme?2:1}function y(e){const n=e._meta?.[i];return Array.isArray(n)?n.filter(t=>typeof t?.api=="string"&&typeof t?.name=="string"&&typeof t?.status=="number").map(t=>{const r=Array.isArray(t.schemes)?t.schemes.filter(f=>typeof f=="string"):[],{schemes:o,sentScheme:s,callerHeader:a,detail:c,...p}=t;return{...p,...r.length>0?{schemes:r}:{},...typeof s=="string"?{sentScheme:s}:{},...typeof a=="string"?{callerHeader:a}:{},...typeof c=="string"?{detail:c}:{}}}):[]}function d(e){if(!e._meta||!(i in e._meta))return e;const{[i]:n,...t}=e._meta,r={...e};return Object.keys(t).length>0?r._meta=t:delete r._meta,r}export{i as UPSTREAM_AUTH_REQUIRED_META,y as readAuthRequiredMeta,d as stripAuthRequiredMeta,m as upstreamAuthKey,h as withAuthRequiredMeta};
1
+ const i="com.redocly/upstream-auth-required";function f(e){return`${e.api}\0${e.scheme??""}`}function m(e,n){const t=new Map;for(const a of n){const s=f(a),o=t.get(s);(!o||u(a)>u(o))&&t.set(s,a)}const r=[...t.values()];return r.length===0?e:{...e,_meta:{...e._meta,[i]:r}}}function u(e){return e.status!==401?0:e.sentScheme?2:1}function h(e){const n=e._meta?.[i];return Array.isArray(n)?n.filter(t=>typeof t?.api=="string"&&typeof t?.name=="string"&&typeof t?.status=="number").map(t=>{const r=Array.isArray(t.schemes)?t.schemes.filter(p=>typeof p=="string"):[],{schemes:a,sentScheme:s,detail:o,...c}=t;return{...c,...r.length>0?{schemes:r}:{},...typeof s=="string"?{sentScheme:s}:{},...typeof o=="string"?{detail:o}:{}}}):[]}function y(e){if(!e._meta||!(i in e._meta))return e;const{[i]:n,...t}=e._meta,r={...e};return Object.keys(t).length>0?r._meta=t:delete r._meta,r}export{i as UPSTREAM_AUTH_REQUIRED_META,h as readAuthRequiredMeta,y as stripAuthRequiredMeta,f as upstreamAuthKey,m as withAuthRequiredMeta};
@@ -0,0 +1,7 @@
1
+ export declare const FORM_ACTIONS: {
2
+ readonly save: 'save';
3
+ readonly remove: 'remove';
4
+ readonly removeAll: 'remove-all';
5
+ readonly decline: 'decline';
6
+ };
7
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ const e={save:"save",remove:"remove",removeAll:"remove-all",decline:"decline"};export{e as FORM_ACTIONS};
@@ -0,0 +1,5 @@
1
+ import type { ElicitationScheme, ElicitationTarget } from '../../gateway-mcp/secret-store.js';
2
+ import type { SchemeField } from './types.js';
3
+ export declare function schemeFieldName(field: SchemeField, schemeIndex: number): string;
4
+ export declare function credentialSchemesOf(target: ElicitationTarget): ElicitationScheme[];
5
+ //# sourceMappingURL=form-contract.d.ts.map
@@ -0,0 +1 @@
1
+ function c(e,n){return`${e}__${n}`}function h(e){return e.schemes??[{name:e.scheme,auth:e.auth}]}export{h as credentialSchemesOf,c as schemeFieldName};
@@ -0,0 +1,14 @@
1
+ import type { ElicitationTarget, StoredSchemes } from '../../gateway-mcp/secret-store.js';
2
+ import type { FormError, PageContent, PageSite } from './types.js';
3
+ type FormPageInput = {
4
+ site: PageSite;
5
+ target: ElicitationTarget;
6
+ token: string;
7
+ stored: StoredSchemes;
8
+ error?: FormError;
9
+ /** Client IDs to put back into the OAuth2 fields after a rejected submission, by scheme index. */
10
+ clientIds?: readonly string[];
11
+ };
12
+ export declare function formPage(input: FormPageInput): PageContent;
13
+ export {};
14
+ //# sourceMappingURL=form-page.d.ts.map
@@ -0,0 +1,62 @@
1
+ import{withPathPrefix as D}from"@redocly/theme/core/utils";import{MCP_CREDENTIALS_ROUTE as R}from"../../../../constants/mcp.js";import{ELICITATION_TTL_MS as _}from"../../gateway-mcp/secret-store.js";import{secretTtlSeconds as I}from"../../gateway-mcp/subject.js";import{FORM_ACTIONS as $}from"./constants.js";import{credentialSchemesOf as F,schemeFieldName as M}from"./form-contract.js";import{ICONS as v,code as h,escapeHtml as r,siteHeader as N,targetTitle as A}from"./layout.js";const S=10,g="form-error",T=new Intl.DateTimeFormat("en-GB",{day:"numeric",month:"short",year:"numeric",timeZone:"UTC"}),P={bearer:{title:"API token",noun:"API token",placeholder:"Paste your API token"},apiKey:{title:"API key",noun:"API key",placeholder:"Paste your API key"},cookie:{title:"Cookie",noun:"cookie value",placeholder:"Paste the cookie value"},oauth2ClientCredentials:{title:"OAuth2",noun:"access token",placeholder:"Paste an access token"},raw:{title:"Authorization header",noun:"header value",placeholder:"Bearer <token>"}};function te(e){const{site:t,target:n,token:o,error:s}=e,a=D(R),u=F(n),i=u.length>1,c=L(e,u),l=c.some(f=>f.isStored),d=i&&l,m=c.map(f=>i?U(f):B(f)).join(""),p=d?'<button class="text-danger" type="submit" form="remove-all">Remove all credentials</button>':"",O=s?`<div class="banner" role="alert" id="${g}">${v.alert}<p>${r(s.message)}</p></div>`:"";return{title:`Credentials for ${A(n)}`,body:`<div class="page">
2
+ <main class="card">
3
+ ${N(t)}
4
+ <h1>${l?"Update":"Add"} credentials for ${r(A(n))}</h1>
5
+ <p class="lede">${i?"This API accepts more than one kind of credential. Fill in the one that you have.":"This API needs one credential to authenticate your calls."}</p>
6
+ ${O}
7
+ <form class="credentials" method="post" action="${a}" autocomplete="off">
8
+ ${y(o,$.save)}
9
+ <div class="schemes">${m}</div>
10
+ <div class="actions">
11
+ <div class="actions-row">${p}<button class="primary" type="submit">Save credentials</button></div>
12
+ <p class="decline small">Did not ask for this link? <button class="link-button" type="submit" form="decline">Decline it</button></p>
13
+ </div>
14
+ </form>
15
+ ${H(a,o,c,d)}
16
+ </main>
17
+ ${W(n,t.host)}
18
+ </div>`}}function L(e,t){const{target:n,stored:o,error:s,clientIds:a}=e,u=n.acceptedSchemes??[n.scheme],i=s?.schemeIndex??Math.max(0,t.findIndex(({name:l})=>u.includes(l))),c=t.length>1;return t.map((l,d)=>{const m=o[l.name],p=s?.schemeIndex===d?s:void 0;return{scheme:l,index:d,isMulti:c,title:c?l.name:P[l.auth.kind].title,savedAt:m?.savedAt,deletedAt:m?.savedAt===void 0?void 0:m.savedAt+I(n.subject)*1e3,isStored:m!==void 0,isOpen:d===i||u.includes(l.name)||m!==void 0,error:p,clientId:a?.[d]??"",autofocusField:d===i?p?.field??"secret":void 0}})}function B(e){return`<section class="scheme" aria-labelledby="scheme-${e.index}-title">
19
+ <div class="scheme-body">
20
+ <div class="scheme-head">
21
+ <h2 id="scheme-${e.index}-title">${r(e.title)}</h2>
22
+ <p>${E(e.scheme.auth)}</p>
23
+ </div>
24
+ ${C(e)}
25
+ </div>
26
+ </section>`}function U(e){const t=e.isStored?'<span class="tag stored">Stored</span>':'<span class="tag">Not set</span>';return`<details class="scheme" aria-labelledby="scheme-${e.index}-title"${e.isOpen?" open":""}>
27
+ <summary><span class="summary-title" id="scheme-${e.index}-title">${r(e.title)}</span><span class="summary-tag">${t}</span><span class="summary-line">${E(e.scheme.auth)}</span>${e.scheme.label?`<span class="summary-line">${r(e.scheme.label)}</span>`:""}</summary>
28
+ <div class="scheme-body">
29
+ ${C(e)}
30
+ </div>
31
+ </details>`}function C(e){const t=e.isStored?j(e):"",n=e.isMulti?K(e.scheme.operations):"";return e.scheme.auth.kind==="oauth2ClientCredentials"?`${t}${x(e,!1)}${n}${z(e)}`:`${t}${x(e,!e.isMulti&&!e.isStored)}${n}`}function j({savedAt:e,deletedAt:t,index:n,title:o}){return`<div class="stored-box">
32
+ <div>${e===void 0||t===void 0?"<p>A credential is stored for this scheme.</p>":`<p>Saved on ${T.format(e)}</p><p class="muted small">Kept until ${T.format(t)} at ${b(t)} UTC</p>`}</div>
33
+ <button class="text-danger small" type="submit" form="remove-${n}" aria-label="Remove the stored ${r(o)} credential">Remove</button>
34
+ </div>`}function x(e,t){const{auth:n}=e.scheme,{noun:o,placeholder:s}=P[n.kind];return k({view:e,field:"secret",type:"password",label:e.isStored?`Replace the ${o}`:Y(o),placeholder:e.isStored&&e.isMulti?"Leave empty to keep the stored value":s,hint:q(n),isRequired:t})}function z(e){return`<hr class="divider">
35
+ <div class="scheme-head">
36
+ <p class="strong">No token yet?</p>
37
+ <p>We exchange your client ID and secret for a token and store only the token.</p>
38
+ </div>
39
+ <div class="field-row">
40
+ ${k({view:e,field:"client_id",type:"text",label:"Client ID",placeholder:"Enter the client ID",value:e.clientId})}
41
+ ${k({view:e,field:"client_secret",type:"password",label:"Client secret",placeholder:"Enter the client secret"})}
42
+ </div>`}function k({view:e,field:t,type:n,label:o,placeholder:s,hint:a,value:u,isRequired:i}){const c=M(t,e.index),l=e.error!==void 0&&e.error.field===t,d=`${c}-hint`,m=[a?d:"",l?g:""].filter(Boolean).join(" "),p=[`type="${n}"`,`id="${c}"`,`name="${c}"`,`placeholder="${r(s)}"`,'autocomplete="off"','autocapitalize="off"','spellcheck="false"',u?`value="${r(u)}"`:"",m?`aria-describedby="${m}"`:"",l?'aria-invalid="true"':"",i?"required":"",e.autofocusField===t?"autofocus":""].filter(Boolean);return`<div class="field">
43
+ <label for="${c}">${r(o)}</label>
44
+ <input ${p.join(" ")}>
45
+ ${a?`<p class="muted small" id="${d}">${a}</p>`:""}
46
+ </div>`}function K(e){if(!e||e.length===0)return"";const t=e.slice(0,S).map(s=>`<li>${h(s)}</li>`).join(""),n=e.length-S,o=n>0?`<li class="muted">and ${n} more</li>`:"";return`<details class="operations"><summary>Which operations use it</summary><ul>${t}${o}</ul></details>`}function W({name:e,subject:t,expiresAt:n},o){const s=`${v.shield}What happens to your credentials`,a=`<ol class="steps">
47
+ <li>Sent straight to ${r(o)}, never through your AI client</li>
48
+ <li>Stored encrypted and never shown again</li>
49
+ <li>Added only to calls to ${r(e)} from the AI client that asked for this link</li>
50
+ <li>You can delete them anytime</li>
51
+ </ol>`;return`<aside class="panel" aria-labelledby="panel-title">
52
+ <div class="panel-steps"><h2 id="panel-title">${s}</h2>${a}</div>
53
+ <details class="panel-steps-toggle"><summary><h2>${s}</h2></summary>${a}</details>
54
+ <div class="link-facts">
55
+ <p>Link requested at ${b(n-_)} UTC</p>
56
+ <span class="pill">Open until ${b(n)} UTC</span>
57
+ <p>This link works one time.</p>
58
+ <p>Kept for ${V(I(t))} after you save them.</p>
59
+ <p>To delete them, ask your AI client for a new link.</p>
60
+ </div>
61
+ </aside>`}function H(e,t,n,o){const s=n.filter(i=>i.isStored).map(i=>`<form id="remove-${i.index}" method="post" action="${e}">${y(t,$.remove)}<input type="hidden" name="scheme" value="${i.index}"></form>`),a=o?`<form id="remove-all" method="post" action="${e}">${y(t,$.removeAll)}</form>`:"",u=`<form id="decline" method="post" action="${e}">${y(t,$.decline)}</form>`;return[...s,a,u].join(`
62
+ `)}function y(e,t){return`<input type="hidden" name="token" value="${r(e)}"><input type="hidden" name="action" value="${t}">`}function E(e){return e.kind==="apiKey"?`Sent to the API as the ${h(e.header)} header.`:e.kind==="cookie"?`Sent to the API as the ${h(e.name)} cookie.`:`Sent to the API as the ${h("Authorization")} header.`}function q(e){if(e.kind==="cookie")return`Paste only the value. We send it as ${h(`${e.name}=<value>`)}.`;if(e.kind==="raw")return`Paste the full header value, for example ${h("Bearer <token>")}.`}function b(e){return new Date(e).toISOString().slice(11,16)}function V(e){const t=e/86400;return Number.isInteger(t)&&t>1?`${t} days`:`${Math.round(e/3600)} hours`}function Y(e){return e.charAt(0).toUpperCase()+e.slice(1)}export{te as formPage};
@@ -0,0 +1,17 @@
1
+ import type { ElicitationTarget } from '../../gateway-mcp/secret-store.js';
2
+ import type { PageColorScheme, PageContent, PageSite } from './types.js';
3
+ export declare function escapeHtml(value: string): string;
4
+ /** The API name, plus the scheme when the link names one scheme of a multi-scheme API. */
5
+ export declare function targetTitle({ name, schemeLabel, schemes }: ElicitationTarget): string;
6
+ export declare function code(value: string): string;
7
+ export declare function htmlDocument({ title, body }: PageContent, colorScheme: PageColorScheme | undefined): string;
8
+ export declare function siteHeader({ name, logo, host, colorScheme }: PageSite): string;
9
+ export declare const ICONS: {
10
+ readonly shield: '<svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true"><path d="M10 2.5 3.75 4.8v4.7c0 3.9 2.6 6.9 6.25 8 3.65-1.1 6.25-4.1 6.25-8V4.8L10 2.5Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="m7.3 10 1.9 1.9 3.6-3.8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>';
11
+ readonly alert: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><circle cx="8" cy="8" r="6.25" stroke="currentColor" stroke-width="1.5"/><path d="m3.8 12.2 8.4-8.4" stroke="currentColor" stroke-width="1.5"/></svg>';
12
+ readonly warning: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M8 2.2 1.9 13h12.2L8 2.2Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="M8 6.5v3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="8" cy="11.3" r=".8" fill="currentColor"/></svg>';
13
+ readonly check: '<svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true"><path d="m4.5 9.2 3 3 6-6.4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
14
+ readonly cross: '<svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true"><path d="m5.5 5.5 7 7m0-7-7 7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>';
15
+ readonly clock: '<svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true"><circle cx="9" cy="9" r="5.75" stroke="currentColor" stroke-width="1.6"/><path d="M9 6v3.2l2 1.3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>';
16
+ };
17
+ //# sourceMappingURL=layout.d.ts.map
@@ -0,0 +1,11 @@
1
+ import{pageStyles as l}from"./styles.js";function o(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;")}function a({name:e,schemeLabel:r,schemes:t}){return t&&t.length>1?e:r?`${e} (${r})`:e}function h(e){return`<code>${o(e)}</code>`}function u({title:e,body:r},t){return`<!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="robots" content="noindex">
7
+ <title>${o(e)}</title>
8
+ <style>${l(t)}</style>
9
+ </head>
10
+ <body>${r}</body>
11
+ </html>`}function k({name:e,logo:r,host:t,colorScheme:i}){const n=r?d(r,e??t,i):`<span class="site-name">${o(e??t)}</span>`,s=r||e?`<span class="site-host">${o(t)}</span>`:"";return`<header class="site">${n}${s}</header>`}function d(e,r,t){const i=n=>`<img class="site-logo" src="${o(n)}" alt="${o(r)}">`;return!e.dark||t==="light"?i(e.light):t==="dark"?i(e.dark):`<picture><source srcset="${o(e.dark)}" media="(prefers-color-scheme: dark)">${i(e.light)}</picture>`}const p={shield:'<svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true"><path d="M10 2.5 3.75 4.8v4.7c0 3.9 2.6 6.9 6.25 8 3.65-1.1 6.25-4.1 6.25-8V4.8L10 2.5Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="m7.3 10 1.9 1.9 3.6-3.8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',alert:'<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><circle cx="8" cy="8" r="6.25" stroke="currentColor" stroke-width="1.5"/><path d="m3.8 12.2 8.4-8.4" stroke="currentColor" stroke-width="1.5"/></svg>',warning:'<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M8 2.2 1.9 13h12.2L8 2.2Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="M8 6.5v3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="8" cy="11.3" r=".8" fill="currentColor"/></svg>',check:'<svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true"><path d="m4.5 9.2 3 3 6-6.4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',cross:'<svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true"><path d="m5.5 5.5 7 7m0-7-7 7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>',clock:'<svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true"><circle cx="9" cy="9" r="5.75" stroke="currentColor" stroke-width="1.6"/><path d="M9 6v3.2l2 1.3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>'};export{p as ICONS,h as code,o as escapeHtml,u as htmlDocument,k as siteHeader,a as targetTitle};
@@ -0,0 +1,10 @@
1
+ import type { ElicitationTarget } from '../../gateway-mcp/secret-store.js';
2
+ import type { CredentialsOutcome, PageContent } from './types.js';
3
+ type Tone = 'success' | 'danger' | 'neutral' | 'warning';
4
+ export declare function outcomePage(target: ElicitationTarget, { action, schemes, stored }: CredentialsOutcome): PageContent;
5
+ export declare function declinedPage(): PageContent;
6
+ export declare function expiredPage(): PageContent;
7
+ /** A centered status card; `lines` are trusted HTML. */
8
+ export declare function statusPage(tone: Tone, title: string, lines: readonly string[]): PageContent;
9
+ export {};
10
+ //# sourceMappingURL=outcome-pages.d.ts.map
@@ -0,0 +1,11 @@
1
+ import{ELICITATION_TTL_MINUTES as p}from"../../gateway-mcp/secret-store.js";import{credentialSchemesOf as $}from"./form-contract.js";import{ICONS as a,code as f,escapeHtml as r,targetTitle as k}from"./layout.js";const v={success:a.check,danger:a.cross,neutral:a.cross,warning:a.clock};function y(n,{action:e,schemes:o,stored:t}){const s=e==="saved",i=$(n).length>1,c=r(n.name),d=t.filter(m=>!o.includes(m)),g=i?`We ${e} ${u(o)} for ${c}.`:`We ${e} your credentials for ${r(k(n))}.`,h=t.length===0?`Calls to ${c} now go without credentials.`:d.length>0?`${s?"Also stored":"Still stored"}: ${u(d)}.`:"";return l(s?"success":"danger",s?"Credentials saved":"Credentials removed",[g,h,s?"Go back to your AI client and tell it that you saved them.":""])}function S(){return l("neutral","Link declined",["We saved nothing, and this link no longer works.","If you did not ask for this link, tell your AI client to stop."])}function A(){return l("warning","This link no longer works",[`The link is not valid, was already used, or is older than ${p} minutes.`,"Ask your AI client for a new link."])}function l(n,e,o){const t=o.filter(Boolean).map(i=>`<p>${i}</p>`).join(`
2
+ `),s=n==="warning"?"":'<p class="muted small">You can close this tab.</p>';return{title:e,body:`<div class="page narrow">
3
+ <main class="card outcome">
4
+ <span class="status-icon ${n}">${v[n]}</span>
5
+ <h1>${r(e)}</h1>
6
+ <div class="message">
7
+ ${t}
8
+ ${s}
9
+ </div>
10
+ </main>
11
+ </div>`}}function u(n){const e=n.map(f);return e.length>1?`${e.slice(0,-1).join(", ")} and ${e.at(-1)}`:e.join("")}export{S as declinedPage,A as expiredPage,y as outcomePage,l as statusPage};