@redocly/realm 0.136.0-next.9 → 0.136.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/CHANGELOG.md +207 -0
  2. package/dist/client/App.js +1 -1
  3. package/dist/client/ScriptLoader.js +1 -1
  4. package/dist/client/TestProvider.js +1 -1
  5. package/dist/client/app/Link.js +1 -1
  6. package/dist/client/app/Sidebar/useBreadcrumbs.js +1 -1
  7. package/dist/client/app/Sidebar/useSidebarItems.js +1 -1
  8. package/dist/client/app/Sidebar/utils.d.ts +1 -1
  9. package/dist/client/app/hooks/catalog/useCatalogClassic.js +1 -1
  10. package/dist/client/app/hooks/catalog/useCatalogFilter.js +1 -1
  11. package/dist/client/app/hooks/catalog/useCatalogSearch.js +1 -1
  12. package/dist/client/app/hooks/catalog/useCatalogSort.js +1 -1
  13. package/dist/client/app/hooks/products/useCurrentProduct.js +1 -1
  14. package/dist/client/app/hooks/useAutoScroll.js +1 -1
  15. package/dist/client/app/hooks/useBanner.js +1 -1
  16. package/dist/client/app/hooks/useL10nConfig.js +1 -1
  17. package/dist/client/app/hooks/useLoginUrl.js +1 -1
  18. package/dist/client/app/hooks/usePageTimeTracker.js +1 -1
  19. package/dist/client/app/hooks/useRouteChangeTracker.js +1 -1
  20. package/dist/client/app/markdoc/custom-components/openapi/replay-openapi.js +1 -1
  21. package/dist/client/app/pages/DevLogin/DevLogin.js +1 -1
  22. package/dist/client/app/search/useSearch.d.ts +1 -0
  23. package/dist/client/app/search/useSearch.js +1 -1
  24. package/dist/client/app/seo/hooks/useHreflangLinks.js +1 -1
  25. package/dist/client/app/useMobileMenu.js +1 -1
  26. package/dist/client/app/usePreloadHistory.d.ts +1 -1
  27. package/dist/client/app/usePreloadHistory.js +1 -1
  28. package/dist/client/app/utils/loadAndNavigate.d.ts +1 -1
  29. package/dist/client/browser-entry.js +2 -2
  30. package/dist/client/providers/hooks.d.ts +1 -1
  31. package/dist/client/providers/hooks.js +1 -1
  32. package/dist/client/providers/page-data/hooks.js +1 -1
  33. package/dist/client/providers/post-message/PostMessageProvider.js +1 -1
  34. package/dist/constants/api.d.ts +2 -0
  35. package/dist/constants/api.js +1 -1
  36. package/dist/server/constants/common.d.ts +2 -0
  37. package/dist/server/constants/common.js +1 -1
  38. package/dist/server/constants/plugins/search.d.ts +1 -1
  39. package/dist/server/constants/plugins/search.js +1 -1
  40. package/dist/server/esbuild/esbuild.js +1 -1
  41. package/dist/server/esbuild/plugins/themes-resolver.js +4 -2
  42. package/dist/server/plugins/dev-onboarding/template/App.js +1 -1
  43. package/dist/server/plugins/dev-onboarding/template/AppsList.js +1 -1
  44. package/dist/server/plugins/dev-onboarding/template/components/ApiKeyItem.js +1 -1
  45. package/dist/server/plugins/dev-onboarding/template/components/ApiLogDetails.js +1 -1
  46. package/dist/server/plugins/dev-onboarding/template/components/ApiLogs.js +1 -1
  47. package/dist/server/plugins/dev-onboarding/template/components/CreateApiKey.js +1 -1
  48. package/dist/server/plugins/dev-onboarding/template/components/DeleteAppDialog.js +1 -1
  49. package/dist/server/plugins/dev-onboarding/template/components/EditApis.js +1 -1
  50. package/dist/server/plugins/dev-onboarding/template/components/EditAppDescriptionDialog.js +1 -1
  51. package/dist/server/plugins/dev-onboarding/template/components/EditAppDialog.js +1 -1
  52. package/dist/server/plugins/dev-onboarding/template/components/EditCallbackUrlDialog.js +1 -1
  53. package/dist/server/plugins/dev-onboarding/template/components/RevokeAPIKey.js +1 -1
  54. package/dist/server/plugins/dev-onboarding/template/components/RollAPIKey.js +1 -1
  55. package/dist/server/plugins/dev-onboarding/template/components/UpdateApiKey.js +1 -1
  56. package/dist/server/plugins/dev-onboarding/template/index.js +1 -1
  57. package/dist/server/plugins/mcp/audit/audit-log.d.ts +36 -0
  58. package/dist/server/plugins/mcp/audit/audit-log.js +1 -0
  59. package/dist/server/plugins/mcp/audit/constants.d.ts +6 -0
  60. package/dist/server/plugins/mcp/audit/constants.js +1 -0
  61. package/dist/server/plugins/mcp/audit/secret-patterns.d.ts +2 -0
  62. package/dist/server/plugins/mcp/audit/secret-patterns.js +1 -0
  63. package/dist/server/plugins/mcp/codemode/build-tool-list.d.ts +12 -0
  64. package/dist/server/plugins/mcp/codemode/build-tool-list.js +2 -0
  65. package/dist/server/plugins/mcp/codemode/capabilities/fetch/index.js +4 -4
  66. package/dist/server/plugins/mcp/codemode/capabilities/types.d.ts +1 -0
  67. package/dist/server/plugins/mcp/codemode/capabilities/utils.js +3 -3
  68. package/dist/server/plugins/mcp/codemode/code-execution-audit.d.ts +17 -0
  69. package/dist/server/plugins/mcp/codemode/code-execution-audit.js +1 -0
  70. package/dist/server/plugins/mcp/codemode/constants.js +1 -1
  71. package/dist/server/plugins/mcp/codemode/prompts.d.ts +6 -3
  72. package/dist/server/plugins/mcp/codemode/prompts.js +7 -4
  73. package/dist/server/plugins/mcp/codemode/sandbox/create-sandbox-setup.js +1 -1
  74. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/fetch-shim.d.ts +6 -0
  75. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/fetch-shim.js +54 -0
  76. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/form-data.d.ts +3 -0
  77. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/form-data.js +17 -0
  78. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/index.d.ts +3 -0
  79. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/index.js +2 -0
  80. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/url-search-params.d.ts +3 -0
  81. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/url-search-params.js +64 -0
  82. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/url.d.ts +3 -0
  83. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/url.js +239 -0
  84. package/dist/server/plugins/mcp/codemode/tools/describe-tools.js +1 -1
  85. package/dist/server/plugins/mcp/codemode/tools/execute.js +1 -1
  86. package/dist/server/plugins/mcp/codemode/tools/manage-credentials.d.ts +19 -0
  87. package/dist/server/plugins/mcp/codemode/tools/manage-credentials.js +2 -0
  88. package/dist/server/plugins/mcp/codemode/tools/tool-errors.d.ts +4 -0
  89. package/dist/server/plugins/mcp/codemode/tools/tool-errors.js +1 -0
  90. package/dist/server/plugins/mcp/constants.d.ts +1 -0
  91. package/dist/server/plugins/mcp/constants.js +1 -1
  92. package/dist/server/plugins/mcp/docs-mcp/tool-schemas.js +1 -1
  93. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/get-endpoint-info.js +1 -1
  94. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/get-endpoints.js +1 -1
  95. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/get-full-api-description.js +1 -1
  96. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/get-security-schemes.js +1 -1
  97. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/list-apis.js +1 -1
  98. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/load-api-description.d.ts +5 -1
  99. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/load-api-description.js +1 -1
  100. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/types.d.ts +2 -0
  101. package/dist/server/plugins/mcp/docs-mcp/utils.js +1 -1
  102. package/dist/server/plugins/mcp/gateway-mcp/elicitation.d.ts +35 -15
  103. package/dist/server/plugins/mcp/gateway-mcp/elicitation.js +3 -2
  104. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-audit.d.ts +24 -0
  105. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-audit.js +1 -0
  106. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-bridge.js +1 -1
  107. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/apply-edge-rate-limit.js +1 -1
  108. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/edge-limiter.d.ts +2 -2
  109. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/edge-limiter.js +1 -1
  110. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/window-counter.d.ts +2 -2
  111. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/window-counter.js +1 -1
  112. package/dist/server/plugins/mcp/gateway-mcp/secret-store.d.ts +3 -0
  113. package/dist/server/plugins/mcp/gateway-mcp/secret-store.js +1 -1
  114. package/dist/server/plugins/mcp/gateway-mcp/upstream-auth.d.ts +1 -0
  115. package/dist/server/plugins/mcp/gateway-mcp/upstream-auth.js +1 -1
  116. package/dist/server/plugins/mcp/handlers/mcp-credentials-handler.js +47 -24
  117. package/dist/server/plugins/mcp/servers/docs-server.js +1 -1
  118. package/dist/server/plugins/mcp/servers/mcp-server.d.ts +1 -0
  119. package/dist/server/plugins/mcp/servers/mcp-server.js +3 -1
  120. package/dist/server/plugins/mcp/workers/execute-mcp-tool.js +1 -1
  121. package/dist/server/plugins/mcp/workers/mcp-tool-telemetry.d.ts +11 -0
  122. package/dist/server/plugins/mcp/workers/mcp-tool-telemetry.js +1 -1
  123. package/dist/server/plugins/scorecard-classic/template/ApiDetailsPage.js +1 -1
  124. package/dist/server/plugins/scorecard-classic/template/RuleDetailsPage.js +1 -1
  125. package/dist/server/plugins/scorecard-classic/template/index.js +1 -1
  126. package/dist/server/plugins/scorecard-classic/template/utils.d.ts +1 -1
  127. package/dist/server/plugins/scorecard-classic/template/views.js +1 -1
  128. package/dist/server/plugins/search/engines/typesense/index.js +1 -1
  129. package/dist/server/ssr/render.js +1 -1
  130. package/dist/server/tools/notifiers/formatter.d.ts +1 -0
  131. package/dist/server/tools/notifiers/formatter.js +3 -3
  132. package/dist/server/tools/notifiers/logger.d.ts +1 -0
  133. package/dist/server/tools/notifiers/logger.js +2 -2
  134. package/dist/server/types/plugin-hooks.d.ts +1 -1
  135. package/dist/server/utils/llmstxt/agent-feedback-llms-footer.js +1 -1
  136. package/dist/server/web-server/routes/ask-ai.d.ts +11 -0
  137. package/dist/server/web-server/routes/ask-ai.js +1 -1
  138. package/dist/server/web-server/routes/index.js +1 -1
  139. package/dist/server/web-server/routes/mcp-routes/mcp-server-card.js +1 -1
  140. package/dist/types/api.d.ts +3 -0
  141. package/package.json +20 -20
  142. package/dist/server/plugins/mcp/codemode/sandbox/polyfills.d.ts +0 -2
  143. package/dist/server/plugins/mcp/codemode/sandbox/polyfills.js +0 -99
@@ -1 +1 @@
1
- import{shouldHandleMcpAuth as d}from"../auth/auth-handlers.js";import{CREATE_ANONYMOUS_SESSION_TOOL_NAME as m}from"../constants.js";import{GRAPHQL_TYPE_KINDS as o,GRAPHQL_LIST_SCOPES as h}from"./tools/graphql/constants.js";import{OPERATION_METHODS as p}from"./tools/openapi/constants.js";const i={type:"string",nullable:!0},n={limit:{type:"number"},total:{type:"number"},page:{type:"number"},totalPages:{type:"number"}},r={codemodeType:"OpenApiServer[]"},s={codemodeType:"OpenApiSecurityRequirement[]"},l={codemodeType:"Record<string, OpenApiSecurityScheme>"},y={codemodeType:"OpenApiDocument"},f={codemodeType:"OpenApiOperation & { path: string; method: string }"},g={type:"object",additionalProperties:!1,required:[],properties:{filter:{type:"string",description:"API name (or part of it)",minLength:1,nullable:!0},page:{type:"number",description:"Page number",minimum:1,default:1,nullable:!0},limit:{type:"number",description:"Number of APIs per page. Default is 300",minimum:1,default:300,nullable:!0}}},a={type:"object",required:["name"],additionalProperties:!1,properties:{name:{type:"string",description:"API name",minLength:1},version:{type:"string",description:"API version (required when multiple APIs share the same name but differ by version)",nullable:!0}}},b={type:"object",required:["name","path","method"],additionalProperties:!1,properties:{name:{type:"string",description:"API name",minLength:1},path:{type:"string",description:"Endpoint path (e.g. /api/v1/users)",minLength:1},method:{type:"string",description:"HTTP method (GET, POST, PUT, DELETE, etc.)",enum:[...p.map(e=>e.toUpperCase()),...p],minLength:1},version:{type:"string",description:"API version (required when multiple APIs share the same name but differ by version)",nullable:!0}}},S={type:"object",required:[],additionalProperties:!1,properties:{}},v={type:"object",required:[],additionalProperties:!1,properties:{}};function I(e){const t=[{name:"list-apis",description:"Lists available APIs with their context and purpose",schema:g,annotations:{title:"List APIs",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{items:{type:"array",items:{type:"object",properties:{name:{type:"string"},description:i,version:i,servers:r}}},...n}}},{name:"get-endpoints",description:"Get all endpoints for a specific API",schema:a,annotations:{title:"Get Endpoints",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{api:{type:"string"},version:{type:"string"},servers:r,endpoints:{type:"array",items:{type:"object",properties:{path:{type:"string"},method:{type:"string"},summary:i,description:i,security:s}}}}}},{name:"get-endpoint-info",description:"Get comprehensive information about specific endpoint including parameters, security, and examples",schema:b,annotations:{title:"Get Endpoint Info",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{api:{type:"string"},version:{type:"string"},servers:r,endpoint:f,globalSecurity:s,securitySchemes:l}}},{name:"get-security-schemes",description:"Get the security schemes for a specific API",schema:a,annotations:{title:"Get Security Schemes",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{name:i,version:i,securitySchemes:l,security:s}}},{name:"get-full-api-description",description:"Get the complete OpenAPI description",schema:a,annotations:{title:"Get Full API Description",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{api:{type:"string"},version:{type:"string"},definition:y}}},q(e.products,e.locales)];return d(e.requiresLogin,e.rbac)&&t.push({name:"whoami",description:"Get information about the currently authenticated user",schema:S,annotations:{title:"Who Am I",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{email:{type:"string"},name:{type:"string"},subject:{type:"string"},clientId:{type:"string"},scope:{type:"string"},issuedAt:{type:"string"},expiresAt:{type:"string"}}}}),e.hasGraphqlApis&&t.push(...T()),e.elicitationEnabled&&t.push({name:m,description:"Create an anonymous session handle for storing upstream API credentials. Pass the returned sessionHandle to execute when an API needs credentials and the caller is not signed in. Treat it as a secret and reuse it across calls.",schema:v,annotations:{title:"Create anonymous session",readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1},outputSchema:{type:"object",required:["sessionHandle"],properties:{sessionHandle:{type:"string"}}}}),t}const H={type:"object",required:[],additionalProperties:!1,properties:{filter:{type:"string",description:"GraphQL schema name (or part of it)",minLength:1,nullable:!0},page:{type:"number",description:"Page number",minimum:1,default:1,nullable:!0},limit:{type:"number",description:"Number of results per page. Default is 300",minimum:1,default:300,nullable:!0}}},L={type:"object",required:["name"],additionalProperties:!1,properties:{name:{type:"string",description:"GraphQL schema name",minLength:1},kind:{type:"string",description:'Optional: return only a single category of schema members. With scope "fields", restricts to the fields of that parent category ("objects", "interfaces", or "inputs" only).',enum:[...o],nullable:!0},scope:{type:"string",description:'Optional: what each list item is. "members" (default) lists top-level types and operations. "fields" flattens every field of every object/interface/input type into "ParentType.fieldName" entries with their descriptions \u2014 combine with `filter` to search nested fields anywhere in the schema.',enum:[...h],nullable:!0},filter:{type:"string",description:'Optional: only members whose name or description contains this substring, case-insensitive (e.g. "custom tags")',minLength:1,nullable:!0},referencesType:{type:"string",description:'Optional: only members that reference this type directly or transitively (via return, field, argument, union-member or input-field types). Finds cross-cutting members that never name it \u2014 e.g. referencesType "Book" surfaces a search query returning a union that includes Book.',minLength:1,nullable:!0},page:{type:"number",description:"Page number",minimum:1,default:1,nullable:!0},limit:{type:"number",description:"Number of results per page. Default is 300",minimum:1,default:300,nullable:!0}}},P={type:"object",required:["name","type"],additionalProperties:!1,properties:{name:{type:"string",description:"GraphQL schema name",minLength:1},type:{type:"string",description:"Name of the type, operation, or directive",minLength:1},includeReferencedTypes:{type:"boolean",description:"Also include the SDL of every type referenced transitively. Gives the whole subgraph at once, but can be large for highly-connected types.",default:!1,nullable:!0}}},A={type:"object",required:["name"],additionalProperties:!1,properties:{name:{type:"string",description:"GraphQL schema name",minLength:1}}};function T(){return[{name:"list-graphql-apis",description:"Lists available GraphQL schemas with their context and purpose",schema:H,annotations:{title:"List GraphQL APIs",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{items:{type:"array",items:{type:"object",properties:{name:{type:"string"},description:i}}},...n}}},{name:"get-graphql-type-list",description:'List all operations and types in a GraphQL schema as a flat, paginated list. Each item is tagged with its kind (query, object, enum, \u2026) and carries a one-line description; operations also include their return type and argument signature \u2014 use it to decide which members to fetch in detail. Narrow the result with `kind` (one category), `filter` (case-insensitive substring of the member name or description, e.g. "book"), or `referencesType` (only members that use a given type, directly or transitively) to explore a domain cheaply instead of pulling the full schema. To search nested fields \u2014 not just type names \u2014 set scope "fields": it flattens every field of every object/interface/input type into "ParentType.fieldName" entries with their descriptions, so `filter` finds things like an input field whose docstring mentions your keyword.',schema:L,annotations:{title:"Get GraphQL Type List",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{name:{type:"string"},items:{type:"array",items:{type:"object",properties:{kind:{type:"string",enum:o},name:{type:"string"},description:i,type:{type:"string"},args:{type:"string"},directives:{type:"string"}}}},...n}}},{name:"get-graphql-type",description:'Get the SDL for a single GraphQL type, operation (query/mutation/subscription field), or directive. Without includeReferencedTypes, the SDL is followed by a one-level manifest of every directly referenced type\'s field names (e.g. "input ActionOperationInput { addTags, removeTags }"), so you can see what nested types contain before fetching them. Set includeReferencedTypes to instead pull in the full SDL of every type it references transitively \u2014 the whole structural subgraph in one call, ideal for complete exploration of an entity (e.g. Book and all the types it touches). That traversal only follows what the target depends on; to go the other direction and find the members that use a type, call get-graphql-type-list with `referencesType`.',schema:P,annotations:{title:"Get GraphQL Type",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"string"}},{name:"get-graphql-schema",description:"Get the complete GraphQL schema as SDL \u2014 the whole type graph in one call. Returns everything, so it can be very large and token-expensive for big schemas; prefer get-graphql-type-list (filter/paginate) plus targeted get-graphql-type calls, and reach for this only for small schemas or when you truly need the full SDL.",schema:A,annotations:{title:"Get GraphQL Schema",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"string"}}]}function q(e,t){const c=e&&e.length>0,u=t&&t.length>0;return{name:"search",description:"Search across the documentation to fetch relevant content for a given query",schema:{type:"object",required:["query"],additionalProperties:!1,properties:{query:{type:"string",description:"Search query. Should be a single word or that phrase that is presented in a documentation.",minLength:1},...c?{product:{type:"string",description:"Optional product name to filter search results by specific product.",enum:e,nullable:!0}}:{},...u?{locale:{type:"string",description:"Optional locale code to filter search results by documentation language (e.g. 'en', 'ja'). Use the locale matching the language of the user's request. Defaults to the project's default locale.",enum:t,nullable:!0}}:{}}},annotations:{title:"Search Documentation",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1},outputSchema:{type:"string"}}}export{I as getDefaultToolSchemas};
1
+ import{shouldHandleMcpAuth as d}from"../auth/auth-handlers.js";import{CREATE_ANONYMOUS_SESSION_TOOL_NAME as m}from"../constants.js";import{GRAPHQL_TYPE_KINDS as o,GRAPHQL_LIST_SCOPES as h}from"./tools/graphql/constants.js";import{OPERATION_METHODS as p}from"./tools/openapi/constants.js";const i={type:"string",nullable:!0},n={limit:{type:"number"},total:{type:"number"},page:{type:"number"},totalPages:{type:"number"}},r={codemodeType:"OpenApiServer[]"},s={codemodeType:"OpenApiSecurityRequirement[]"},l={codemodeType:"Record<string, OpenApiSecurityScheme>"},y={codemodeType:"OpenApiDocument"},f={codemodeType:"OpenApiOperation & { path: string; method: string }"},g={type:"object",additionalProperties:!1,required:[],properties:{filter:{type:"string",description:"API name (or part of it)",minLength:1,nullable:!0},page:{type:"number",description:"Page number",minimum:1,default:1,nullable:!0},limit:{type:"number",description:"Number of APIs per page. Default is 300",minimum:1,default:300,nullable:!0}}},a={type:"object",required:["name"],additionalProperties:!1,properties:{name:{type:"string",description:"API name",minLength:1},version:{type:"string",description:"API version",nullable:!0},filePath:{type:"string",description:"API definition file path",nullable:!0}}},b={type:"object",required:["name","path","method"],additionalProperties:!1,properties:{name:{type:"string",description:"API name",minLength:1},path:{type:"string",description:"Endpoint path (e.g. /api/v1/users)",minLength:1},method:{type:"string",description:"HTTP method (GET, POST, PUT, DELETE, etc.)",enum:[...p.map(e=>e.toUpperCase()),...p],minLength:1},version:{type:"string",description:"API version (required when multiple APIs share the same name but differ by version)",nullable:!0},filePath:{type:"string",description:"API definition file path",nullable:!0}}},S={type:"object",required:[],additionalProperties:!1,properties:{}},v={type:"object",required:[],additionalProperties:!1,properties:{}};function I(e){const t=[{name:"list-apis",description:"Lists available APIs with their context and purpose",schema:g,annotations:{title:"List APIs",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{items:{type:"array",items:{type:"object",properties:{name:{type:"string"},description:i,version:i,servers:r,filePath:{type:"string",description:"API definition file path"}}}},...n}}},{name:"get-endpoints",description:"Get all endpoints for a specific API",schema:a,annotations:{title:"Get Endpoints",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{api:{type:"string"},version:{type:"string"},servers:r,endpoints:{type:"array",items:{type:"object",properties:{path:{type:"string"},method:{type:"string"},summary:i,description:i,security:s}}}}}},{name:"get-endpoint-info",description:"Get comprehensive information about specific endpoint including parameters, security, and examples",schema:b,annotations:{title:"Get Endpoint Info",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{api:{type:"string"},version:{type:"string"},servers:r,endpoint:f,globalSecurity:s,securitySchemes:l}}},{name:"get-security-schemes",description:"Get the security schemes for a specific API",schema:a,annotations:{title:"Get Security Schemes",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{name:i,version:i,securitySchemes:l,security:s}}},{name:"get-full-api-description",description:"Get the complete OpenAPI description",schema:a,annotations:{title:"Get Full API Description",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{api:{type:"string"},version:{type:"string"},definition:y}}},G(e.products,e.locales)];return d(e.requiresLogin,e.rbac)&&t.push({name:"whoami",description:"Get information about the currently authenticated user",schema:S,annotations:{title:"Who Am I",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{email:{type:"string"},name:{type:"string"},subject:{type:"string"},clientId:{type:"string"},scope:{type:"string"},issuedAt:{type:"string"},expiresAt:{type:"string"}}}}),e.hasGraphqlApis&&t.push(...T()),e.elicitationEnabled&&t.push({name:m,description:"Create an anonymous session handle for storing upstream API credentials. Pass the returned sessionHandle to execute when an API needs credentials and the caller is not signed in. Treat it as a secret and reuse it across calls.",schema:v,annotations:{title:"Create anonymous session",readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1},outputSchema:{type:"object",required:["sessionHandle"],properties:{sessionHandle:{type:"string"}}}}),t}const H={type:"object",required:[],additionalProperties:!1,properties:{filter:{type:"string",description:"GraphQL schema name (or part of it)",minLength:1,nullable:!0},page:{type:"number",description:"Page number",minimum:1,default:1,nullable:!0},limit:{type:"number",description:"Number of results per page. Default is 300",minimum:1,default:300,nullable:!0}}},P={type:"object",required:["name"],additionalProperties:!1,properties:{name:{type:"string",description:"GraphQL schema name",minLength:1},kind:{type:"string",description:'Optional: return only a single category of schema members. With scope "fields", restricts to the fields of that parent category ("objects", "interfaces", or "inputs" only).',enum:[...o],nullable:!0},scope:{type:"string",description:'Optional: what each list item is. "members" (default) lists top-level types and operations. "fields" flattens every field of every object/interface/input type into "ParentType.fieldName" entries with their descriptions \u2014 combine with `filter` to search nested fields anywhere in the schema.',enum:[...h],nullable:!0},filter:{type:"string",description:'Optional: only members whose name or description contains this substring, case-insensitive (e.g. "custom tags")',minLength:1,nullable:!0},referencesType:{type:"string",description:'Optional: only members that reference this type directly or transitively (via return, field, argument, union-member or input-field types). Finds cross-cutting members that never name it \u2014 e.g. referencesType "Book" surfaces a search query returning a union that includes Book.',minLength:1,nullable:!0},page:{type:"number",description:"Page number",minimum:1,default:1,nullable:!0},limit:{type:"number",description:"Number of results per page. Default is 300",minimum:1,default:300,nullable:!0}}},A={type:"object",required:["name","type"],additionalProperties:!1,properties:{name:{type:"string",description:"GraphQL schema name",minLength:1},type:{type:"string",description:"Name of the type, operation, or directive",minLength:1},includeReferencedTypes:{type:"boolean",description:"Also include the SDL of every type referenced transitively. Gives the whole subgraph at once, but can be large for highly-connected types.",default:!1,nullable:!0}}},L={type:"object",required:["name"],additionalProperties:!1,properties:{name:{type:"string",description:"GraphQL schema name",minLength:1}}};function T(){return[{name:"list-graphql-apis",description:"Lists available GraphQL schemas with their context and purpose",schema:H,annotations:{title:"List GraphQL APIs",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{items:{type:"array",items:{type:"object",properties:{name:{type:"string"},description:i}}},...n}}},{name:"get-graphql-type-list",description:'List all operations and types in a GraphQL schema as a flat, paginated list. Each item is tagged with its kind (query, object, enum, \u2026) and carries a one-line description; operations also include their return type and argument signature \u2014 use it to decide which members to fetch in detail. Narrow the result with `kind` (one category), `filter` (case-insensitive substring of the member name or description, e.g. "book"), or `referencesType` (only members that use a given type, directly or transitively) to explore a domain cheaply instead of pulling the full schema. To search nested fields \u2014 not just type names \u2014 set scope "fields": it flattens every field of every object/interface/input type into "ParentType.fieldName" entries with their descriptions, so `filter` finds things like an input field whose docstring mentions your keyword.',schema:P,annotations:{title:"Get GraphQL Type List",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{name:{type:"string"},items:{type:"array",items:{type:"object",properties:{kind:{type:"string",enum:o},name:{type:"string"},description:i,type:{type:"string"},args:{type:"string"},directives:{type:"string"}}}},...n}}},{name:"get-graphql-type",description:'Get the SDL for a single GraphQL type, operation (query/mutation/subscription field), or directive. Without includeReferencedTypes, the SDL is followed by a one-level manifest of every directly referenced type\'s field names (e.g. "input ActionOperationInput { addTags, removeTags }"), so you can see what nested types contain before fetching them. Set includeReferencedTypes to instead pull in the full SDL of every type it references transitively \u2014 the whole structural subgraph in one call, ideal for complete exploration of an entity (e.g. Book and all the types it touches). That traversal only follows what the target depends on; to go the other direction and find the members that use a type, call get-graphql-type-list with `referencesType`.',schema:A,annotations:{title:"Get GraphQL Type",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"string"}},{name:"get-graphql-schema",description:"Get the complete GraphQL schema as SDL \u2014 the whole type graph in one call. Returns everything, so it can be very large and token-expensive for big schemas; prefer get-graphql-type-list (filter/paginate) plus targeted get-graphql-type calls, and reach for this only for small schemas or when you truly need the full SDL.",schema:L,annotations:{title:"Get GraphQL Schema",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"string"}}]}function G(e,t){const c=e&&e.length>0,u=t&&t.length>0;return{name:"search",description:"Search across the documentation to fetch relevant content for a given query",schema:{type:"object",required:["query"],additionalProperties:!1,properties:{query:{type:"string",description:"Search query. Should be a single word or that phrase that is presented in a documentation.",minLength:1},...c?{product:{type:"string",description:"Optional product name to filter search results by specific product.",enum:e,nullable:!0}}:{},...u?{locale:{type:"string",description:"Optional locale code to filter search results by documentation language (e.g. 'en', 'ja'). Use the locale matching the language of the user's request. Defaults to the project's default locale.",enum:t,nullable:!0}}:{}}},annotations:{title:"Search Documentation",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1},outputSchema:{type:"string"}}}export{I as getDefaultToolSchemas};
@@ -1 +1 @@
1
- import{resolveParameters as h,resolveRequestBody as v,resolveResponses as x}from"../../utils.js";import{isMcpEndpoint as E}from"./utils.js";import{loadApiDescription as g}from"./load-api-description.js";import{checkEndpointAndDeleteXMcp as P}from"../../../utils/xmcp-utils.js";const S=async(s,i)=>{const{name:p,path:o,method:n,version:a}=s;let t;try{t=await g(p,i,a)}catch(y){return{content:[{type:"text",text:y.message}],isError:!0}}const c=o.startsWith("/")?o:`/${o}`,{title:d=""}=t.info||{},r=t.paths?.[c],m=n.toLowerCase();if(!r)return{content:[{type:"text",text:"Endpoint not found"}],isError:!0};const e=r[m];if(!E(e)||!P(e,"docs"))return{content:[{type:"text",text:"Endpoint not found"}],isError:!0};const u=r?.parameters||[],l=e.parameters||[],f={...e,parameters:h({pathParams:u,opParams:l,definition:t}),requestBody:v(e.requestBody,t),responses:x(e.responses,t)};return{content:[{type:"text",text:JSON.stringify({api:d,version:t.info?.version||"",servers:t.servers||[],endpoint:{path:o,method:n.toUpperCase(),...f},globalSecurity:t.security||[],securitySchemes:t.components?.securitySchemes||{}},null,2)}]}};var M={"get-endpoint-info":S};export{M as default};
1
+ import{resolveParameters as f,resolveRequestBody as l,resolveResponses as y}from"../../utils.js";import{isMcpEndpoint as x}from"./utils.js";import{loadApiDescription as E}from"./load-api-description.js";import{checkEndpointAndDeleteXMcp as v}from"../../../utils/xmcp-utils.js";const g=async(s,i)=>{const{path:o,method:r}=s;if(!o||!r)return{content:[{type:"text",text:'Both "path" and "method" are required (e.g. path: "/tickets", method: "GET").'}],isError:!0};let t;try{t=await E(s,i)}catch(h){return{content:[{type:"text",text:h.message}],isError:!0}}const p=o.startsWith("/")?o:`/${o}`,{title:a=""}=t.info||{},n=t.paths?.[p],c=r.toLowerCase();if(!n)return{content:[{type:"text",text:"Endpoint not found"}],isError:!0};const e=n[c];if(!x(e)||!v(e,"docs"))return{content:[{type:"text",text:"Endpoint not found"}],isError:!0};const d=n?.parameters||[],m=e.parameters||[],u={...e,parameters:f({pathParams:d,opParams:m,definition:t}),requestBody:l(e.requestBody,t),responses:y(e.responses,t)};return{content:[{type:"text",text:JSON.stringify({api:a,version:t.info?.version||"",servers:t.servers||[],endpoint:{path:o,method:r.toUpperCase(),...u},globalSecurity:t.security||[],securitySchemes:t.components?.securitySchemes||{}},null,2)}]}};var w={"get-endpoint-info":g};export{w as default};
@@ -1 +1 @@
1
- import{loadApiDescription as p}from"./load-api-description.js";import{getEndpointsFromPaths as c}from"./utils.js";const a=async(n,r)=>{const{name:o,version:i}=n;let t;try{t=await p(o,r,i)}catch(s){return{content:[{type:"text",text:s.message}],isError:!0}}const e=c(t);return e.length===0?{content:[{type:"text",text:"No endpoints found"}]}:{content:[{type:"text",text:JSON.stringify({api:t.info?.title||"",version:t.info?.version||"",servers:t.servers||[],endpoints:e},null,2)}]}};var l={"get-endpoints":a};export{l as default};
1
+ import{loadApiDescription as i}from"./load-api-description.js";import{getEndpointsFromPaths as s}from"./utils.js";const p=async(n,r)=>{let t;try{t=await i(n,r)}catch(o){return{content:[{type:"text",text:o.message}],isError:!0}}const e=s(t);return e.length===0?{content:[{type:"text",text:"No endpoints found"}]}:{content:[{type:"text",text:JSON.stringify({api:t.info?.title||"",version:t.info?.version||"",servers:t.servers||[],endpoints:e},null,2)}]}};var d={"get-endpoints":p};export{d as default};
@@ -1 +1 @@
1
- import{loadApiDescription as s}from"./load-api-description.js";const p=async(e,i)=>{const{name:n,version:r}=e;let t;try{t=await s(n,i,r)}catch(o){return{content:[{type:"text",text:o.message}],isError:!0}}return{content:[{type:"text",text:JSON.stringify({api:t.info?.title||"",version:t.info?.version||"",definition:t},null,2)}]}};var a={"get-full-api-description":p};export{a as default};
1
+ import{loadApiDescription as n}from"./load-api-description.js";const o=async(e,i)=>{let t;try{t=await n(e,i)}catch(r){return{content:[{type:"text",text:r.message}],isError:!0}}return{content:[{type:"text",text:JSON.stringify({api:t.info?.title||"",version:t.info?.version||"",definition:t},null,2)}]}};var s={"get-full-api-description":o};export{s as default};
@@ -1 +1 @@
1
- import{loadApiDescription as c}from"./load-api-description.js";const o=async(t,r)=>{const{name:s,version:i}=t;let e;try{e=await c(s,r,i)}catch(n){return{content:[{type:"text",text:n.message}],isError:!0}}return{content:[{type:"text",text:JSON.stringify({name:e.info?.title,version:e.info?.version,securitySchemes:e.components?.securitySchemes||{},security:e.security||[]},null,2)}]}};var y={"get-security-schemes":o};export{y as default};
1
+ import{loadApiDescription as s}from"./load-api-description.js";const c=async(t,r)=>{let e;try{e=await s(t,r)}catch(i){return{content:[{type:"text",text:i.message}],isError:!0}}return{content:[{type:"text",text:JSON.stringify({name:e.info?.title,version:e.info?.version,securitySchemes:e.components?.securitySchemes||{},security:e.security||[]},null,2)}]}};var o={"get-security-schemes":c};export{o as default};
@@ -1 +1 @@
1
- import{filterApiDescriptionsByName as f}from"../../utils.js";const g=async(l,a)=>{const{filter:i,page:n=1,limit:e=300}=l;let t=Object.values(a.apiDescriptionsMap);i&&(t=f(t,i));const s=(n-1)*e,o=s+e,c=Math.ceil(t.length/e),r=t.length;return t=t.slice(s,o),t.length===0?{content:[{type:"text",text:"No APIs available"}]}:{content:[{type:"text",text:JSON.stringify({items:t.map(({relativePath:h,fetchEligible:x,...p})=>p),limit:e,total:r,page:n,totalPages:c})}]}};var u={"list-apis":g};export{u as default};
1
+ import{filterApiDescriptionsByName as g}from"../../utils.js";const h=async(l,a)=>{const{filter:i,page:n=1,limit:e=300}=l;let t=Object.values(a.apiDescriptionsMap);i&&(t=g(t,i));const s=(n-1)*e,o=s+e,c=Math.ceil(t.length/e),p=t.length;return t=t.slice(s,o),t.length===0?{content:[{type:"text",text:"No APIs available"}]}:{content:[{type:"text",text:JSON.stringify({items:t.map(({relativePath:r,fetchEligible:x,...f})=>({filePath:r,...f})),limit:e,total:p,page:n,totalPages:c})}]}};var u={"list-apis":h};export{u as default};
@@ -1,4 +1,8 @@
1
1
  import type { OpenAPIDefinition } from '@redocly/api-docs';
2
2
  import type { McpToolContext } from '../../../types.js';
3
- export declare function loadApiDescription(name: string, context: McpToolContext, version?: string): Promise<OpenAPIDefinition>;
3
+ export declare function loadApiDescription({ name, version, filePath }: {
4
+ name: string;
5
+ version?: string;
6
+ filePath?: string;
7
+ }, context: McpToolContext): Promise<OpenAPIDefinition>;
4
8
  //# sourceMappingURL=load-api-description.d.ts.map
@@ -1 +1 @@
1
- import{findApiDescriptionByNameAndVersion as t}from"../../utils.js";import{getApiDescriptionFromFs as a}from"./utils.js";async function u(r,i,s){const o=t(i.apiDescriptionsMap,r,s);if(!o)throw new Error(`No API found matching "${r}".`);const e=!!i?.config?.access?.requiresLogin,c=i?.config?.access?.rbac||{},n=await a({relativePath:o.relativePath||"",outdir:i.outdir||"",user:i.user,rbac:c,requiresLogin:e});if(!n)throw new Error(`No API found matching "${r}".`);return n}export{u as loadApiDescription};
1
+ import{findApiDescriptionByNameAndVersion as p}from"../../utils.js";import{getApiDescriptionFromFs as u}from"./utils.js";async function g({name:o,version:n,filePath:r},i){const s=r?Object.values(i.apiDescriptionsMap).find(c=>c.relativePath===r):p(i.apiDescriptionsMap,o,n);if(!s)throw new Error(r?`No API found with filePath "${r}". File paths are returned by the list-apis tool.`:`No API found matching "${o}".`);const t=!!i?.config?.access?.requiresLogin,a=i?.config?.access?.rbac||{},e=await u({relativePath:s.relativePath||"",outdir:i.outdir||"",user:i.user,rbac:a,requiresLogin:t});if(!e)throw new Error(`No API found matching "${r||o}".`);return e}export{g as loadApiDescription};
@@ -6,11 +6,13 @@ export type ListApisArgs = {
6
6
  export type ApiNameVersionArgs = {
7
7
  name: string;
8
8
  version?: string;
9
+ filePath?: string;
9
10
  };
10
11
  export type GetEndpointInfoArgs = {
11
12
  name: string;
12
13
  path: string;
13
14
  method: string;
14
15
  version?: string;
16
+ filePath?: string;
15
17
  };
16
18
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- function v(t,o,n){if(!o)return;const s=o.toLowerCase(),r=Object.values(t).filter(({name:i})=>i.toLowerCase()===s);if(r.length===0)return;const e=n?r.filter(i=>i.version===n):r;if(e.length===0)return;if(e.length===1)return e[0];if(new Set(e.map(({version:i})=>i||"")).size>1){const i=e.map(({version:d})=>d||"(no version)").join(", ");throw new Error(`Multiple versions of "${o}" found: ${i}. Specify a version to disambiguate.`)}const c=e.map(({relativePath:i})=>i||"(unknown path)").join(", "),u=e[0].version?` at version "${e[0].version}"`:"";throw new Error(`Multiple APIs are titled "${o}"${u} (${c}). Give them a distinct info.title or info.version so they can be addressed individually.`)}function $(t,o){return Object.values(t).filter(({name:n})=>n.toLowerCase().includes(o.toLowerCase()))}function a(t,o){if(!o||!t?.startsWith("#/"))return;const n=t.slice(2).split("/");let s=o;for(const r of n)if(typeof s=="object"&&s!==null&&r in s)s=s[r];else return;return s}function l({schema:t,definition:o,visitedRefs:n=new Set,maxDepth:s=50,currentDepth:r=0}){if(!t)return;if(r>=s)return{type:"object",description:`Maximum resolution depth exceeded (${s})`};if("$ref"in t&&t.$ref){if(n.has(t.$ref))return{type:"object",description:`Circular reference detected: ${t.$ref}`,"x-circular-ref":t.$ref};const c=new Set(n);c.add(t.$ref);const u=a(t.$ref,o);return u?l({schema:u,definition:o,visitedRefs:c,maxDepth:s,currentDepth:r+1}):{type:"object",description:`Could not resolve reference: ${t.$ref}`,"x-unresolved-ref":t.$ref}}const e={...t};if(e.properties){const c={};for(const[u,i]of Object.entries(e.properties)){const d=l({schema:i,definition:o,visitedRefs:new Set(n),maxDepth:s,currentDepth:r+1});c[u]=d||i}e.properties=c}if(e.items&&typeof e.items=="object")if(Array.isArray(e.items))e.items=e.items.map(c=>l({schema:c,definition:o,visitedRefs:new Set(n),maxDepth:s,currentDepth:r+1})||c);else{const c=l({schema:e.items,definition:o,visitedRefs:new Set(n),maxDepth:s,currentDepth:r+1});c&&(e.items=c)}return["allOf","anyOf","oneOf"].forEach(c=>{const u=e[c];Array.isArray(u)&&(e[c]=u.reduce((i,d)=>{const p=l({schema:d,definition:o,visitedRefs:new Set(n),maxDepth:s,currentDepth:r+1});return p&&i.push(p),i},[]))}),e}function h({pathParams:t,opParams:o,definition:n}){return[...t,...o].reduce((s,r)=>{if(!r)return s;if("$ref"in r&&r.$ref){const e=a(r.$ref,n);return e&&s.push({...e,schema:"schema"in e&&e.schema?l({schema:e.schema,definition:n}):void 0}),s}return s.push({...r,schema:"schema"in r&&r.schema?l({schema:r.schema,definition:n}):void 0}),s},[])}function m(t,o){const n={...t};if(t.example!==void 0&&(n.example=t.example),!t.examples)return n;const s={};for(const[r,e]of Object.entries(t.examples))if("$ref"in e){const f=a(e.$ref,o);f&&(s[r]=f)}else s[r]=e;return n.examples=s,n}function w(t,o){if(!t)return;const n="$ref"in t&&t.$ref?a(t.$ref,o):t;if(!n)return;const s={...n};if("content"in n&&n.content){const r={};for(const[e,f]of Object.entries(n.content))r[e]={...m(f,o),schema:f.schema?l({schema:f.schema,definition:o}):void 0};s.content=r}return s}function b(t,o){return Object.entries(t).reduce((n,[s,r])=>{const e="$ref"in r&&r.$ref?a(r.$ref,o):r;if(!e)return n;const f={...e};if("content"in e&&e.content){const c={};for(const[u,i]of Object.entries(e.content))c[u]={...m(i,o),schema:i.schema?l({schema:i.schema,definition:o}):void 0};f.content=c}return n[s]=f,n},{})}export{$ as filterApiDescriptionsByName,v as findApiDescriptionByNameAndVersion,h as resolveParameters,a as resolveRef,w as resolveRequestBody,b as resolveResponses,l as resolveSchemaRefs};
1
+ function v(e,o,s){if(!o)return;const r=o.toLowerCase(),t=Object.values(e).filter(({name:f})=>f.toLowerCase()===r);if(t.length===0)return;const n=s?t.filter(f=>f.version===s):t;if(n.length===0)return;if(n.length===1)return n[0];if(new Set(n.map(({version:f})=>f||"")).size>1){const f=n.map(({version:l})=>l||"(no version)").join(", ");throw new Error(`Multiple versions of "${o}" found: ${f}. Specify a version to disambiguate.`)}const i=n.map(({relativePath:f})=>`"${f||"(unknown path)"}"`).join(", "),u=n[0].version?` at version "${n[0].version}"`:"";throw new Error(`Multiple APIs are titled "${o}"${u}. Pass the "filePath" parameter to select one of: ${i}.`)}function h(e,o){return Object.values(e).filter(({name:s})=>s.toLowerCase().includes(o.toLowerCase()))}function d(e,o){if(!o||!e?.startsWith("#/"))return;const s=e.slice(2).split("/");let r=o;for(const t of s)if(typeof r=="object"&&r!==null&&t in r)r=r[t];else return;return r}function p(e,o){const s=Object.fromEntries(Object.entries(e).filter(([r,t])=>r!=="$ref"&&t!==void 0));return{...o,...s}}function a({schema:e,definition:o,visitedRefs:s=new Set,maxDepth:r=50,currentDepth:t=0}){if(!e)return;if(t>=r)return{type:"object",description:`Maximum resolution depth exceeded (${r})`};if("$ref"in e&&e.$ref){if(s.has(e.$ref))return{type:"object",description:`Circular reference detected: ${e.$ref}`,"x-circular-ref":e.$ref};const i=new Set(s);i.add(e.$ref);const u=d(e.$ref,o);if(!u)return{type:"object",description:`Could not resolve reference: ${e.$ref}`,"x-unresolved-ref":e.$ref};const f=a({schema:u,definition:o,visitedRefs:i,maxDepth:r,currentDepth:t+1});return f&&p(e,f)}const n={...e};if(n.properties){const i={};for(const[u,f]of Object.entries(n.properties)){const l=a({schema:f,definition:o,visitedRefs:new Set(s),maxDepth:r,currentDepth:t+1});i[u]=l||f}n.properties=i}if(n.items&&typeof n.items=="object")if(Array.isArray(n.items))n.items=n.items.map(i=>a({schema:i,definition:o,visitedRefs:new Set(s),maxDepth:r,currentDepth:t+1})||i);else{const i=a({schema:n.items,definition:o,visitedRefs:new Set(s),maxDepth:r,currentDepth:t+1});i&&(n.items=i)}return["allOf","anyOf","oneOf"].forEach(i=>{const u=n[i];Array.isArray(u)&&(n[i]=u.reduce((f,l)=>{const m=a({schema:l,definition:o,visitedRefs:new Set(s),maxDepth:r,currentDepth:t+1});return m&&f.push(m),f},[]))}),n}function w({pathParams:e,opParams:o,definition:s}){return[...e,...o].reduce((r,t)=>{if(!t)return r;if("$ref"in t&&t.$ref){const n=d(t.$ref,s);if(!n)return r;const c=p(t,n);return r.push({...c,schema:"schema"in c&&c.schema?a({schema:c.schema,definition:s}):void 0}),r}return r.push({...t,schema:"schema"in t&&t.schema?a({schema:t.schema,definition:s}):void 0}),r},[])}function $(e,o){const s={...e};if(e.example!==void 0&&(s.example=e.example),!e.examples)return s;const r={};for(const[t,n]of Object.entries(e.examples))if("$ref"in n){const c=d(n.$ref,o);c&&(r[t]=c)}else r[t]=n;return s.examples=r,s}function b(e,o){if(!e)return;const s="$ref"in e&&e.$ref?d(e.$ref,o):e;if(!s)return;const r=p(e,s),t={...r};if("content"in r&&r.content){const n={};for(const[c,i]of Object.entries(r.content))n[c]={...$(i,o),schema:i.schema?a({schema:i.schema,definition:o}):void 0};t.content=n}return t}function j(e,o){return Object.entries(e).reduce((s,[r,t])=>{const n="$ref"in t&&t.$ref?d(t.$ref,o):t;if(!n)return s;const c=p(t,n),i={...c};if("content"in c&&c.content){const u={};for(const[f,l]of Object.entries(c.content))u[f]={...$(l,o),schema:l.schema?a({schema:l.schema,definition:o}):void 0};i.content=u}return s[r]=i,s},{})}export{h as filterApiDescriptionsByName,v as findApiDescriptionByNameAndVersion,w as resolveParameters,d as resolveRef,b as resolveRequestBody,j as resolveResponses,a as resolveSchemaRefs};
@@ -3,26 +3,46 @@ import type { UpstreamAuthRequiredEvent } from './upstream-auth.js';
3
3
  import type { UpstreamAuthScheme } from './upstream-auth-scheme.js';
4
4
  import type { KvService } from '@redocly/config';
5
5
  export declare const CREDENTIALS_INPUT_REQUEST_KEY = "credentials";
6
+ export type CredentialTarget = {
7
+ api: string;
8
+ name: string;
9
+ };
10
+ /**
11
+ * Why the prompt is being raised. `authFailure` is the automatic path behind an upstream 401;
12
+ * `requested` is the model calling the credentials tool on the user's behalf.
13
+ */
14
+ export type CredentialElicitationReason = {
15
+ kind: 'authFailure';
16
+ status: number;
17
+ otherApiNames?: string[];
18
+ } | {
19
+ kind: 'requested';
20
+ };
21
+ type CredentialElicitationRequest = {
22
+ subject: string;
23
+ baseUrl: string;
24
+ target: CredentialTarget;
25
+ auth: UpstreamAuthScheme;
26
+ kv: KvService;
27
+ hasExistingSecret: boolean;
28
+ reason: CredentialElicitationReason;
29
+ };
6
30
  export declare function isCredentialElicitationDeclined(inputResponses: Record<string, unknown> | undefined): boolean;
31
+ export declare function targetOf(event: UpstreamAuthRequiredEvent): CredentialTarget;
7
32
  /**
8
33
  * The 2026-07-28-era credentials prompt: the tool call returns an input-required result the
9
34
  * client fulfils and retries, with no push `elicitation/create` and no session behind it.
10
35
  * Nothing has to survive the round trip - the secrets are keyed by `subject`, which is
11
36
  * recomputed from the caller's identity on every request.
12
37
  */
13
- export declare function buildUpstreamCredentialInputRequired({ subject, baseUrl, events, auth, kv, }: {
14
- subject: string;
15
- baseUrl: string;
16
- events: UpstreamAuthRequiredEvent[];
17
- auth: UpstreamAuthScheme;
18
- kv: KvService;
19
- }): Promise<InputRequiredResult>;
20
- /** Fallback text for a 2026-07-28-era client without URL-elicitation support. */
21
- export declare function buildUpstreamCredentialFallback({ subject, baseUrl, events, auth, kv, }: {
22
- subject: string;
23
- baseUrl: string;
24
- events: UpstreamAuthRequiredEvent[];
25
- auth: UpstreamAuthScheme;
26
- kv: KvService;
27
- }): Promise<string>;
38
+ export declare function buildUpstreamCredentialInputRequired(request: CredentialElicitationRequest): Promise<InputRequiredResult>;
39
+ /** Fallback text for a client without URL-elicitation support. */
40
+ export declare function buildUpstreamCredentialFallback(request: CredentialElicitationRequest): Promise<string>;
41
+ /**
42
+ * Text for an authorization failure we deliberately do NOT prompt for: a stored secret the
43
+ * upstream rejected, or a 403/407 that is more likely a permissions problem than a missing key.
44
+ * Minting another link here is what produced the credentials-link loop.
45
+ */
46
+ export declare function buildCredentialsToolHint(event: UpstreamAuthRequiredEvent): string;
47
+ export {};
28
48
  //# sourceMappingURL=elicitation.d.ts.map
@@ -1,2 +1,3 @@
1
- import{inputRequired as s,inputResponse as p}from"@modelcontextprotocol/server";import{secretStore as h,ELICITATION_TTL_MS as m}from"./secret-store.js";import{ANONYMOUS_SUBJECT_PREFIX as c}from"./subject.js";const l="credentials";function k(e){const t=p(e,l);return t.kind==="elicit"&&(t.action==="decline"||t.action==="cancel")}async function v({subject:e,baseUrl:t,events:n,auth:a,kv:r}){const{url:i}=await u(r,e,t,n,a);return s({inputRequests:{[l]:s.elicitUrl({message:T(n[0].name),url:i})}})}async function A({subject:e,baseUrl:t,events:n,auth:a,kv:r}){const{url:i}=await u(r,e,t,n,a);return E(n,i,f(e))}async function u(e,t,n,a,r){const i=a[0],o=await h.createElicitation(e,{subject:t,api:i.api,name:i.name,auth:r});return{token:o,url:g(n,o)}}function f(e){return e.startsWith(c)?e.slice(c.length):void 0}function T(e){return`The "${e}" API requires credentials. Open the link to provide them securely; they never pass through the chat.`}function g(e,t){if(!e)return`/mcp/credentials?token=${t}`;try{const n=new URL("/mcp/credentials",e);return n.searchParams.set("token",t),n.toString()}catch{return`${e}/mcp/credentials?token=${t}`}}function E(e,t,n){const[a,...r]=e,i=[`The upstream API "${a.name}" returned 401 Unauthorized.`,"",`**Action required: show the user the link below and ask them to open it in a browser to provide the API credentials (valid ${Math.round(m/6e4)} minutes):**`,"",t,"",n?`Then call this tool again with sessionHandle: "${n}" - the credentials are stored against that handle, applied server-side, and never appear in the conversation.`:"Then call this tool again - the credentials are applied server-side and never appear in the conversation."];if(r.length>0){const o=r.map(d=>`"${d.name}"`).join(", ");i.push("",`The remaining APIs (${o}) will prompt for credentials on subsequent calls.`)}return i.join(`
2
- `)}export{l as CREDENTIALS_INPUT_REQUEST_KEY,A as buildUpstreamCredentialFallback,v as buildUpstreamCredentialInputRequired,k as isCredentialElicitationDeclined};
1
+ import{inputRequired as c,inputResponse as f}from"@modelcontextprotocol/server";import{MANAGE_API_CREDENTIALS_TOOL_NAME as T}from"../constants.js";import{secretStore as $,ELICITATION_TTL_MS as A}from"./secret-store.js";import{ANONYMOUS_SUBJECT_PREFIX as l}from"./subject.js";const h="credentials";function C(e){const t=f(e,h);return t.kind==="elicit"&&(t.action==="decline"||t.action==="cancel")}function _(e){return{api:e.api,name:e.name}}async function b(e){const{url:t}=await d(e);return c({inputRequests:{[h]:c.elicitUrl({message:P(e.target.name,e.hasExistingSecret),url:t})}})}async function O(e){const{url:t}=await d(e);return y(e,t,I(e.subject))}function R(e){return[e.status===401?`The stored credentials for "${e.name}" were rejected (HTTP 401).`:`The "${e.name}" API rejected the request with HTTP ${e.status}.`,"",`Do not retry the same call. Call \`${T}\` with api: "${e.api}" to get a link the user has to open in a browser to replace or remove the stored credentials, then retry once they confirm they saved them.`].join(`
2
+ `)}async function d({kv:e,subject:t,baseUrl:n,target:i,auth:a,hasExistingSecret:o}){const r=await $.createElicitation(e,{subject:t,api:i.api,name:i.name,auth:a,hasExistingSecret:o});return{token:r,url:g(n,r)}}function I(e){return e.startsWith(l)?e.slice(l.length):void 0}function P(e,t){const n=`The link works for ${u()} minutes.`;return t?`Open this page in a browser to replace or remove the stored credentials for the "${e}" API. They go straight to the API and never enter the chat. ${n}`:`The "${e}" API needs credentials. Open this page in a browser to paste them - they go straight to the API and never enter the chat. ${n}`}function u(){return Math.round(A/6e4)}function g(e,t){if(!e)return`/mcp/credentials?token=${t}`;try{const n=new URL("/mcp/credentials",e);return n.searchParams.set("token",t),n.toString()}catch{return`${e}/mcp/credentials?token=${t}`}}function y({target:e,reason:t,hasExistingSecret:n},i,a){const r=[`**Stop and show the user this link. They have to open it in a browser to ${n?`replace or remove the stored credentials for the "${e.name}" API`:`add credentials for the "${e.name}" API`} - nothing continues until they do.**`,"",i,"",`The link is valid ${u()} minutes and opens a form on this site. Credentials go straight to the API from there: never ask for them in the conversation and never pass them as a tool argument.`,"",t.kind==="authFailure"?`The upstream API "${e.name}" returned ${t.status} and needs credentials.`:n?`Credentials are already stored for the "${e.name}" API; the link lets the user replace or remove them.`:`The "${e.name}" API needs credentials.`,a?`Once the user confirms they saved them, call this tool again with sessionHandle: "${a}" - the credentials are stored against that handle, applied server-side, and never appear in the conversation.`:"Once the user confirms they saved them, call this tool again - the credentials are applied server-side and never appear in the conversation."],s=t.kind==="authFailure"?t.otherApiNames??[]:[];if(s.length>0){const p=s.map(m=>`"${m}"`).join(", ");r.push("",`The remaining APIs (${p}) will prompt for credentials on subsequent calls.`)}return r.join(`
3
+ `)}export{h as CREDENTIALS_INPUT_REQUEST_KEY,R as buildCredentialsToolHint,O as buildUpstreamCredentialFallback,b as buildUpstreamCredentialInputRequired,C as isCredentialElicitationDeclined,_ as targetOf};
@@ -0,0 +1,24 @@
1
+ import type { GatewayFetchOutcome } from './fetch-telemetry.js';
2
+ type GatewayFetchAuditRecord = {
3
+ rawUrl: string;
4
+ method: string;
5
+ host: string;
6
+ outcome: GatewayFetchOutcome;
7
+ durationMs: number;
8
+ injectedSecretValues: readonly string[];
9
+ path?: string;
10
+ api?: string;
11
+ status?: number;
12
+ requestHeaders?: Record<string, string>;
13
+ requestBody?: string;
14
+ responseHeaders?: Record<string, string>;
15
+ responseBody?: string;
16
+ };
17
+ /**
18
+ * One outbound gateway fetch. `host`, `method` and `path` repeat the `mcpServer.gatewayFetch`
19
+ * telemetry event verbatim so a Grafana row joins onto this line; `url_sha256` identifies the
20
+ * request itself, since the telemetry event's `path` is deliberately normalized.
21
+ */
22
+ export declare function logGatewayFetchAudit({ rawUrl, method, host, outcome, durationMs, injectedSecretValues, path, api, status, requestHeaders, requestBody, responseHeaders, responseBody, }: GatewayFetchAuditRecord): void;
23
+ export {};
24
+ //# sourceMappingURL=fetch-audit.d.ts.map
@@ -0,0 +1 @@
1
+ import{auditHeaderNames as u,auditInput as g,auditOutputDigest as c,auditSha256 as b,auditUrl as l,logAuditRecord as y}from"../../audit/audit-log.js";function D({rawUrl:e,method:n,host:i,outcome:r,durationMs:o,injectedSecretValues:t,path:_,api:h,status:f,requestHeaders:p,requestBody:d,responseHeaders:m,responseBody:a}){const s=a===void 0?void 0:c(a);y("Record gateway fetch",{url_sha256:b(e),host:i,method:n,path:_,api:h,outcome:r,status:f,duration_ms:o,url:l(e,t),request_header_names:u(p),request_body:d===void 0?void 0:g(d,t),response_header_names:u(m),response_sha256:s?.sha256,response_bytes:s?.bytes})}export{D as logGatewayFetchAudit};
@@ -1 +1 @@
1
- import{Agent as P}from"undici";import{envConfig as W}from"../../../../config/env-config.js";import{logger as C}from"../../../../tools/notifiers/logger.js";import{assertUrlAllowed as H}from"./ssrf-guard.js";import{createSsrfSafeLookup as J}from"./ssrf-safe-lookup.js";import{SsrfBlockedError as U}from"./ssrf-blocklist.js";import{GatewayFetchBlockedError as $,GatewayFetchSystemError as K,GatewayFetchUserError as A}from"./fetch-errors.js";import{redactHeaders as x,redactSecretsFromString as V}from"./redact.js";import{normalizeFetchPath as X}from"./normalize-path.js";import{reportGatewayFetch as O}from"./fetch-telemetry.js";const G="x-forwarded-for",Y=3e4,z=1024*1024;let k;function I(){if(!k){const t=W.isDevelopMode;t&&C.warn("Gateway MCP SSRF guard: loopback upstreams are ALLOWED (develop mode only, never in runtime)."),k=new P({connect:{lookup:J({allowLoopback:t})}})}return k}function we({allowedHosts:t,apis:e=[],authorization:r,onAuthRequired:n,fetchImpl:s=fetch,dispatcher:u=I(),timeoutMs:c=Y,maxResponseBytes:h=z,maxFetchesPerExec:f,clientIp:m}){const v=[...t,...e.flatMap(o=>o.matchers)],N=[r,...e.map(o=>o.secret?.value)].filter(o=>!!o),D=e.map(o=>o.secret?.header).filter(o=>!!o);let F=0;return async o=>{const g=o.method??"GET",T=Date.now();let M,b,E,S={},R,L;try{if(f!==void 0){const l=Math.max(0,f);if(F+=1,F>l)throw new A(`Outbound fetch budget exceeded (max ${l} fetches per execute); split the work across calls`)}const i=H(o.url,v);M=i.hostname,b=X(i.pathname);const y=e.filter(l=>l.matchers.some(j=>j.test(i.hostname))),a=y.length===1?y[0]:void 0;E=a?.slug,S=ne(o.headers,a?.secret,r,m);const p=new AbortController;L=setTimeout(()=>p.abort(),c);const d=await s(i,{method:g,headers:S,body:o.body,redirect:"manual",signal:p.signal,dispatcher:u});R=d.status,d.status===401&&a&&n&&n({api:a.slug,name:a.name});const _=await oe(d,h);let w=ae(d.headers);if(ee(d.status)){w=x(w,D);const{location:l}=w;l&&!te(l,i,v)&&delete w.location}return O({host:i.hostname,method:g,outcome:"success",durationMs:Date.now()-T,path:b,api:E,status:d.status,bytes:Buffer.byteLength(_,"utf8")}),{status:d.status,statusText:d.statusText,ok:d.ok,headers:w,bodyText:_}}catch(i){const{outcome:y,userSafe:a}=Z(i),p=M??(a instanceof $?a.host:void 0)??q(o.url)??"invalid-url";throw O({host:p,method:g,outcome:y,durationMs:Date.now()-T,path:b,api:E,status:R}),a||(C.error(`Gateway MCP fetch failed [${g} ${p}]: ${V(re(i),N)} headers=${JSON.stringify(x(S,D))}`),new K("Upstream fetch failed due to an internal error"))}finally{L!==void 0&&clearTimeout(L)}}}function Q(t){let e=t;for(let r=0;r<5&&e instanceof Error;r++){if(e instanceof A||e instanceof U)return e;e=e.cause}}function Z(t){const e=Q(t);return{outcome:e instanceof $||e instanceof U?"blocked":e?"user_error":"system_error",userSafe:e}}function q(t){try{return new URL(t).hostname||void 0}catch{return}}function ee(t){return t>=300&&t<400}function te(t,e,r){try{return!!H(new URL(t,e).href,r)}catch{return!1}}function re(t){if(!(t instanceof Error))return String(t);const e=t.cause;return e instanceof Error?`${t.message}: ${e.message}`:t.message}async function oe(t,e){const r=Number(t.headers.get("content-length"));if(Number.isFinite(r)&&r>e)throw B(e,r);if(!t.body)return"";const n=t.body.getReader(),s=new TextDecoder;let u=0,c="";try{for(;;){const{done:h,value:f}=await n.read();if(h)break;if(u+=f.byteLength,u>e)throw B(e,u);c+=s.decode(f,{stream:!0})}return c+s.decode()}finally{await n.cancel().catch(()=>{})}}function B(t,e){return new A(`Response is too large (over ${t} bytes, got ${e}); request less data`)}function ne(t,e,r,n){const s={},u=new Set;for(const[h,f]of Object.entries(t??{})){const m=h.toLowerCase();m==="host"||m==="content-length"||m!==G&&(u.add(m),s[h]=f)}n&&(s[G]=n);const c=e??(r?{header:"Authorization",value:r}:void 0);return c&&!u.has(c.header.toLowerCase())&&(s[c.header]=c.value),s}function ae(t){const e={};return t.forEach((r,n)=>{e[n.toLowerCase()]=r}),e}export{we as buildFetchBridge,I as getDefaultAgent,oe as readCappedBody};
1
+ import{Agent as W}from"undici";import{envConfig as I}from"../../../../config/env-config.js";import{logger as R}from"../../../../tools/notifiers/logger.js";import{assertUrlAllowed as B}from"./ssrf-guard.js";import{createSsrfSafeLookup as J}from"./ssrf-safe-lookup.js";import{SsrfBlockedError as C}from"./ssrf-blocklist.js";import{GatewayFetchBlockedError as $,GatewayFetchSystemError as K,GatewayFetchUserError as U}from"./fetch-errors.js";import{redactHeaders as x,redactSecretsFromString as V}from"./redact.js";import{normalizeFetchPath as X}from"./normalize-path.js";import{reportGatewayFetch as G}from"./fetch-telemetry.js";import{logGatewayFetchAudit as O}from"./fetch-audit.js";const N="x-forwarded-for",Y=3e4,z=1024*1024,Q=new Set([401,403,407]);let M;function Z(){if(!M){const t=I.isDevelopMode;t&&R.warn("Gateway MCP SSRF guard: loopback upstreams are ALLOWED (develop mode only, never in runtime)."),M=new W({connect:{lookup:J({allowLoopback:t})}})}return M}function be({allowedHosts:t,apis:e=[],authorization:o,onAuthRequired:s,fetchImpl:i=fetch,dispatcher:u=Z(),timeoutMs:d=Y,maxResponseBytes:h=z,maxFetchesPerExec:f,clientIp:m}){const k=[...t,...e.flatMap(r=>r.matchers)],L=[o,...e.map(r=>r.secret?.value)].filter(r=>!!r),v=e.map(r=>r.secret?.header).filter(r=>!!r);let H=0;return async r=>{const w=r.method??"GET",A=Date.now();let _,g,b,S={},T,D;try{if(f!==void 0){const l=Math.max(0,f);if(H+=1,H>l)throw new U(`Outbound fetch budget exceeded (max ${l} fetches per execute); split the work across calls`)}const n=B(r.url,k);_=n.hostname,g=X(n.pathname);const E=e.filter(l=>l.matchers.some(P=>P.test(n.hostname))),c=E.length===1?E[0]:void 0;b=c?.slug,S=se(r.headers,c?.secret,o,m);const p=new AbortController;D=setTimeout(()=>p.abort(),d);const a=await i(n,{method:w,headers:S,body:r.body,redirect:"manual",signal:p.signal,dispatcher:u});T=a.status,Q.has(a.status)&&c&&s&&s({api:c.slug,name:c.name,status:a.status});const F=await ae(a,h);let y=ce(a.headers);if(re(a.status)){y=x(y,v);const{location:l}=y;l&&!oe(l,n,k)&&delete y.location}return G({host:n.hostname,method:w,outcome:"success",durationMs:Date.now()-A,path:g,api:b,status:a.status,bytes:Buffer.byteLength(F,"utf8")}),O({rawUrl:n.href,method:w,host:n.hostname,outcome:"success",durationMs:Date.now()-A,injectedSecretValues:L,path:g,api:b,status:a.status,requestHeaders:S,requestBody:r.body,responseHeaders:y,responseBody:F}),{status:a.status,statusText:a.statusText,ok:a.ok,headers:y,bodyText:F}}catch(n){const{outcome:E,userSafe:c}=ee(n),p=_??(c instanceof $?c.host:void 0)??te(r.url)??"invalid-url";throw G({host:p,method:w,outcome:E,durationMs:Date.now()-A,path:g,api:b,status:T}),O({rawUrl:r.url,method:w,host:p,outcome:E,durationMs:Date.now()-A,injectedSecretValues:L,path:g,api:b,status:T,requestHeaders:S,requestBody:r.body}),c||(R.error(`Gateway MCP fetch failed [${w} ${p}]: ${V(ne(n),L)} headers=${JSON.stringify(x(S,v))}`),new K("Upstream fetch failed due to an internal error"))}finally{D!==void 0&&clearTimeout(D)}}}function q(t){let e=t;for(let o=0;o<5&&e instanceof Error;o++){if(e instanceof U||e instanceof C)return e;e=e.cause}}function ee(t){const e=q(t);return{outcome:e instanceof $||e instanceof C?"blocked":e?"user_error":"system_error",userSafe:e}}function te(t){try{return new URL(t).hostname||void 0}catch{return}}function re(t){return t>=300&&t<400}function oe(t,e,o){try{return!!B(new URL(t,e).href,o)}catch{return!1}}function ne(t){if(!(t instanceof Error))return String(t);const e=t.cause;return e instanceof Error?`${t.message}: ${e.message}`:t.message}async function ae(t,e){const o=Number(t.headers.get("content-length"));if(Number.isFinite(o)&&o>e)throw j(e,o);if(!t.body)return"";const s=t.body.getReader(),i=new TextDecoder;let u=0,d="";try{for(;;){const{done:h,value:f}=await s.read();if(h)break;if(u+=f.byteLength,u>e)throw j(e,u);d+=i.decode(f,{stream:!0})}return d+i.decode()}finally{await s.cancel().catch(()=>{})}}function j(t,e){return new U(`Response is too large (over ${t} bytes, got ${e}); request less data`)}function se(t,e,o,s){const i={},u=new Set;for(const[h,f]of Object.entries(t??{})){const m=h.toLowerCase();m==="host"||m==="content-length"||m!==N&&(u.add(m),i[h]=f)}s&&(i[N]=s);const d=e??(o?{header:"Authorization",value:o}:void 0);return d&&!u.has(d.header.toLowerCase())&&(i[d.header]=d.value),i}function ce(t){const e={};return t.forEach((o,s)=>{e[s.toLowerCase()]=o}),e}export{be as buildFetchBridge,Z as getDefaultAgent,ae as readCappedBody};
@@ -1 +1 @@
1
- import{CacheService as a}from"../../../../persistence/cache/services/cache-service.js";import{logger as s}from"../../../../tools/notifiers/logger.js";import{ipRateLimitKey as c}from"./ip-key.js";import{checkEdgeRateLimit as p}from"./edge-limiter.js";import{resolveGatewayFetchLimits as m}from"./limits.js";import{reportMcpRateLimited as d}from"./rate-limit-telemetry.js";async function S(o,n){const i=c(o);if(!i)return;let e,r;try{const t=await a.getInstance({baseDbDir:n.serverOutDir,databaseType:"local"});r=m(),e=await p({cache:t,ip:i,limits:r,now:Date.now()})}catch(t){s.warn(`gateway rate-limit init error, failing open: ${String(t)}`);return}if(!e.allowed)return d({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{S as applyEdgeRateLimit};
1
+ import{envConfig as o}from"../../../../config/env-config.js";import{KvRepository as s}from"../../../../persistence/kv/repositories/kv-repository.js";import{logger as m}from"../../../../tools/notifiers/logger.js";import{ipRateLimitKey as p}from"./ip-key.js";import{checkEdgeRateLimit as c}from"./edge-limiter.js";import{resolveGatewayFetchLimits as d}from"./limits.js";import{reportMcpRateLimited as f}from"./rate-limit-telemetry.js";async function S(n,a){const i=p(n);if(!i)return;let e,t;try{const r=await s.create({baseDbDir:a.serverOutDir,sqldRemoteDatabaseUrl:o.SQLD_REMOTE_DATABASE_URL,sqldRemoteDatabaseAuthToken:o.SQLD_REMOTE_DATABASE_AUTH_TOKEN});t=d(),e=await c({kv:r,ip:i,limits:t,now:Date.now()})}catch(r){m.warn(`gateway rate-limit init error, failing open: ${String(r)}`);return}if(!e.allowed)return f({scope:e.scope,retryAfterSeconds:e.retryAfterSeconds,limit:e.scope==="rate"?t.ratePerMin:t.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{S as applyEdgeRateLimit};
@@ -1,4 +1,4 @@
1
- import type { CacheService } from '../../../../persistence/cache/services/cache-service.js';
1
+ import type { KvRepository } from '../../../../persistence/kv/repositories/kv-repository.js';
2
2
  import type { GatewayFetchLimits } from './limits.js';
3
3
  export type EdgeDecision = {
4
4
  allowed: true;
@@ -8,7 +8,7 @@ export type EdgeDecision = {
8
8
  scope: 'rate' | 'quota';
9
9
  };
10
10
  export declare function checkEdgeRateLimit(params: {
11
- cache: CacheService;
11
+ kv: KvRepository;
12
12
  ip: string;
13
13
  limits: GatewayFetchLimits;
14
14
  now: number;
@@ -1 +1 @@
1
- import{checkAndIncrement as a}from"./window-counter.js";const s=60,d=86400;async function w(i){const{cache:e,ip:t,limits:o,now:r}=i,n=await a({cache:e,ip:t,windowSeconds:s,limit:o.ratePerMin,now:r});if(!n.allowed)return{allowed:!1,retryAfterSeconds:n.retryAfterSeconds,scope:"rate"};const c=await a({cache:e,ip:t,windowSeconds:d,limit:o.quotaPerDay,now:r});return c.allowed?{allowed:!0}:{allowed:!1,retryAfterSeconds:c.retryAfterSeconds,scope:"quota"}}export{w as checkEdgeRateLimit};
1
+ import{checkAndIncrement as c}from"./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};
@@ -1,4 +1,4 @@
1
- import type { CacheService } from '../../../../persistence/cache/services/cache-service.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;
@@ -6,7 +6,7 @@ export type WindowCheck = {
6
6
  allowed: true;
7
7
  };
8
8
  export declare function checkAndIncrement(params: {
9
- cache: CacheService;
9
+ kv: KvRepository;
10
10
  ip: string;
11
11
  windowSeconds: number;
12
12
  limit: number;
@@ -1 +1 @@
1
- import{logger as f}from"../../../../tools/notifiers/logger.js";const s="gateway-fetch-rl";async function m(i){const{cache:e,ip:l,windowSeconds:t,limit:w,now:d}=i,n=Math.floor(d/1e3),o=Math.floor(n/t),r=`${l}:${t}:${o}`;try{const a=(await e.get({key:r,namespace:s}))?.count??0;if(a>=w){const u=(o+1)*t-n;return{allowed:!1,retryAfterSeconds:Math.max(1,u)}}return await e.set({key:r,namespace:s,value:{count:a+1},ttlInSeconds:t}),{allowed:!0}}catch(c){return f.warn(`gateway rate-limit store error, failing open: ${String(c)}`),{allowed:!0}}}export{m as checkAndIncrement};
1
+ import{logger as g}from"../../../../tools/notifiers/logger.js";function u(e,t,o){return["mcp","gateway-fetch-rl",e,String(t),String(o)]}async function S(e){const{kv:t,ip:o,windowSeconds:n,limit:w,now:l}=e,r=Math.floor(l/1e3),c=Math.floor(r/n),i=u(o,n,c);try{const s=(await t.get(i))?.count??0;if(s>=w){const d=(c+1)*n-r;return{allowed:!1,retryAfterSeconds:Math.max(1,d)}}return await t.set(i,{count:s+1},{ttlInSeconds:n}),{allowed:!0}}catch(a){return g.warn(`gateway rate-limit store error, failing open: ${String(a)}`),{allowed:!0}}}export{S as checkAndIncrement};
@@ -10,11 +10,14 @@ export type ElicitationTarget = {
10
10
  api: string;
11
11
  name: string;
12
12
  auth: UpstreamAuthScheme;
13
+ hasExistingSecret?: boolean;
13
14
  };
14
15
  export declare class SecretStore {
15
16
  #private;
16
17
  setSecret(kv: KvService, subject: string, api: string, secret: StoredSecret): Promise<void>;
17
18
  getSecretsForSubject(kv: KvService, subject: string): Promise<Record<string, StoredSecret>>;
19
+ hasSecret(kv: KvService, subject: string, api: string): Promise<boolean>;
20
+ deleteSecret(kv: KvService, subject: string, api: string): Promise<void>;
18
21
  deleteSubject(kv: KvService, subject: string): Promise<void>;
19
22
  createElicitation(kv: KvService, target: ElicitationTarget): Promise<string>;
20
23
  peekElicitation(kv: KvService, token: string): Promise<ElicitationTarget | undefined>;
@@ -1 +1 @@
1
- import{createCipheriv as o,createDecipheriv as f,createHash as u,hkdfSync as a,randomBytes as h,randomUUID as m}from"node:crypto";import{JWT_SECRET_KEY as c}from"../../../constants/common.js";import{secretTtlSeconds as l}from"./subject.js";const p=900*1e3,d="mcp-upstream-secrets",y="mcp-elicitation",S="mcp-elicitation";class g{async setSecret(t,e,r,n){const i={api:r,...this.#c(this.#a(e),n,r)};await t.set(this.#n(e,r),i,{ttlInSeconds:l(e)})}async getSecretsForSubject(t,e){const r=this.#a(e),{items:n}=await t.list({prefix:this.#i(e)}),i={};for(const s of n)if(s.value)try{i[s.value.api]=this.#r(r,s.value,s.value.api)}catch{continue}return i}async deleteSubject(t,e){const{items:r}=await t.list({prefix:this.#i(e)}),n=r.flatMap(i=>i.value?[this.#n(e,i.value.api)]:[]);n.length!==0&&await t.transaction(async i=>{for(const s of n)await i.delete(s)})}async createElicitation(t,e){const r=m();return await t.set(this.#t(r),this.#c(this.#e(),e),{ttlInSeconds:p/1e3}),r}async peekElicitation(t,e){const r=await t.get(this.#t(e));if(r)try{return this.#r(this.#e(),r)}catch{return}}async consumeElicitation(t,e){const r=this.#t(e),n=await t.transaction(async i=>{const s=await i.get(r);return s&&await i.delete(r),s});if(n)try{return this.#r(this.#e(),n)}catch{return}}#i(t){return["mcp","secret",this.#s(t)]}#n(t,e){return["mcp","secret",this.#s(t),e]}#t(t){return["mcp","elicitation",t]}#s(t){return u("sha256").update(t).digest("hex")}#a(t){return Buffer.from(a("sha256",c,t,d,32))}#e(){return Buffer.from(a("sha256",c,S,y,32))}#c(t,e,r){const n=h(12),i=o("aes-256-gcm",t,n);r&&i.setAAD(Buffer.from(r,"utf8"));const s=Buffer.concat([i.update(JSON.stringify(e),"utf8"),i.final()]);return{iv:n.toString("base64"),tag:i.getAuthTag().toString("base64"),data:s.toString("base64")}}#r(t,e,r){const n=f("aes-256-gcm",t,Buffer.from(e.iv,"base64"));r&&n.setAAD(Buffer.from(r,"utf8")),n.setAuthTag(Buffer.from(e.tag,"base64"));const i=Buffer.concat([n.update(Buffer.from(e.data,"base64")),n.final()]).toString("utf8");return JSON.parse(i)}}const E=new g;export{p as ELICITATION_TTL_MS,g as SecretStore,E as secretStore};
1
+ import{createCipheriv as o,createDecipheriv as f,createHash as u,hkdfSync as a,randomBytes as h,randomUUID as l}from"node:crypto";import{JWT_SECRET_KEY as c}from"../../../constants/common.js";import{secretTtlSeconds as m}from"./subject.js";const p=900*1e3,d="mcp-upstream-secrets",y="mcp-elicitation",S="mcp-elicitation";class g{async setSecret(t,e,r,n){const i={api:r,...this.#c(this.#a(e),n,r)};await t.set(this.#t(e,r),i,{ttlInSeconds:m(e)})}async getSecretsForSubject(t,e){const r=this.#a(e),{items:n}=await t.list({prefix:this.#n(e)}),i={};for(const s of n)if(s.value)try{i[s.value.api]=this.#i(r,s.value,s.value.api)}catch{continue}return i}async hasSecret(t,e,r){return!!await t.get(this.#t(e,r))}async deleteSecret(t,e,r){await t.delete(this.#t(e,r))}async deleteSubject(t,e){const{items:r}=await t.list({prefix:this.#n(e)}),n=r.flatMap(i=>i.value?[this.#t(e,i.value.api)]:[]);n.length!==0&&await t.transaction(async i=>{for(const s of n)await i.delete(s)})}async createElicitation(t,e){const r=l();return await t.set(this.#e(r),this.#c(this.#r(),e),{ttlInSeconds:p/1e3}),r}async peekElicitation(t,e){const r=await t.get(this.#e(e));if(r)try{return this.#i(this.#r(),r)}catch{return}}async consumeElicitation(t,e){const r=this.#e(e),n=await t.transaction(async i=>{const s=await i.get(r);return s&&await i.delete(r),s});if(n)try{return this.#i(this.#r(),n)}catch{return}}#n(t){return["mcp","secret",this.#s(t)]}#t(t,e){return["mcp","secret",this.#s(t),e]}#e(t){return["mcp","elicitation",t]}#s(t){return u("sha256").update(t).digest("hex")}#a(t){return Buffer.from(a("sha256",c,t,d,32))}#r(){return Buffer.from(a("sha256",c,S,y,32))}#c(t,e,r){const n=h(12),i=o("aes-256-gcm",t,n);r&&i.setAAD(Buffer.from(r,"utf8"));const s=Buffer.concat([i.update(JSON.stringify(e),"utf8"),i.final()]);return{iv:n.toString("base64"),tag:i.getAuthTag().toString("base64"),data:s.toString("base64")}}#i(t,e,r){const n=f("aes-256-gcm",t,Buffer.from(e.iv,"base64"));r&&n.setAAD(Buffer.from(r,"utf8")),n.setAuthTag(Buffer.from(e.tag,"base64"));const i=Buffer.concat([n.update(Buffer.from(e.data,"base64")),n.final()]).toString("utf8");return JSON.parse(i)}}const B=new g;export{p as ELICITATION_TTL_MS,g as SecretStore,B as secretStore};
@@ -3,6 +3,7 @@ export declare const UPSTREAM_AUTH_REQUIRED_META = "com.redocly/upstream-auth-re
3
3
  export type UpstreamAuthRequiredEvent = {
4
4
  api: string;
5
5
  name: string;
6
+ status: number;
6
7
  };
7
8
  export declare function withAuthRequiredMeta(result: CallToolResult, events: UpstreamAuthRequiredEvent[]): CallToolResult;
8
9
  export declare function readAuthRequiredMeta(result: CallToolResult): UpstreamAuthRequiredEvent[];
@@ -1 +1 @@
1
- const n="com.redocly/upstream-auth-required";function i(e,a){const t=[...new Map(a.map(r=>[r.api,r])).values()];return t.length===0?e:{...e,_meta:{...e._meta,[n]:t}}}function o(e){const a=e._meta?.[n];return Array.isArray(a)?a.filter(t=>typeof t?.api=="string"&&typeof t?.name=="string"):[]}function p(e){if(!e._meta||!(n in e._meta))return e;const{[n]:a,...t}=e._meta,r={...e};return Object.keys(t).length>0?r._meta=t:delete r._meta,r}export{n as UPSTREAM_AUTH_REQUIRED_META,o as readAuthRequiredMeta,p as stripAuthRequiredMeta,i as withAuthRequiredMeta};
1
+ const i="com.redocly/upstream-auth-required";function u(t,n){const e=new Map;for(const a of n){const o=e.get(a.api);(!o||o.status!==401&&a.status===401)&&e.set(a.api,a)}const r=[...e.values()];return r.length===0?t:{...t,_meta:{...t._meta,[i]:r}}}function p(t){const n=t._meta?.[i];return Array.isArray(n)?n.filter(e=>typeof e?.api=="string"&&typeof e?.name=="string"&&typeof e?.status=="number"):[]}function s(t){if(!t._meta||!(i in t._meta))return t;const{[i]:n,...e}=t._meta,r={...t};return Object.keys(e).length>0?r._meta=e:delete r._meta,r}export{i as UPSTREAM_AUTH_REQUIRED_META,p as readAuthRequiredMeta,s as stripAuthRequiredMeta,u as withAuthRequiredMeta};
@@ -1,4 +1,4 @@
1
- import{secretStore as l}from"../gateway-mcp/secret-store.js";import{buildAllowedHostMatchers as C}from"../gateway-mcp/fetch/allowed-hosts.js";import{assertUrlAllowed as P}from"../gateway-mcp/fetch/ssrf-guard.js";import{readCappedBody as I,getDefaultAgent as _}from"../gateway-mcp/fetch/fetch-bridge.js";const m={"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store","X-Frame-Options":"DENY","Referrer-Policy":"no-referrer","Content-Security-Policy":"default-src 'none'; style-src 'unsafe-inline'; form-action 'self'"},E=3e4,T=64*1024;let v=fetch;function L(e){v=e}const z=async(e,t)=>{const o=await t.getKv();if(e.method==="GET"){const n=new URL(e.url).searchParams.get("token")??"",r=await l.peekElicitation(o,n);return r?new Response(q(r,n),{status:200,headers:m}):u()}if(e.method==="POST"){const n=await e.formData(),r=String(n.get("token")??"");return String(n.get("mode")??"token")==="client-credentials"?F(o,n,r):R(o,n,r)}return new Response("Method Not Allowed",{status:405,headers:{Allow:"GET, POST"}})};async function R(e,t,o){const n=String(t.get("secret")??"");if(!n)return u();const r=await l.consumeElicitation(e,o);return r?(await l.setSecret(e,r.subject,r.api,O(r.auth,n)),new Response(x(r.name),{status:200,headers:m})):u()}async function F(e,t,o){const n=String(t.get("client_id")??"").trim(),r=String(t.get("client_secret")??"").trim();if(!n||!r)return u();const b=await l.peekElicitation(e,o);if(!b||b.auth.kind!=="oauth2ClientCredentials")return u();const a=await l.consumeElicitation(e,o);if(!a||a.auth.kind!=="oauth2ClientCredentials")return u();const f=a.auth;try{const A=C([{url:f.tokenUrl}]),S=P(f.tokenUrl,A),w=new AbortController,$=setTimeout(()=>w.abort(),E);let y;try{y=await v(S,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"client_credentials",client_id:n,client_secret:r,...f.scopes.length>0?{scope:f.scopes.join(" ")}:{}}),redirect:"manual",signal:w.signal,dispatcher:_()})}finally{clearTimeout($)}if(!y.ok)return h(a.name);let k;try{k=await I(y,T)}catch{return h(a.name)}let p;try{p=JSON.parse(k)}catch{return h(a.name)}const g=typeof p=="object"&&p!==null&&"access_token"in p?p.access_token:void 0;return typeof g!="string"||!g?h(a.name):(await l.setSecret(e,a.subject,a.api,{header:"Authorization",value:`Bearer ${g}`}),new Response(x(a.name),{status:200,headers:m}))}catch{return h(a.name)}}function O(e,t){return e.kind==="apiKey"?{header:e.header,value:t}:e.kind==="bearer"||e.kind==="oauth2ClientCredentials"?{header:"Authorization",value:B(t)}:{header:"Authorization",value:t}}function B(e){const t=e.trim();return t.toLowerCase().startsWith("bearer")?t:`Bearer ${t}`}function i(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;")}function s(e){return`<!doctype html>
1
+ import{secretStore as s,ELICITATION_TTL_MS as x}from"../gateway-mcp/secret-store.js";import{buildAllowedHostMatchers as P}from"../gateway-mcp/fetch/allowed-hosts.js";import{assertUrlAllowed as _}from"../gateway-mcp/fetch/ssrf-guard.js";import{readCappedBody as E,getDefaultAgent as T}from"../gateway-mcp/fetch/fetch-bridge.js";const p={"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store","X-Frame-Options":"DENY","Referrer-Policy":"no-referrer","Content-Security-Policy":"default-src 'none'; style-src 'unsafe-inline'; form-action 'self'"},R=3e4,z=64*1024;let $=fetch;function Q(e){$=e}const F=async(e,t)=>{const n=await t.getKv();if(e.method==="GET"){const o=new URL(e.url).searchParams.get("token")??"",r=await s.peekElicitation(n,o);return r?new Response(j(r,o),{status:200,headers:p}):u()}if(e.method==="POST"){const o=await e.formData(),r=String(o.get("token")??""),m=String(o.get("mode")??"token");return m==="client-credentials"?M(n,o,r):m==="clear"?B(n,r):O(n,o,r)}return new Response("Method Not Allowed",{status:405,headers:{Allow:"GET, POST"}})};async function O(e,t,n){const o=String(t.get("secret")??"");if(!o)return u();const r=await s.consumeElicitation(e,n);return r?(await s.setSecret(e,r.subject,r.api,H(r.auth,o)),new Response(A(r.name),{status:200,headers:p})):u()}async function B(e,t){const n=await s.consumeElicitation(e,t);return n?(await s.deleteSecret(e,n.subject,n.api),new Response(D(n.name),{status:200,headers:p})):u()}async function M(e,t,n){const o=String(t.get("client_id")??"").trim(),r=String(t.get("client_secret")??"").trim();if(!o||!r)return u();const m=await s.peekElicitation(e,n);if(!m||m.auth.kind!=="oauth2ClientCredentials")return u();const i=await s.consumeElicitation(e,n);if(!i||i.auth.kind!=="oauth2ClientCredentials")return u();const b=i.auth;try{const S=P([{url:b.tokenUrl}]),C=_(b.tokenUrl,S),k=new AbortController,I=setTimeout(()=>k.abort(),R);let g;try{g=await $(C,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"client_credentials",client_id:o,client_secret:r,...b.scopes.length>0?{scope:b.scopes.join(" ")}:{}}),redirect:"manual",signal:k.signal,dispatcher:T()})}finally{clearTimeout(I)}if(!g.ok)return f(i.name);let v;try{v=await E(g,z)}catch{return f(i.name)}let h;try{h=JSON.parse(v)}catch{return f(i.name)}const w=typeof h=="object"&&h!==null&&"access_token"in h?h.access_token:void 0;return typeof w!="string"||!w?f(i.name):(await s.setSecret(e,i.subject,i.api,{header:"Authorization",value:`Bearer ${w}`}),new Response(A(i.name),{status:200,headers:p}))}catch{return f(i.name)}}function H(e,t){return e.kind==="apiKey"?{header:e.header,value:t}:e.kind==="bearer"||e.kind==="oauth2ClientCredentials"?{header:"Authorization",value:N(t)}:{header:"Authorization",value:t}}function N(e){const t=e.trim();return t.toLowerCase().startsWith("bearer")?t:`Bearer ${t}`}function a(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;")}function c(e){return`<!doctype html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="utf-8">
@@ -20,6 +20,9 @@ input:focus{outline:none;border-color:#4f46e5;box-shadow:0 0 0 3px rgba(79,70,22
20
20
  button{margin-top:1.25rem;padding:.625rem 1.5rem;font-size:1rem;font-weight:600;color:#fff;background:#4f46e5;border:none;border-radius:.5rem;cursor:pointer}
21
21
  button:hover{background:#4338ca}
22
22
  button:focus-visible{outline:2px solid #4f46e5;outline-offset:2px}
23
+ button.danger{background:#b91c1c}
24
+ button.danger:hover{background:#991b1b}
25
+ button.danger:focus-visible{outline-color:#b91c1c}
23
26
  code{background:#f3f4f6;padding:.125rem .375rem;border-radius:.25rem;font-size:.85em}
24
27
  footer{margin-top:1.5rem;padding-top:1rem;border-top:1px solid #e5e7eb;font-size:.75rem;color:#9ca3af;text-align:center}
25
28
  @media (prefers-color-scheme:dark){
@@ -34,34 +37,47 @@ footer{border-top-color:#1f2937;color:#6b7280}
34
37
  </style>
35
38
  </head>
36
39
  <body><div class="card">${e}</div></body>
37
- </html>`}function c(e){return`<h1>\u{1F512} ${i(e)}</h1>`}function d(){return"<footer>Secured by Redocly &middot; credentials never enter the AI conversation</footer>"}function q(e,t){switch(e.auth.kind){case"bearer":return H(e.name,t);case"apiKey":return N(e.name,t,e.auth.header);case"oauth2ClientCredentials":return U(e.name,t,e.auth.scopes);default:return j(e.name,t)}}function H(e,t){return s(`
38
- ${c(`Credentials for ${e}`)}
40
+ </html>`}function d(e){return`<h1>\u{1F512} ${a(e)}</h1>`}function l(){return"<footer>Secured by Redocly &middot; credentials never enter the AI conversation</footer>"}function y(){return`<p class="hint">This link expires ${Math.round(x/6e4)} minutes after it was issued. If it stops working, ask your AI client for a new one.</p>`}function j(e,t){const n=e.hasExistingSecret?q(t):"";switch(e.auth.kind){case"bearer":return L(e.name,t,n);case"apiKey":return U(e.name,t,e.auth.header,n);case"oauth2ClientCredentials":return G(e.name,t,e.auth.scopes,n);default:return K(e.name,t,n)}}function q(e){return`
41
+ <div class="section">
42
+ <h2>Remove stored credentials</h2>
43
+ <p class="hint">This API already has credentials stored. Save above to replace them, or remove them to make calls anonymous again.</p>
44
+ <form method="post" action="/mcp/credentials" autocomplete="off">
45
+ <input type="hidden" name="token" value="${a(e)}">
46
+ <input type="hidden" name="mode" value="clear">
47
+ <button class="danger" type="submit">Remove credentials</button>
48
+ </form>
49
+ </div>`}function L(e,t,n){return c(`
50
+ ${d(`Credentials for ${e}`)}
39
51
  <p class="hint">Stored encrypted for you only (24 hours for anonymous callers, 30 days when signed in) and sent to the API as the <code>Authorization</code> header. It never passes through the AI conversation.</p>
40
52
  <form method="post" action="/mcp/credentials" autocomplete="off">
41
- <input type="hidden" name="token" value="${i(t)}">
53
+ <input type="hidden" name="token" value="${a(t)}">
42
54
  <input type="hidden" name="mode" value="token">
43
55
  <label for="secret">API token</label>
44
56
  <input type="password" id="secret" name="secret" placeholder="your API token" required autofocus>
45
57
  <p class="hint">Paste the raw token or one already prefixed with <code>Bearer</code> &mdash; we add the prefix automatically if it is missing.</p>
46
58
  <button type="submit">Save credentials</button>
47
59
  </form>
48
- ${d()}`)}function N(e,t,o){return s(`
49
- ${c(`Credentials for ${e}`)}
50
- <p class="hint">Stored encrypted for you only (24 hours for anonymous callers, 30 days when signed in) and sent to the API as the <code>${i(o)}</code> header. It never passes through the AI conversation.</p>
60
+ ${n}
61
+ ${y()}
62
+ ${l()}`)}function U(e,t,n,o){return c(`
63
+ ${d(`Credentials for ${e}`)}
64
+ <p class="hint">Stored encrypted for you only (24 hours for anonymous callers, 30 days when signed in) and sent to the API as the <code>${a(n)}</code> header. It never passes through the AI conversation.</p>
51
65
  <form method="post" action="/mcp/credentials" autocomplete="off">
52
- <input type="hidden" name="token" value="${i(t)}">
66
+ <input type="hidden" name="token" value="${a(t)}">
53
67
  <input type="hidden" name="mode" value="token">
54
- <label for="secret">${i(o)} value</label>
68
+ <label for="secret">${a(n)} value</label>
55
69
  <input type="password" id="secret" name="secret" placeholder="value" required autofocus>
56
70
  <button type="submit">Save credentials</button>
57
71
  </form>
58
- ${d()}`)}function U(e,t,o){const n=o.length>0?o.map(i).join(", "):"default scopes";return s(`
59
- ${c(`Credentials for ${e}`)}
72
+ ${o}
73
+ ${y()}
74
+ ${l()}`)}function G(e,t,n,o){const r=n.length>0?n.map(a).join(", "):"default scopes";return c(`
75
+ ${d(`Credentials for ${e}`)}
60
76
  <p class="hint">Paste a token you already have, or let us exchange client credentials for one via OAuth2 client_credentials.</p>
61
77
  <div class="section">
62
78
  <h2>I have a token</h2>
63
79
  <form method="post" action="/mcp/credentials" autocomplete="off">
64
- <input type="hidden" name="token" value="${i(t)}">
80
+ <input type="hidden" name="token" value="${a(t)}">
65
81
  <input type="hidden" name="mode" value="token">
66
82
  <label for="secret">API token</label>
67
83
  <input type="password" id="secret" name="secret" placeholder="your API token" required autofocus>
@@ -71,9 +87,9 @@ ${c(`Credentials for ${e}`)}
71
87
  </div>
72
88
  <div class="section">
73
89
  <h2>Exchange client credentials</h2>
74
- <p class="hint">We request a token from the API on your behalf. Scopes: ${n}.</p>
90
+ <p class="hint">We request a token from the API on your behalf. Scopes: ${r}.</p>
75
91
  <form method="post" action="/mcp/credentials" autocomplete="off">
76
- <input type="hidden" name="token" value="${i(t)}">
92
+ <input type="hidden" name="token" value="${a(t)}">
77
93
  <input type="hidden" name="mode" value="client-credentials">
78
94
  <label for="client_id">Client ID</label>
79
95
  <input type="text" id="client_id" name="client_id" required>
@@ -82,20 +98,27 @@ ${c(`Credentials for ${e}`)}
82
98
  <button type="submit">Exchange &amp; save</button>
83
99
  </form>
84
100
  </div>
85
- ${d()}`)}function j(e,t){return s(`
86
- ${c(`Credentials for ${e}`)}
101
+ ${o}
102
+ ${y()}
103
+ ${l()}`)}function K(e,t,n){return c(`
104
+ ${d(`Credentials for ${e}`)}
87
105
  <p class="hint">The value is stored encrypted for you only (24 hours for anonymous callers, 30 days when signed in) and is sent to the API as the <code>Authorization</code> header. It never passes through the AI conversation.</p>
88
106
  <form method="post" action="/mcp/credentials" autocomplete="off">
89
- <input type="hidden" name="token" value="${i(t)}">
107
+ <input type="hidden" name="token" value="${a(t)}">
90
108
  <input type="hidden" name="mode" value="token">
91
109
  <label for="secret">Authorization header value</label>
92
110
  <input type="password" id="secret" name="secret" placeholder="Bearer &lt;your token&gt;" required autofocus>
93
111
  <button type="submit">Save credentials</button>
94
112
  </form>
95
- ${d()}`)}function x(e){return s(`
96
- ${c("Credentials saved")}
97
- <p>Credentials for ${i(e)} are set for this session. Return to your AI client and continue - confirm the elicitation prompt if it is still open.</p>
98
- ${d()}`)}function h(e){return new Response(s(`
99
- ${c("Exchange failed")}
100
- <p>We could not exchange your client credentials for a token for ${i(e)}. This link has been used up - go back to your AI client and request a new link, then try again.</p>
101
- ${d()}`),{status:502,headers:m})}function u(){return new Response(s(`${c("Link expired")}<p>This credentials link is invalid or has expired. Retry the request in your AI client to get a fresh link.</p>${d()}`),{status:404,headers:m})}var W=z;export{W as default,L as setCredentialsFetchImpl};
113
+ ${n}
114
+ ${y()}
115
+ ${l()}`)}function D(e){return c(`
116
+ ${d("Credentials removed")}
117
+ <p>The stored credentials for ${a(e)} are gone. Calls to that API are anonymous again until you add new ones.</p>
118
+ ${l()}`)}function A(e){return c(`
119
+ ${d("Credentials saved")}
120
+ <p>Credentials for ${a(e)} are saved. Go back to your AI client and ask it to retry - or confirm the prompt it left open, if there is one. You can close this tab.</p>
121
+ ${l()}`)}function f(e){return new Response(c(`
122
+ ${d("Exchange failed")}
123
+ <p>We could not exchange your client credentials for a token for ${a(e)}. This link has been used up - go back to your AI client and request a new link, then try again.</p>
124
+ ${l()}`),{status:502,headers:p})}function u(){return new Response(c(`${d("Link expired")}<p>This credentials link is invalid, already used, or older than ${Math.round(x/6e4)} minutes. Retry the request in your AI client to get a fresh link.</p>${l()}`),{status:404,headers:p})}var V=F;export{V as default,Q as setCredentialsFetchImpl};