@redocly/revel 0.138.0-next.16 → 0.138.0-next.18

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 (104) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/api-schemas/mcp-fetch-count.d.ts +24 -0
  3. package/dist/api-schemas/mcp-fetch-count.js +1 -0
  4. package/dist/cli/develop.js +1 -1
  5. package/dist/cli/eject/ejectTheme.js +1 -1
  6. package/dist/cli/translations/collect-config-translations.js +1 -1
  7. package/dist/client/polyfills.js +1 -1
  8. package/dist/client/utils/catalog/parse-filter-query.js +1 -1
  9. package/dist/constants/api.d.ts +1 -0
  10. package/dist/constants/api.js +1 -1
  11. package/dist/constants/common.d.ts +6 -0
  12. package/dist/constants/common.js +1 -1
  13. package/dist/server/constants/entitlements.js +1 -1
  14. package/dist/server/entitlements/entitlements-provider.js +1 -1
  15. package/dist/server/esbuild/esbuild.js +3 -3
  16. package/dist/server/esbuild/plugins/on-rebuild.js +1 -1
  17. package/dist/server/esbuild/plugins/styled-components-ssr.js +1 -1
  18. package/dist/server/esbuild/plugins/yaml-loader.js +1 -1
  19. package/dist/server/plugins/analytics/amplitude/index.js +1 -1
  20. package/dist/server/plugins/analytics/heap/browser-hooks.js +1 -1
  21. package/dist/server/plugins/api-docs/loader.js +3 -3
  22. package/dist/server/plugins/api-docs/utils/apply-nav-item-to-root.js +1 -1
  23. package/dist/server/plugins/arazzo-docs/arazzo-doc-loader.js +2 -2
  24. package/dist/server/plugins/config-parser/format-custom-tags.js +1 -1
  25. package/dist/server/plugins/config-parser/loaders/content-slugs-loader.js +1 -1
  26. package/dist/server/plugins/config-parser/loaders/versions-config-loader.js +1 -1
  27. package/dist/server/plugins/config-parser/resolve-mutual-exclusion.js +1 -1
  28. package/dist/server/plugins/default-theme/resolve-logo.js +1 -1
  29. package/dist/server/plugins/dev-onboarding/api/adapters/apigee/adapter.js +2 -2
  30. package/dist/server/plugins/dev-onboarding/api/adapters/gravitee/client.js +2 -2
  31. package/dist/server/plugins/dev-onboarding/api/combine.js +1 -1
  32. package/dist/server/plugins/dev-onboarding/template/components/AppOverview.js +1 -1
  33. package/dist/server/plugins/dev-onboarding/template/components/useSortableData.js +1 -1
  34. package/dist/server/plugins/markdown/attribute-resolvers/resolve-svg-content.js +1 -1
  35. package/dist/server/plugins/markdown/markdoc/markdoc-options.js +1 -1
  36. package/dist/server/plugins/markdown/markdown-static-data-loader.js +1 -1
  37. package/dist/server/plugins/markdown/search/get-ai-search-documents.js +1 -1
  38. package/dist/server/plugins/markdown/utils/sanitize-malformed-md-content.js +2 -2
  39. package/dist/server/plugins/mcp/docs-mcp/tools/core/utils.js +1 -1
  40. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-bridge.d.ts +2 -1
  41. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-bridge.js +1 -1
  42. package/dist/server/plugins/mcp/gateway-mcp/fetch-capability.js +1 -1
  43. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-count-client.d.ts +4 -0
  44. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-count-client.js +1 -0
  45. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-count-secret.d.ts +4 -0
  46. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-count-secret.js +1 -0
  47. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-count.d.ts +11 -0
  48. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-count.js +1 -0
  49. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-counter-repository.d.ts +13 -0
  50. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-counter-repository.js +13 -0
  51. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/{limits.d.ts → fetch-limiter/fetch-limits.d.ts} +1 -1
  52. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-limits.js +1 -0
  53. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/{apply-edge-rate-limit.d.ts → request-limiter/apply-edge-rate-limit.d.ts} +1 -1
  54. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/request-limiter/apply-edge-rate-limit.js +1 -0
  55. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/{edge-limiter.d.ts → request-limiter/edge-limiter.d.ts} +3 -3
  56. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/request-limiter/edge-limiter.js +1 -0
  57. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/request-limiter/rate-limit-telemetry.js +1 -0
  58. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/request-limiter/request-limits.d.ts +6 -0
  59. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/request-limiter/request-limits.js +1 -0
  60. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/utils/ip-key.js +1 -0
  61. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/{window-counter.d.ts → utils/window-counter.d.ts} +1 -1
  62. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/utils/window-counter.js +1 -0
  63. package/dist/server/plugins/mcp/handlers/docs-mcp-handler.js +1 -1
  64. package/dist/server/plugins/mcp/handlers/handle-mcp-request.js +1 -1
  65. package/dist/server/plugins/mcp/index.js +1 -1
  66. package/dist/server/plugins/mcp/servers/mcp-server.js +2 -2
  67. package/dist/server/plugins/mcp/types.d.ts +9 -0
  68. package/dist/server/plugins/nav-utils.js +1 -1
  69. package/dist/server/plugins/scorecard-classic/loaders/scorecard.js +1 -1
  70. package/dist/server/plugins/scorecard-classic/template/useData.js +1 -1
  71. package/dist/server/plugins/search/engines/flexsearch/index.js +2 -2
  72. package/dist/server/plugins/sidebars/index.js +2 -2
  73. package/dist/server/plugins/sidebars/utils.js +1 -1
  74. package/dist/server/tools/notifiers/formatter.js +1 -1
  75. package/dist/server/tools/notifiers/helpers/privacy/mask-email.js +1 -1
  76. package/dist/server/tools/notifiers/logger.js +1 -1
  77. package/dist/server/types/web-server.d.ts +1 -0
  78. package/dist/server/utils/codeframes/codeframes.js +2 -2
  79. package/dist/server/utils/entitlements/read-entitlement-number.d.ts +3 -0
  80. package/dist/server/utils/entitlements/read-entitlement-number.js +1 -0
  81. package/dist/server/utils/fs.js +1 -1
  82. package/dist/server/utils/rbac.js +1 -1
  83. package/dist/server/web-server/dev-server.js +1 -1
  84. package/dist/server/web-server/http.d.ts +2 -1
  85. package/dist/server/web-server/http.js +2 -2
  86. package/dist/server/web-server/middleware/catalogAuthMiddleware.js +1 -1
  87. package/dist/server/web-server/middleware/dynamic-middleware/hono-compose.js +1 -1
  88. package/dist/server/web-server/middleware/responseHeadersMiddleware.js +1 -1
  89. package/dist/server/web-server/routes/auth.js +1 -1
  90. package/dist/server/web-server/routes/mcp-routes/mcp-fetch-count.d.ts +8 -0
  91. package/dist/server/web-server/routes/mcp-routes/mcp-fetch-count.js +1 -0
  92. package/dist/server/web-server/routes/mcp-routes/mcp-routes.js +1 -1
  93. package/dist/server/web-server/routes/resolve-route.js +1 -1
  94. package/dist/types/entitlements.d.ts +2 -0
  95. package/dist/utils/time/debounce.js +1 -1
  96. package/package.json +5 -5
  97. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/apply-edge-rate-limit.js +0 -1
  98. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/edge-limiter.js +0 -1
  99. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/ip-key.js +0 -1
  100. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/limits.js +0 -1
  101. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/rate-limit-telemetry.js +0 -1
  102. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/window-counter.js +0 -1
  103. /package/dist/server/plugins/mcp/gateway-mcp/rate-limit/{rate-limit-telemetry.d.ts → request-limiter/rate-limit-telemetry.d.ts} +0 -0
  104. /package/dist/server/plugins/mcp/gateway-mcp/rate-limit/{ip-key.d.ts → utils/ip-key.d.ts} +0 -0
@@ -1 +1 @@
1
- import*as e from"react";import{Flex as i}from"@redocly/portal-legacy-ui";import{H2 as m}from"@redocly/theme/components/Typography/H2";import{Dropdown as l}from"@redocly/theme/components/Dropdown/Dropdown";import{DropdownMenu as p}from"@redocly/theme/components/Dropdown/DropdownMenu";import{OverflowMenuHorizontalIcon as c}from"@redocly/theme/icons/OverflowMenuHorizontalIcon/OverflowMenuHorizontalIcon";import{Button as s}from"@redocly/theme/components/Button/Button";import{useTranslate as u}from"../../../../../client/app/hooks";import{DeleteAppDialog as d}from"./DeleteAppDialog";import{EditAppDialog as f}from"./EditAppDialog";function g(o){const r=e.useRef(null),t=e.useRef(null),{app:n}=o,{translate:a}=u();return e.createElement(i,{alignItems:"flex-start",justifyContent:"space-between",mb:"1.5rem"},e.createElement("div",null,e.createElement(m,{style:{marginBottom:"0.5rem"}},n.name)),e.createElement(l,{trigger:e.createElement(s,{variant:"text",icon:e.createElement(c,{size:"24px"})}),alignment:"end"},e.createElement(p,{items:[{onAction:()=>r.current.open(),content:a("dev.app.edit","Edit"),dataAttributes:{"data-translation-key":"dev.app.edit"}},{onAction:()=>t.current.open(),content:a("dev.app.delete","Delete"),dataAttributes:{"data-translation-key":"dev.app.delete"}}]})),e.createElement(f,{ref:r,appName:n.name,appDisplayName:n.name}),e.createElement(d,{ref:t}))}function R(o){const r={day:"numeric",month:"short",year:"numeric"},t=new Date(parseInt(o,10));let n=Math.abs(t-Date.now())<3600*24*1e3;return t.toLocaleString("en-US",{...r,...n?{hour:"numeric",minute:"numeric"}:{}})}export{g as default,R as getReadableDate};
1
+ import*as e from"react";import{Flex as i}from"@redocly/portal-legacy-ui";import{H2 as m}from"@redocly/theme/components/Typography/H2";import{Dropdown as l}from"@redocly/theme/components/Dropdown/Dropdown";import{DropdownMenu as p}from"@redocly/theme/components/Dropdown/DropdownMenu";import{OverflowMenuHorizontalIcon as c}from"@redocly/theme/icons/OverflowMenuHorizontalIcon/OverflowMenuHorizontalIcon";import{Button as s}from"@redocly/theme/components/Button/Button";import{useTranslate as u}from"../../../../../client/app/hooks";import{DeleteAppDialog as d}from"./DeleteAppDialog";import{EditAppDialog as f}from"./EditAppDialog";function g(o){const r=e.useRef(null),t=e.useRef(null),{app:n}=o,{translate:a}=u();return e.createElement(i,{alignItems:"flex-start",justifyContent:"space-between",mb:"1.5rem"},e.createElement("div",null,e.createElement(m,{style:{marginBottom:"0.5rem"}},n.name)),e.createElement(l,{trigger:e.createElement(s,{variant:"text",icon:e.createElement(c,{size:"24px"})}),alignment:"end"},e.createElement(p,{items:[{onAction:()=>r.current.open(),content:a("dev.app.edit","Edit"),dataAttributes:{"data-translation-key":"dev.app.edit"}},{onAction:()=>t.current.open(),content:a("dev.app.delete","Delete"),dataAttributes:{"data-translation-key":"dev.app.delete"}}]})),e.createElement(f,{ref:r,appName:n.name,appDisplayName:n.name}),e.createElement(d,{ref:t}))}function R(o){const r={day:"numeric",month:"short",year:"numeric"},t=new Date(parseInt(o,10)),n=Math.abs(t-Date.now())<3600*24*1e3;return t.toLocaleString("en-US",{...r,...n?{hour:"numeric",minute:"numeric"}:{}})}export{g as default,R as getReadableDate};
@@ -1 +1 @@
1
- import*as s from"react";const a=(i,o)=>{const[e,c]=s.useState(o),d=s.useMemo(()=>{let n=[...i];return e!==null&&n.sort((t,r)=>t[e.key]<r[e.key]?e.direction==="ascending"?-1:1:t[e.key]>r[e.key]?e.direction==="ascending"?1:-1:0),n},[i,e]),u=s.useCallback(n=>{c(t=>{let r="descending";return t&&t.key===n&&t.direction==="descending"&&(r="ascending"),{key:n,direction:r}})},[]);return{sortedItems:d,requestSort:u,sortConfig:e}};export{a as useSortableData};
1
+ import*as s from"react";const a=(o,i)=>{const[e,c]=s.useState(i),d=s.useMemo(()=>{const n=[...o];return e!==null&&n.sort((t,r)=>t[e.key]<r[e.key]?e.direction==="ascending"?-1:1:t[e.key]>r[e.key]?e.direction==="ascending"?1:-1:0),n},[o,e]),u=s.useCallback(n=>{c(t=>{let r="descending";return t&&t.key===n&&t.direction==="descending"&&(r="ascending"),{key:n,direction:r}})},[]);return{sortedItems:d,requestSort:u,sortConfig:e}};export{a as useSortableData};
@@ -1 +1 @@
1
- import{getNodeAttribute as m}from"../../../../markdoc/helpers/get-node-attribute.js";import{setNodeAttributeValue as l}from"../../../../markdoc/helpers/set-node-attribute-value.js";import{optimize as p}from"../../../../compiled/svgo/svgo-node.js";import{slug as c}from"../../../utils/index.js";import{MdResolveError as d}from"./md-resolve-error.js";import{resolvePathInMarkdown as u}from"../resolve-path-in-markdown.js";async function b(o,r,n,{context:i,actions:s}){const e=m(o,r),a=s.contentDir;if(e){const t=u(e,n,a);if(!i.fs.exists(t))throw new d(`File ${t} does not exist`,{rawLink:e});let f=p(await i.fs.read(t),{plugins:[{name:"prefixIds",params:{prefix:c(t).replaceAll(/[/.]/g,"-")}}]}).data;l(o,r+"RawContent",f)}}export{b as resolveSvgContent};
1
+ import{getNodeAttribute as m}from"../../../../markdoc/helpers/get-node-attribute.js";import{setNodeAttributeValue as p}from"../../../../markdoc/helpers/set-node-attribute-value.js";import{optimize as l}from"../../../../compiled/svgo/svgo-node.js";import{slug as c}from"../../../utils/index.js";import{MdResolveError as d}from"./md-resolve-error.js";import{resolvePathInMarkdown as u}from"../resolve-path-in-markdown.js";async function b(e,r,n,{context:i,actions:s}){const o=m(e,r),a=s.contentDir;if(o){const t=u(o,n,a);if(!i.fs.exists(t))throw new d(`File ${t} does not exist`,{rawLink:o});const f=l(await i.fs.read(t),{plugins:[{name:"prefixIds",params:{prefix:c(t).replaceAll(/[/.]/g,"-")}}]}).data;p(e,r+"RawContent",f)}}export{b as resolveSvgContent};
@@ -1 +1 @@
1
- import f from"@markdoc/markdoc";import*as p from"@redocly/theme/markdoc/default";import{reporter as g}from"../../../tools/notifiers/reporter.js";import*as l from"../../../../markdoc/nodes/index.js";import u from"../../../../markdoc/tags/index.js";import{normalizeCustomTags as d}from"../../../../markdoc/helpers/normalize-custom-tags.js";import{includes as h}from"./functions/includes.js";import{importUserTags as O}from"./import-user-tags.js";import{concat as y}from"./functions/concat.js";const T=p.tags;function b(r){const{tags:c,functions:e={},...s}=r,n={...c,...s};let a=Object.entries(n);const o=[],t=[];for(const[i,m]of a)i in f.nodes?o.push([i,m]):t.push([i,m]);return{nodes:Object.fromEntries(o),tags:Object.fromEntries(t),functions:e}}async function _(r="../../../esbuild/cache/server/",c=""){let e;try{e=(await O(r)).schema}catch(t){throw t.code==="ERR_MODULE_NOT_FOUND"?(g.printErrors(),new Error("Please fix the errors above and try again.")):t}const{tags:s={},nodes:n={},functions:a={}}=b(e),o={tags:{schemaDefinition:{render:"SchemaDefinition",attributes:{schemaRef:{type:String},exampleRef:{type:String},showReadOnly:{type:Boolean},showWriteOnly:{type:Boolean}}}}};return{nodes:{...l,...n},tags:{...u,...T,...d(s),...o.tags},functions:{...f.functions,...a,includes:h,concat:y}}}export{_ as getMarkdocOptions};
1
+ import f from"@markdoc/markdoc";import*as p from"@redocly/theme/markdoc/default";import{reporter as g}from"../../../tools/notifiers/reporter.js";import*as u from"../../../../markdoc/nodes/index.js";import d from"../../../../markdoc/tags/index.js";import{normalizeCustomTags as l}from"../../../../markdoc/helpers/normalize-custom-tags.js";import{includes as h}from"./functions/includes.js";import{importUserTags as O}from"./import-user-tags.js";import{concat as y}from"./functions/concat.js";const T=p.tags;function b(s){const{tags:c,functions:e={},...r}=s,n={...c,...r},a=Object.entries(n),o=[],t=[];for(const[i,m]of a)i in f.nodes?o.push([i,m]):t.push([i,m]);return{nodes:Object.fromEntries(o),tags:Object.fromEntries(t),functions:e}}async function _(s="../../../esbuild/cache/server/",c=""){let e;try{e=(await O(s)).schema}catch(t){throw t.code==="ERR_MODULE_NOT_FOUND"?(g.printErrors(),new Error("Please fix the errors above and try again.")):t}const{tags:r={},nodes:n={},functions:a={}}=b(e),o={tags:{schemaDefinition:{render:"SchemaDefinition",attributes:{schemaRef:{type:String},exampleRef:{type:String},showReadOnly:{type:Boolean},showWriteOnly:{type:Boolean}}}}};return{nodes:{...u,...n},tags:{...d,...T,...l(r),...o.tags},functions:{...f.functions,...a,includes:h,concat:y}}}export{_ as getMarkdocOptions};
@@ -1 +1 @@
1
- import D from"path";import{REDOCLY_TEAMS_RBAC as v,REDOCLY_ROUTE_RBAC as A}from"@redocly/config";import{removeTrailingSlash as M}from"../../../utils/url/remove-trailing-slash.js";import{addLeadingSlash as N}from"../../../utils/url/add-leading-slash.js";import{shaHexShort as E}from"../../utils/crypto/sha-hex-short.js";import{isDefined as I}from"../../../utils/guards/is-defined.js";import{resolveFrontmatterKeys as L}from"../resolve-frontmatter-keys.js";import{resolveItem as _}from"../nav-utils.js";const R=new WeakMap;async function j(o,i,s,e,a){const d=await s.getConfig(),{ast:l,compoundHash:n,info:g}=await a.parseMarkdoc({input:o,context:s}),r={...d.markdown?.editPage,...e.markdown?.editPage},t=i.fsPath!=null?s.fs.getFileInfo(i.fsPath):null,f=!r?.hide&&r?.baseUrl?{to:M(r.baseUrl)+N(t?.realRelativePath||i.fsPath||"")}:void 0,w={...d.feedback,...e.feedback},m=e.feedback?{type:w.type||"sentiment",settings:w.settings||{},hide:e.feedback?.hide}:void 0;let h;Array.isArray(e.banner)&&(h=(await Promise.all(e.banner.map(async(B,O)=>{const{content:k,...b}=B;if(!k)return;const{ast:T}=await a.parseMarkdoc({input:{content:k,relativePath:o.relativePath,isVirtual:o.isVirtual},context:s,resource:`${o.relativePath}#banner${O}`});return{...b,...b.rbac?{[v]:b.rbac}:{},ast:JSON.stringify(T),hash:E(k)}}))).filter(I));let y=R.get(l),c=y?.ast;(!c||y?.compoundHash!==n)&&(c=JSON.stringify(l),R.set(l,{ast:c,compoundHash:n}));let p;e.navigation&&(p={...e.navigation,nextButton:await S(e.navigation.nextButton,i,a,s),previousButton:await S(e.navigation.previousButton,i,a,s)});const u=e?.seo?await L(e.seo,["image"],i.fsPath||"",a,s):void 0,P=g.tagList;Array.isArray(P)&&P.includes("code-walkthrough")&&(e.markdown=e.markdown||{},e.markdown.toc={hide:!0},e.footer=e.footer||{},e.footer={hide:!0});const C=e?.seo?.title||await i.getNavText?.();return{ast:c,frontmatter:await L({...e,...m!==void 0?{feedback:m}:{},...u!==void 0?{seo:u}:{},...p!==void 0?{navigation:p}:{},...h!==void 0?{banner:h}:{}},d.markdown?.frontMatterKeysToResolve||["image","links"],i.fsPath||"",a,s),editPage:f,props:{metadata:{markdoc:{tagList:g.tagList}},seo:{...u,...C?{title:C}:{}}},[v]:i[v],[A]:i[A]}}async function S(o,i,s,e){if(!o||typeof o!="object")return;let a=o,d,l;if("page"in a&&typeof a.page=="string"){const{page:t,...f}=a;a=f,d=t}if("label"in a&&typeof a.label=="string"){const{label:t,...f}=a;a=f,l=t}let n;if(d){const t=await _({page:d},i.fsPath?D.dirname(i.fsPath):".",s,e,{navFile:i.fsPath||""});n=Array.isArray(t)?t[0]:t,n=n?.type!=="error"?n:void 0}const g=n?.routeSlug,r=l??n?.label;return{...a,label:r,link:g}}export{j as markdownStaticDataLoader};
1
+ import D from"path";import{REDOCLY_TEAMS_RBAC as v,REDOCLY_ROUTE_RBAC as A}from"@redocly/config";import{removeTrailingSlash as M}from"../../../utils/url/remove-trailing-slash.js";import{addLeadingSlash as N}from"../../../utils/url/add-leading-slash.js";import{shaHexShort as E}from"../../utils/crypto/sha-hex-short.js";import{isDefined as I}from"../../../utils/guards/is-defined.js";import{resolveFrontmatterKeys as L}from"../resolve-frontmatter-keys.js";import{resolveItem as _}from"../nav-utils.js";const R=new WeakMap;async function j(o,i,s,e,a){const d=await s.getConfig(),{ast:r,compoundHash:n,info:g}=await a.parseMarkdoc({input:o,context:s}),l={...d.markdown?.editPage,...e.markdown?.editPage},t=i.fsPath!=null?s.fs.getFileInfo(i.fsPath):null,f=!l?.hide&&l?.baseUrl?{to:M(l.baseUrl)+N(t?.realRelativePath||i.fsPath||"")}:void 0,w={...d.feedback,...e.feedback},m=e.feedback?{type:w.type||"sentiment",settings:w.settings||{},hide:e.feedback?.hide}:void 0;let h;Array.isArray(e.banner)&&(h=(await Promise.all(e.banner.map(async(B,O)=>{const{content:k,...b}=B;if(!k)return;const{ast:T}=await a.parseMarkdoc({input:{content:k,relativePath:o.relativePath,isVirtual:o.isVirtual},context:s,resource:`${o.relativePath}#banner${O}`});return{...b,...b.rbac?{[v]:b.rbac}:{},ast:JSON.stringify(T),hash:E(k)}}))).filter(I));const y=R.get(r);let c=y?.ast;(!c||y?.compoundHash!==n)&&(c=JSON.stringify(r),R.set(r,{ast:c,compoundHash:n}));let p;e.navigation&&(p={...e.navigation,nextButton:await S(e.navigation.nextButton,i,a,s),previousButton:await S(e.navigation.previousButton,i,a,s)});const u=e?.seo?await L(e.seo,["image"],i.fsPath||"",a,s):void 0,P=g.tagList;Array.isArray(P)&&P.includes("code-walkthrough")&&(e.markdown=e.markdown||{},e.markdown.toc={hide:!0},e.footer=e.footer||{},e.footer={hide:!0});const C=e?.seo?.title||await i.getNavText?.();return{ast:c,frontmatter:await L({...e,...m!==void 0?{feedback:m}:{},...u!==void 0?{seo:u}:{},...p!==void 0?{navigation:p}:{},...h!==void 0?{banner:h}:{}},d.markdown?.frontMatterKeysToResolve||["image","links"],i.fsPath||"",a,s),editPage:f,props:{metadata:{markdoc:{tagList:g.tagList}},seo:{...u,...C?{title:C}:{}}},[v]:i[v],[A]:i[A]}}async function S(o,i,s,e){if(!o||typeof o!="object")return;let a=o,d,r;if("page"in a&&typeof a.page=="string"){const{page:t,...f}=a;a=f,d=t}if("label"in a&&typeof a.label=="string"){const{label:t,...f}=a;a=f,r=t}let n;if(d){const t=await _({page:d},i.fsPath?D.dirname(i.fsPath):".",s,e,{navFile:i.fsPath||""});n=Array.isArray(t)?t[0]:t,n=n?.type!=="error"?n:void 0}const g=n?.routeSlug,l=r??n?.label;return{...a,label:l,link:g}}export{j as markdownStaticDataLoader};
@@ -1,6 +1,6 @@
1
1
  import M from"@markdoc/markdoc";import D from"node:path";import{AI_SEARCH_CHUNK_SIZE as R,AI_SEARCH_DOCUMENT_CHUNK_SIZE as v}from"../../../constants/plugins/search.js";import{slugger as I}from"../../../../utils/slugger.js";import{joinSectionContent as N}from"./join-section-content.js";import{toMarkdown as P}from"./to-markdown.js";import{AstToSearchNodeTransformer as w}from"./walk-sections.js";import{createRenderTagFn as x}from"./create-render-tag-fn.js";import{getBuildTranslateContext as K}from"../../../utils/l10n/get-build-translate-context.js";import{getLocaleFromRelativePath as L}from"../../../fs/utils/get-locale-from-relative-path.js";import{HeadingNode as b}from"./nodes/heading-node.js";import{extractDocumentSearchFacets as U}from"./search-facets.js";import{formatDocumentMetadata as y}from"../../search/utils.js";import{TagNode as H}from"./nodes/tag-node.js";import{MARKDOC_PARTIALS_DATA_KEY as O}from"../../../constants/common.js";const j=Symbol(),at=(n,o)=>async(e,r,i,c)=>{if(o?.excludeFromSearch)return;I.reset();const l=await e.getNavText?.()||D.basename(e.fsPath),f=M.Ast.fromJSON(r.ast),{locale:g,translate:A}=await K(e.slug,c.getGlobalData?.().l10n),s=new w({partials:c.getGlobalConfig(O)||{},renderTag:x(c.markdocOptions.tags,g,A),getInnerContent:P,ast:f,skipConditionals:!0}),d=Array.from(s.transform());return{async getLLMsTxts(){const u=o?.seo,t=u&&typeof u=="object"&&"description"in u?String(u.description):void 0,a=N(d);return[{title:l,description:t,slug:e.slug,fsPath:e.fsPath,content:a,includeInLLMsTxt:!0}]},async getSearchDocuments(){return F(e,d,n,l)}}};function F(n,o,e,r){const i=n.slug,c=new Map,l=n.metadata||{},f=U(l,e),g=y(l),A=g?`Metadata:
2
2
  ${g}
3
- `:"";let s=[];for(const t of o)if(t instanceof b){s.splice(t.attributes.level-1,6);const a=t.attributes.level>2,m=s[s.length-1]?.title,T=s[s.length-1]?.url,C=a&&m?m:W(r,t),S=a&&T?T:t.getUrl(i),p={title:C,content:"",url:S,level:t.attributes.level,toc:s.map(E=>h(E.section)).join(`
3
+ `:"",s=[];for(const t of o)if(t instanceof b){s.splice(t.attributes.level-1,6);const a=t.attributes.level>2,m=s[s.length-1]?.title,T=s[s.length-1]?.url,C=a&&m?m:W(r,t),S=a&&T?T:t.getUrl(i),p={title:C,content:"",url:S,level:t.attributes.level,toc:s.map(E=>h(E.section)).join(`
4
4
  `)};(t.attributes.level>1||h(t)!==r)&&(p.content+=p.content?`
5
5
  `:"",p.content+=t.content.trimEnd()),c.set(t.id,p),s.push({section:t,title:C,url:S})}else if(t instanceof H)c.set(t.id,{title:t.content,content:t.content,url:t.getUrl(i),level:t.attributes.level+1,toc:s.map(a=>h(a.section)).concat(t.content).join(`
6
6
  `)});else{const a=t.parentNode?.id??j,m=c.get(a)??{title:r,content:"",url:i,level:1/0,toc:""};m.content+=m.content?`
@@ -1,2 +1,2 @@
1
- function c(n){return n.replace(/\[(?<!\[[^\]()]*?\[)([^\]<]*<[^>]*>[^\]]*)\]\(([^)]+)\)/g,(r,t,s)=>`[${t.replace(/(`{1,16})[^`]+\1|[<>]/g,e=>e==="<"?"&lt;":e===">"?"&gt;":e)}](${s})`).replace(/({%\s*(\w+)(?:\s[^%]*)?%})(\s*)({%\s*\/\2\s*%})/g,(r,t,s,l,e)=>`${t}
2
- ${e}`)}export{c as sanitizeMalformedMdContent};
1
+ function a(n){return n.replace(/\[(?<!\[[^\]()]*?\[)([^\]<]*<[^>]*>[^\]]*)\]\(([^)]+)\)/g,(r,s,t)=>`[${s.replace(/(`{1,16})[^`]+\1|[<>]/g,e=>e==="<"?"&lt;":e===">"?"&gt;":e)}](${t})`).replace(/({%\s*(\w+)(?:\s[^%]*)?%})(\s*)({%\s*\/\2\s*%})/g,(r,s,t,c,e)=>`${s}
2
+ ${e}`)}export{a as sanitizeMalformedMdContent};
@@ -1,6 +1,6 @@
1
1
  import c from"node:fs";import f from"node:path";import{MAX_DOCUMENTS_PER_CATEGORY as $}from"../../../constants.js";function j(m,u){return Object.entries(m).map(([h,s])=>{if(!s||s.length===0)return"";const x=s.slice(0,$).map(d=>{const{document:t,highlight:o}=d,p=o?.title||(Array.isArray(t.title)?t.title[0]:t.title);let n=`Document: ${t.title}`;n+=`### [${p}](${t.url})
2
2
 
3
- `;let r;if(t.url)try{let e=t.url.startsWith("/")?t.url.slice(1):t.url;const i=e.indexOf("#");i!==-1&&(e=e.substring(0,i));const l=f.extname(e);l&&(e=e.slice(0,-l.length));let y=e+".md";const a=f.join(u,y);c.existsSync(a)&&(r=c.readFileSync(a,"utf8"))}catch{}return r||(r=o?.text||(Array.isArray(t.text)?t.text[0]:t.text)),n+=r,t.facets&&(n+=`
3
+ `;let r;if(t.url)try{let e=t.url.startsWith("/")?t.url.slice(1):t.url;const i=e.indexOf("#");i!==-1&&(e=e.substring(0,i));const l=f.extname(e);l&&(e=e.slice(0,-l.length));const y=e+".md",a=f.join(u,y);c.existsSync(a)&&(r=c.readFileSync(a,"utf8"))}catch{}return r||(r=o?.text||(Array.isArray(t.text)?t.text[0]:t.text)),n+=r,t.facets&&(n+=`
4
4
 
5
5
  **Categories:**
6
6
  `,Object.entries(t.facets).forEach(([e,i])=>{n+=`- ${e}: ${i}
@@ -35,12 +35,13 @@ type FetchBridgeOptions = {
35
35
  timeoutMs?: number;
36
36
  maxResponseBytes?: number;
37
37
  maxFetchesPerExec?: number;
38
+ beforeSend?: () => Promise<void>;
38
39
  clientIp?: string;
39
40
  /** Upper-case methods that may reach the network (`mcp.apiCalls.allowedHttpMethods`); undefined allows all. */
40
41
  allowedHttpMethods?: readonly string[];
41
42
  };
42
43
  /** Build the host-side `fetch` the gateway sandbox calls. */
43
- export declare function buildFetchBridge({ allowedHosts, apis, authorization, onAuthRequired, fetchImpl, dispatcher, timeoutMs, maxResponseBytes, maxFetchesPerExec, clientIp, allowedHttpMethods, }: FetchBridgeOptions): SandboxFetch;
44
+ export declare function buildFetchBridge({ allowedHosts, apis, authorization, onAuthRequired, fetchImpl, dispatcher, timeoutMs, maxResponseBytes, maxFetchesPerExec, beforeSend, clientIp, allowedHttpMethods, }: FetchBridgeOptions): SandboxFetch;
44
45
  export declare function readCappedBody(response: Response, maxBytes: number): Promise<string>;
45
46
  export {};
46
47
  //# sourceMappingURL=fetch-bridge.d.ts.map
@@ -1 +1 @@
1
- import{Agent as ce}from"undici";import{envConfig as ie}from"../../../../config/env-config.js";import{logger as K}from"../../../../tools/notifiers/logger.js";import{auditHeaderNames as le}from"../../audit/audit-log.js";import{assertUrlAllowed as X}from"./ssrf-guard.js";import{createSsrfSafeLookup as de}from"../../../../web-server/utils/ssrf-safe-lookup.js";import{SsrfBlockedError as J}from"../../../../web-server/utils/ssrf-blocklist.js";import{GatewayFetchBlockedError as p,GatewayFetchSystemError as ue,GatewayFetchUserError as S}from"./fetch-errors.js";import{redactHeaders as fe,redactSecretValuesFromHeaders as he,redactSecretsFromString as _,secretEchoForms as me}from"./redact.js";import{extractUpstreamDetail as pe}from"./upstream-detail.js";import{normalizeFetchPath as we}from"./normalize-path.js";import{reportGatewayFetch as Z}from"./fetch-telemetry.js";import{logGatewayFetchAudit as q}from"./fetch-audit.js";import{matchingApiCallOperations as ye}from"../api-call-operations.js";import{operationHttpMethod as Se}from"../route-pattern.js";const ee="x-forwarded-for",be=3e4,ge=1024*1024,Ae=/%2f|%5c/i,Ee=new Set([401,403,407]),ve=["authorization","proxy-authorization","cookie","x-api-key"];let x;function $e(){if(!x){const e=ie.isDevelopMode;e&&K.warn("Gateway MCP SSRF guard: loopback upstreams are ALLOWED (develop mode only, never in runtime)."),x=new ce({connect:{lookup:de({allowLoopback:e})}})}return x}function rt({allowedHosts:e,apis:t=[],authorization:r,onAuthRequired:n,fetchImpl:c=fetch,dispatcher:d=$e(),timeoutMs:f=be,maxResponseBytes:b=ge,maxFetchesPerExec:i,clientIp:oe,allowedHttpMethods:P}){const O=[...e,...t.flatMap(a=>a.matchers)],ae=[...new Set(t.map(a=>a.name).filter(Boolean))].join(", "),j=t.flatMap(a=>Object.values(a.secrets??{})),w=[...r?[{header:"Authorization",value:r}]:[],...j].flatMap(me),se=j.map(a=>a.header),I=t.flatMap(a=>a.credentialQueryParams??[]);let N=0,B=!1;return async a=>{const l=Se(a.method??"GET"),D=Date.now();let G,A,E,v={},$,U,H;try{if(i!==void 0){const o=Math.max(0,i);if(N+=1,N>o)throw new S(`Outbound fetch budget exceeded (max ${o} fetches per execute); split the work across calls`)}if(P&&!P.includes(l))throw new p(`${l} is not allowed: this project permits only ${P.join(", ")} API calls`,te(a.url));const s=Le(a.url,O,ae);if(G=s.hostname,A=we(s.pathname),Ae.test(s.pathname))throw new p(`${l} ${s.pathname} is refused: the path holds an encoded path separator (%2F or %5C), which could reach an operation other than the documented one`,s.hostname);const y=t.filter(o=>o.matchers.some(T=>T.test(s.hostname))),m=y.flatMap(o=>o.operations?ye(o.operations,s.pathname,l).map(T=>({api:o,callable:T.callable,visible:T.visible,variables:T.variables})):[{api:o,callable:!0,visible:!0,variables:Number.POSITIVE_INFINITY}]),k=Math.min(...m.map(o=>o.variables)),M=m.filter(o=>o.variables===k);if(y.length>0&&(m.length===0||M.some(o=>!o.visible)))throw new p(`${l} ${s.pathname} is not a documented operation of ${y.map(o=>o.name).join(", ")}; \`fetch\` calls only the operations the API description documents`,s.hostname);const V=M.find(o=>!o.callable);if(V)throw new p(`${l} ${s.pathname} is not callable: this operation of ${V.api.name} is excluded from agent calls`,s.hostname);const h=new Set(M.map(o=>o.api)).size===1?M[0].api:void 0;E=h?.slug;const z=Ue(a.securityScheme,h),F=z?[z]:h?Fe(h,s.pathname,l):void 0,C=h&&F?Pe(h,F):void 0,Q=C?h?.secrets?.[C]:void 0,L=Q??(r?{header:"Authorization",value:r}:void 0);v=Oe({callerHeaders:a.headers,injected:L,clientIp:oe});const W=_e(a.headers,s,y,L);if(W&&(L||B))throw new S(xe(W,!!L));B||=!!L,$=Q?C:void 0;const Y=new AbortController;H=setTimeout(()=>Y.abort(),f);const u=await c(s,{method:l,headers:v,body:a.body,redirect:"manual",signal:Y.signal,dispatcher:d});U=u.status;let R="";try{R=await Me(u,b)}finally{if(Ee.has(u.status)&&h&&n){const o=pe(R,u.headers.get("content-type"),w);n({api:h.slug,name:h.name,status:u.status,...C?{scheme:C}:{},...F?.length?{schemes:[...F]}:{},...$?{sentScheme:$}:{},...o?{detail:o}:{}})}}let g=he(je(u.headers),w);if(Re(u.status)){g=fe(g,se);const{location:o}=g;o&&!Te(o,s,O)&&delete g.location}return Z({host:s.hostname,method:l,outcome:"success",durationMs:Date.now()-D,path:A,api:E,status:u.status,bytes:Buffer.byteLength(R,"utf8")}),q({rawUrl:s.href,method:l,host:s.hostname,outcome:"success",durationMs:Date.now()-D,injectedSecretValues:w,credentialQueryParams:I,path:A,api:E,status:u.status,injectedScheme:$,requestHeaders:v,requestBody:a.body,responseHeaders:g,responseBody:R}),{status:u.status,statusText:_(u.statusText,w),ok:u.ok,headers:g,bodyText:_(R,w)}}catch(s){const{outcome:y,userSafe:m}=Ce(s),k=G??(m instanceof p?m.host:void 0)??te(a.url)??"invalid-url";throw Z({host:k,method:l,outcome:y,durationMs:Date.now()-D,path:A,api:E,status:U}),q({rawUrl:a.url,method:l,host:k,outcome:y,durationMs:Date.now()-D,injectedSecretValues:w,credentialQueryParams:I,path:A,api:E,status:U,injectedScheme:$,requestHeaders:v,requestBody:a.body}),m||(K.error(`Gateway MCP fetch failed [${l} ${k}]: ${_(De(s),w)} headers=${le(v)}`),new ue("Upstream fetch failed due to an internal error"))}finally{H!==void 0&&clearTimeout(H)}}}function ke(e){let t=e;for(let r=0;r<5&&t instanceof Error;r++){if(t instanceof S||t instanceof J)return t;t=t.cause}}function Ce(e){const t=ke(e);return{outcome:t instanceof p||t instanceof J?"blocked":t?"user_error":"system_error",userSafe:t}}function Le(e,t,r){try{return X(e,t)}catch(n){throw n instanceof p&&r?new p(`${n.message}. \`fetch\` reaches only the documented hosts of the callable APIs (${r}); other documented APIs can be read through the tools but not called.`,n.host):n}}function te(e){try{return new URL(e).hostname||void 0}catch{return}}function Re(e){return e>=300&&e<400}function Te(e,t,r){try{return!!X(new URL(e,t).href,r)}catch{return!1}}function De(e){if(!(e instanceof Error))return String(e);const t=e.cause;return t instanceof Error?`${e.message}: ${t.message}`:e.message}async function Me(e,t){const r=Number(e.headers.get("content-length"));if(Number.isFinite(r)&&r>t)throw re(t,r);if(!e.body)return"";const n=e.body.getReader(),c=new TextDecoder;let d=0,f="";try{for(;;){const{done:b,value:i}=await n.read();if(b)break;if(d+=i.byteLength,d>t)throw re(t,d);f+=c.decode(i,{stream:!0})}return f+c.decode()}finally{await n.cancel().catch(()=>{})}}function re(e,t){return new S(`Response is too large (over ${e} bytes, got ${t}); request less data`)}function Fe(e,t,r){const n=e.routes?.find(c=>c.method===r&&c.pattern.test(t));return n?n.schemes:He(e)}function Pe(e,t){const r=t.filter(n=>e.secrets?.[n]);return r.length===0?t[0]:r.reduce((n,c)=>{const d=e.secrets?.[n]?.savedAt??0;return(e.secrets?.[c]?.savedAt??0)>d?c:n})}function Ue(e,t){if(e==null)return;if(typeof e!="string")throw new S("securityScheme must be a security scheme name (a string)");if(!t)throw new S("securityScheme cannot be applied: the URL host does not identify exactly one callable API");const r=t.schemePriority??[];if(r.length>0&&!r.includes(e))throw new S(`Unknown securityScheme "${e}" for ${t.name}; valid values: ${r.join(", ")}`);return e}function He(e){const t=Object.keys(e.secrets??{});if(t.length===0)return e.defaultScheme?[e.defaultScheme]:[];const r=(e.schemePriority??[]).filter(n=>t.includes(n));return[...r,...t.filter(n=>!r.includes(n))]}function _e(e,t,r,n){const c=ne([...ve,...r.flatMap(i=>i.credentialHeaders??[]),...n?[n.header]:[]]),d=Object.keys(e??{}).find(i=>c.has(i.toLowerCase()));if(d)return{kind:"header",name:d};const f=ne(r.flatMap(i=>i.credentialQueryParams??[])),b=[...t.searchParams.keys()].find(i=>f.has(i.toLowerCase()));return b?{kind:"query parameter",name:b}:void 0}function ne(e){return new Set(e.map(t=>t.toLowerCase()))}function xe({kind:e,name:t},r){return r?`Remove the "${t}" ${e}: this request already sends the stored credential.`:`Remove the "${t}" ${e}: this script already sent a stored credential, so it cannot also send its own.`}function Oe({callerHeaders:e,injected:t,clientIp:r}){const n={};for(const[c,d]of Object.entries(e??{})){const f=c.toLowerCase();f==="host"||f==="content-length"||f!==ee&&(n[c]=d)}return r&&(n[ee]=r),t&&(n[t.header]=t.value),n}function je(e){const t={};return e.forEach((r,n)=>{t[n.toLowerCase()]=r}),t}export{rt as buildFetchBridge,$e as getDefaultAgent,Me as readCappedBody};
1
+ import{Agent as ie}from"undici";import{envConfig as le}from"../../../../config/env-config.js";import{logger as K}from"../../../../tools/notifiers/logger.js";import{auditHeaderNames as de}from"../../audit/audit-log.js";import{assertUrlAllowed as X}from"./ssrf-guard.js";import{createSsrfSafeLookup as ue}from"../../../../web-server/utils/ssrf-safe-lookup.js";import{SsrfBlockedError as J}from"../../../../web-server/utils/ssrf-blocklist.js";import{GatewayFetchBlockedError as p,GatewayFetchSystemError as fe,GatewayFetchUserError as S}from"./fetch-errors.js";import{redactHeaders as he,redactSecretValuesFromHeaders as me,redactSecretsFromString as _,secretEchoForms as pe}from"./redact.js";import{extractUpstreamDetail as we}from"./upstream-detail.js";import{normalizeFetchPath as ye}from"./normalize-path.js";import{reportGatewayFetch as Z}from"./fetch-telemetry.js";import{logGatewayFetchAudit as q}from"./fetch-audit.js";import{matchingApiCallOperations as Se}from"../api-call-operations.js";import{operationHttpMethod as be}from"../route-pattern.js";const ee="x-forwarded-for",ge=3e4,Ae=1024*1024,Ee=/%2f|%5c/i,ve=new Set([401,403,407]),$e=["authorization","proxy-authorization","cookie","x-api-key"];let x;function ke(){if(!x){const e=le.isDevelopMode;e&&K.warn("Gateway MCP SSRF guard: loopback upstreams are ALLOWED (develop mode only, never in runtime)."),x=new ie({connect:{lookup:ue({allowLoopback:e})}})}return x}function nt({allowedHosts:e,apis:t=[],authorization:r,onAuthRequired:n,fetchImpl:c=fetch,dispatcher:d=ke(),timeoutMs:f=ge,maxResponseBytes:b=Ae,maxFetchesPerExec:i,beforeSend:oe,clientIp:ae,allowedHttpMethods:P}){const O=[...e,...t.flatMap(a=>a.matchers)],se=[...new Set(t.map(a=>a.name).filter(Boolean))].join(", "),j=t.flatMap(a=>Object.values(a.secrets??{})),w=[...r?[{header:"Authorization",value:r}]:[],...j].flatMap(pe),ce=j.map(a=>a.header),I=t.flatMap(a=>a.credentialQueryParams??[]);let N=0,B=!1;return async a=>{const l=be(a.method??"GET"),D=Date.now();let G,A,E,v={},$,U,H;try{if(i!==void 0){const o=Math.max(0,i);if(N+=1,N>o)throw new S(`Outbound fetch budget exceeded (max ${o} fetches per execute); split the work across calls`)}if(P&&!P.includes(l))throw new p(`${l} is not allowed: this project permits only ${P.join(", ")} API calls`,te(a.url));const s=Re(a.url,O,se);if(G=s.hostname,A=ye(s.pathname),Ee.test(s.pathname))throw new p(`${l} ${s.pathname} is refused: the path holds an encoded path separator (%2F or %5C), which could reach an operation other than the documented one`,s.hostname);const y=t.filter(o=>o.matchers.some(T=>T.test(s.hostname))),m=y.flatMap(o=>o.operations?Se(o.operations,s.pathname,l).map(T=>({api:o,callable:T.callable,visible:T.visible,variables:T.variables})):[{api:o,callable:!0,visible:!0,variables:Number.POSITIVE_INFINITY}]),k=Math.min(...m.map(o=>o.variables)),M=m.filter(o=>o.variables===k);if(y.length>0&&(m.length===0||M.some(o=>!o.visible)))throw new p(`${l} ${s.pathname} is not a documented operation of ${y.map(o=>o.name).join(", ")}; \`fetch\` calls only the operations the API description documents`,s.hostname);const V=M.find(o=>!o.callable);if(V)throw new p(`${l} ${s.pathname} is not callable: this operation of ${V.api.name} is excluded from agent calls`,s.hostname);const h=new Set(M.map(o=>o.api)).size===1?M[0].api:void 0;E=h?.slug;const z=He(a.securityScheme,h),F=z?[z]:h?Pe(h,s.pathname,l):void 0,C=h&&F?Ue(h,F):void 0,Q=C?h?.secrets?.[C]:void 0,L=Q??(r?{header:"Authorization",value:r}:void 0);v=je({callerHeaders:a.headers,injected:L,clientIp:ae});const W=xe(a.headers,s,y,L);if(W&&(L||B))throw new S(Oe(W,!!L));await oe?.(),B||=!!L,$=Q?C:void 0;const Y=new AbortController;H=setTimeout(()=>Y.abort(),f);const u=await c(s,{method:l,headers:v,body:a.body,redirect:"manual",signal:Y.signal,dispatcher:d});U=u.status;let R="";try{R=await Fe(u,b)}finally{if(ve.has(u.status)&&h&&n){const o=we(R,u.headers.get("content-type"),w);n({api:h.slug,name:h.name,status:u.status,...C?{scheme:C}:{},...F?.length?{schemes:[...F]}:{},...$?{sentScheme:$}:{},...o?{detail:o}:{}})}}let g=me(Ie(u.headers),w);if(Te(u.status)){g=he(g,ce);const{location:o}=g;o&&!De(o,s,O)&&delete g.location}return Z({host:s.hostname,method:l,outcome:"success",durationMs:Date.now()-D,path:A,api:E,status:u.status,bytes:Buffer.byteLength(R,"utf8")}),q({rawUrl:s.href,method:l,host:s.hostname,outcome:"success",durationMs:Date.now()-D,injectedSecretValues:w,credentialQueryParams:I,path:A,api:E,status:u.status,injectedScheme:$,requestHeaders:v,requestBody:a.body,responseHeaders:g,responseBody:R}),{status:u.status,statusText:_(u.statusText,w),ok:u.ok,headers:g,bodyText:_(R,w)}}catch(s){const{outcome:y,userSafe:m}=Le(s),k=G??(m instanceof p?m.host:void 0)??te(a.url)??"invalid-url";throw Z({host:k,method:l,outcome:y,durationMs:Date.now()-D,path:A,api:E,status:U}),q({rawUrl:a.url,method:l,host:k,outcome:y,durationMs:Date.now()-D,injectedSecretValues:w,credentialQueryParams:I,path:A,api:E,status:U,injectedScheme:$,requestHeaders:v,requestBody:a.body}),m||(K.error(`Gateway MCP fetch failed [${l} ${k}]: ${_(Me(s),w)} headers=${de(v)}`),new fe("Upstream fetch failed due to an internal error"))}finally{H!==void 0&&clearTimeout(H)}}}function Ce(e){let t=e;for(let r=0;r<5&&t instanceof Error;r++){if(t instanceof S||t instanceof J)return t;t=t.cause}}function Le(e){const t=Ce(e);return{outcome:t instanceof p||t instanceof J?"blocked":t?"user_error":"system_error",userSafe:t}}function Re(e,t,r){try{return X(e,t)}catch(n){throw n instanceof p&&r?new p(`${n.message}. \`fetch\` reaches only the documented hosts of the callable APIs (${r}); other documented APIs can be read through the tools but not called.`,n.host):n}}function te(e){try{return new URL(e).hostname||void 0}catch{return}}function Te(e){return e>=300&&e<400}function De(e,t,r){try{return!!X(new URL(e,t).href,r)}catch{return!1}}function Me(e){if(!(e instanceof Error))return String(e);const t=e.cause;return t instanceof Error?`${e.message}: ${t.message}`:e.message}async function Fe(e,t){const r=Number(e.headers.get("content-length"));if(Number.isFinite(r)&&r>t)throw re(t,r);if(!e.body)return"";const n=e.body.getReader(),c=new TextDecoder;let d=0,f="";try{for(;;){const{done:b,value:i}=await n.read();if(b)break;if(d+=i.byteLength,d>t)throw re(t,d);f+=c.decode(i,{stream:!0})}return f+c.decode()}finally{await n.cancel().catch(()=>{})}}function re(e,t){return new S(`Response is too large (over ${e} bytes, got ${t}); request less data`)}function Pe(e,t,r){const n=e.routes?.find(c=>c.method===r&&c.pattern.test(t));return n?n.schemes:_e(e)}function Ue(e,t){const r=t.filter(n=>e.secrets?.[n]);return r.length===0?t[0]:r.reduce((n,c)=>{const d=e.secrets?.[n]?.savedAt??0;return(e.secrets?.[c]?.savedAt??0)>d?c:n})}function He(e,t){if(e==null)return;if(typeof e!="string")throw new S("securityScheme must be a security scheme name (a string)");if(!t)throw new S("securityScheme cannot be applied: the URL host does not identify exactly one callable API");const r=t.schemePriority??[];if(r.length>0&&!r.includes(e))throw new S(`Unknown securityScheme "${e}" for ${t.name}; valid values: ${r.join(", ")}`);return e}function _e(e){const t=Object.keys(e.secrets??{});if(t.length===0)return e.defaultScheme?[e.defaultScheme]:[];const r=(e.schemePriority??[]).filter(n=>t.includes(n));return[...r,...t.filter(n=>!r.includes(n))]}function xe(e,t,r,n){const c=ne([...$e,...r.flatMap(i=>i.credentialHeaders??[]),...n?[n.header]:[]]),d=Object.keys(e??{}).find(i=>c.has(i.toLowerCase()));if(d)return{kind:"header",name:d};const f=ne(r.flatMap(i=>i.credentialQueryParams??[])),b=[...t.searchParams.keys()].find(i=>f.has(i.toLowerCase()));return b?{kind:"query parameter",name:b}:void 0}function ne(e){return new Set(e.map(t=>t.toLowerCase()))}function Oe({kind:e,name:t},r){return r?`Remove the "${t}" ${e}: this request already sends the stored credential.`:`Remove the "${t}" ${e}: this script already sent a stored credential, so it cannot also send its own.`}function je({callerHeaders:e,injected:t,clientIp:r}){const n={};for(const[c,d]of Object.entries(e??{})){const f=c.toLowerCase();f==="host"||f==="content-length"||f!==ee&&(n[c]=d)}return r&&(n[ee]=r),t&&(n[t.header]=t.value),n}function Ie(e){const t={};return e.forEach((r,n)=>{t[n.toLowerCase()]=r}),t}export{nt as buildFetchBridge,ke as getDefaultAgent,Fe as readCappedBody};
@@ -1 +1 @@
1
- import{isDefined as c}from"../../../../utils/guards/is-defined.js";import{filterApiDescriptionByAccess as n,readApiDescriptionFromFs as d}from"../docs-mcp/tools/openapi/utils.js";import{isDefinitionHidden as m}from"../utils/xmcp-utils.js";import{getAllowedHttpMethods as u}from"./allowed-http-methods.js";import{getCallableApis as f,getExcludedOperations as p}from"./callable-apis.js";import{apiCallOperationsOf as h}from"./api-call-operations.js";import{buildAllowedHostMatchers as l}from"./fetch/allowed-hosts.js";import{buildFetchBridge as g}from"./fetch/fetch-bridge.js";import{credentialHeadersOf as v,orderedSchemesOf as P,resolveUpstreamApiAuth as b}from"./upstream-auth-scheme.js";async function C(e,a){const i=await Promise.all(f(e).map(async r=>{const t=await y(e,r.relativePath),o=t?.visible;if(o&&m(o.info,"apiCalls"))return;const s=await b(e,r.relativePath,async()=>o);return{slug:r.relativePath,name:r.name,matchers:l(r.servers??[]),secrets:e.upstreamSecrets?.[r.relativePath],defaultScheme:s.defaultScheme,routes:s.routes,credentialHeaders:v(s.schemes),credentialQueryParams:s.credentialQueryParams,schemePriority:P(s.schemes),operations:t?.visible?h(t.full,t.visible,r.servers??[],p(e,r.relativePath)):[]}}));return g({allowedHosts:l([]),apis:i.filter(c),authorization:e.upstreamAuthorization,onAuthRequired:a,maxFetchesPerExec:e.gatewayFetchBudgetPerExec,clientIp:e.clientIp,allowedHttpMethods:u(e.config?.mcp)})}async function y(e,a){try{const i=await d(a,e.outdir||"");if(!i)return;const r=n(i,{user:e.user,rbac:e.config?.access?.rbac||{},requiresLogin:!!e.config?.access?.requiresLogin});return{full:i,visible:r}}catch{return}}export{C as buildExecuteHostFetch};
1
+ import{isDefined as l}from"../../../../utils/guards/is-defined.js";import{filterApiDescriptionByAccess as c,readApiDescriptionFromFs as u}from"../docs-mcp/tools/openapi/utils.js";import{isDefinitionHidden as d}from"../utils/xmcp-utils.js";import{getAllowedHttpMethods as m}from"./allowed-http-methods.js";import{getCallableApis as f,getExcludedOperations as h}from"./callable-apis.js";import{apiCallOperationsOf as p}from"./api-call-operations.js";import{buildAllowedHostMatchers as n}from"./fetch/allowed-hosts.js";import{buildFetchBridge as w}from"./fetch/fetch-bridge.js";import{GatewayFetchUserError as b}from"./fetch/fetch-errors.js";import{sendFetchCount as g}from"./rate-limit/fetch-limiter/fetch-count-client.js";import{credentialHeadersOf as v,orderedSchemesOf as y,resolveUpstreamApiAuth as P}from"./upstream-auth-scheme.js";async function z(e,t){const i=await Promise.all(f(e).map(async r=>{const a=await F(e,r.relativePath),o=a?.visible;if(o&&d(o.info,"apiCalls"))return;const s=await P(e,r.relativePath,async()=>o);return{slug:r.relativePath,name:r.name,matchers:n(r.servers??[]),secrets:e.upstreamSecrets?.[r.relativePath],defaultScheme:s.defaultScheme,routes:s.routes,credentialHeaders:v(s.schemes),credentialQueryParams:s.credentialQueryParams,schemePriority:y(s.schemes),operations:a?.visible?p(a.full,a.visible,r.servers??[],h(e,r.relativePath)):[]}}));return w({allowedHosts:n([]),apis:i.filter(l),authorization:e.upstreamAuthorization,onAuthRequired:t,maxFetchesPerExec:e.gatewayFetchBudgetPerExec,beforeSend:A(e),clientIp:e.clientIp,allowedHttpMethods:m(e.config?.mcp)})}function A(e){const{fetchCountRequest:t,internalBaseUrl:i}=e;if(!(!t||!i))return async()=>{const r=await g(i,t);if(r)throw new b(r)}}async function F(e,t){try{const i=await u(t,e.outdir||"");if(!i)return;const r=c(i,{user:e.user,rbac:e.config?.access?.rbac||{},requiresLogin:!!e.config?.access?.requiresLogin});return{full:i,visible:r}}catch{return}}export{z as buildExecuteHostFetch};
@@ -0,0 +1,4 @@
1
+ import type { FetchCountRequest } from '../../../types.js';
2
+ /** The refusal when a limit is reached; when the check itself fails, the fetch is allowed. */
3
+ export declare function sendFetchCount(serverBaseUrl: string, { caller, secret }: FetchCountRequest): Promise<string | undefined>;
4
+ //# sourceMappingURL=fetch-count-client.d.ts.map
@@ -0,0 +1 @@
1
+ import{withPathPrefix as i}from"@redocly/theme/core/utils";import{ServerRoutes as a}from"../../../../../../constants/api.js";import{logger as e}from"../../../../../tools/notifiers/logger.js";const c=5e3;async function d(r,{caller:n,secret:o}){try{const t=await fetch(`${r}${i(a.MCP_FETCH_COUNT)}`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({caller:n}),signal:AbortSignal.timeout(c)});if(t.status===429)return await t.text();t.ok||e.warn(`Fetch limit check answered ${t.status}; the fetch was allowed uncounted`);return}catch(t){e.warn(`Fetch limit check failed; the fetch was allowed uncounted: ${String(t)}`);return}}export{d as sendFetchCount};
@@ -0,0 +1,4 @@
1
+ /** The secret of this server process; only its own execute workers receive it. */
2
+ export declare function fetchCountSecret(): string;
3
+ export declare function isFetchCountSecret(value: string | null | undefined): boolean;
4
+ //# sourceMappingURL=fetch-count-secret.d.ts.map
@@ -0,0 +1 @@
1
+ import{randomBytes as o,timingSafeEqual as f}from"node:crypto";const n=o(32).toString("base64url");function i(){return n}function u(t){if(typeof t!="string")return!1;const e=Buffer.from(t),r=Buffer.from(n);return e.length===r.length&&f(e,r)}export{i as fetchCountSecret,u as isFetchCountSecret};
@@ -0,0 +1,11 @@
1
+ import type { FetchCountRequest } from '../../../types.js';
2
+ import type { FetchCounterRepository } from './fetch-counter-repository.js';
3
+ import type { GatewayFetchLimits } from './fetch-limits.js';
4
+ type FetchLimits = Pick<GatewayFetchLimits, 'ratePerMin' | 'quotaPerDay'>;
5
+ /**
6
+ * Counts one fetch atomically and returns the refusal when a window is full. A signed-in user
7
+ * is counted by subject, an anonymous caller by IP; a caller with neither is not counted.
8
+ */
9
+ export declare function applyFetchCount(store: FetchCounterRepository, caller: FetchCountRequest['caller'], limits: FetchLimits, now: number): Promise<string | undefined>;
10
+ export {};
11
+ //# sourceMappingURL=fetch-count.d.ts.map
@@ -0,0 +1 @@
1
+ import{logger as u}from"../../../../../tools/notifiers/logger.js";const d=[{seconds:60,limitOf:"ratePerMin",per:"minute"},{seconds:86400,limitOf:"quotaPerDay",per:"day"}];function a(c,r,i){const n=Math.floor(i/1e3);return d.map(e=>{const t=Math.floor(n/e.seconds);return{key:["mcp","fetch-rl",c,String(e.seconds),String(t)],per:e.per,limit:r[e.limitOf],retryAfterSeconds:Math.max(1,(t+1)*e.seconds-n),ttlInSeconds:e.seconds}})}async function l(c,r){const i=await c.getMany(r.map(e=>e.key)),n=r.filter((e,t)=>(i[t]?.value?.count??0)>=e.limit);return n.length>0?n.reduce((e,t)=>e.retryAfterSeconds>=t.retryAfterSeconds?e:t):r.reduce((e,t)=>e.retryAfterSeconds<=t.retryAfterSeconds?e:t)}async function y(c,r,i,n){const e=r.subject??(r.ip&&`ip:${r.ip}`);if(!e)return;const t=a(e,i,n);try{if(await c.incrementBelowLimits(t))return;const{limit:o,per:s,retryAfterSeconds:f}=await l(c,t);return`Outbound fetch limit reached (${o} fetches per ${s}); retry in ${f}s`}catch(o){u.warn(`Fetch limit store failed; the fetch was allowed uncounted: ${String(o)}`);return}}export{y as applyFetchCount};
@@ -0,0 +1,13 @@
1
+ import type { KvKey } from '@redocly/config';
2
+ import type { RepositoryInstanceOptions } from '../../../../../providers/database/types.js';
3
+ import { KvRepository } from '../../../../../persistence/kv/repositories/kv-repository.js';
4
+ export declare class FetchCounterRepository extends KvRepository {
5
+ static create(options: RepositoryInstanceOptions): Promise<FetchCounterRepository>;
6
+ /** Adds one to every slot in one statement unless a slot is at its limit; tells whether it did. */
7
+ incrementBelowLimits(slots: readonly {
8
+ key: KvKey;
9
+ limit: number;
10
+ ttlInSeconds: number;
11
+ }[]): Promise<boolean>;
12
+ }
13
+ //# sourceMappingURL=fetch-counter-repository.d.ts.map
@@ -0,0 +1,13 @@
1
+ import{sql as t}from"drizzle-orm";import{createDatabaseRepositoryInstance as s}from"../../../../../providers/database/instance-cache-resolver.js";import{KvRepository as u}from"../../../../../persistence/kv/repositories/kv-repository.js";import{createKvDbRecord as _}from"../../../../../persistence/kv/mappers/create-kv-db-record.js";class o extends u{static create(a){return s(a,"fetch counter",o)}async incrementBelowLimits(a){const d=t.join(a.map(({key:c,limit:n,ttlInSeconds:r})=>{const e=_({key:c,value:{count:1},ttlInSeconds:r});return t`(${e.encodedKey}, ${e.value}, ${e.expiresAt}, ${e.createdAt}, ${e.updatedAt}, ${n})`}),t`, `);return(await this.databaseClient.dbClient.all(t`
2
+ WITH slot (encoded_key, value, expires_at, created_at, updated_at, max_count) AS (VALUES ${d})
3
+ INSERT INTO kv (encoded_key, value, expires_at, created_at, updated_at)
4
+ SELECT encoded_key, value, expires_at, created_at, updated_at FROM slot
5
+ WHERE NOT EXISTS (
6
+ SELECT 1 FROM slot AS s JOIN kv ON kv.encoded_key = s.encoded_key
7
+ WHERE json_extract(kv.value, '$.count') >= s.max_count
8
+ )
9
+ ON CONFLICT (encoded_key) DO UPDATE SET
10
+ value = json_object('count', json_extract(kv.value, '$.count') + 1),
11
+ updated_at = excluded.updated_at
12
+ RETURNING encoded_key
13
+ `)).length>0}}export{o as FetchCounterRepository};
@@ -4,4 +4,4 @@ export type GatewayFetchLimits = {
4
4
  budgetPerExec: number;
5
5
  };
6
6
  export declare function resolveGatewayFetchLimits(): GatewayFetchLimits;
7
- //# sourceMappingURL=limits.d.ts.map
7
+ //# sourceMappingURL=fetch-limits.d.ts.map
@@ -0,0 +1 @@
1
+ import{FEATURE as e,GATEWAY_FETCH_LIMIT_DEFAULTS as t}from"../../../../../../constants/common.js";import{readEntitlementNumber as r}from"../../../../../utils/entitlements/read-entitlement-number.js";function i(){return{ratePerMin:r(e.gatewayFetchRatePerMin,t.ratePerMin),quotaPerDay:r(e.gatewayFetchQuotaPerDay,t.quotaPerDay),budgetPerExec:r(e.gatewayFetchBudgetPerExec,t.budgetPerExec)}}export{i as resolveGatewayFetchLimits};
@@ -1,3 +1,3 @@
1
- import type { Store } from '../../../../store.js';
1
+ import type { Store } from '../../../../../store.js';
2
2
  export declare function applyEdgeRateLimit(request: Request, store: Store): Promise<Response | undefined>;
3
3
  //# sourceMappingURL=apply-edge-rate-limit.d.ts.map
@@ -0,0 +1 @@
1
+ import{envConfig as o}from"../../../../../config/env-config.js";import{isRealmOrReef as s}from"../../../../../utils/is-realm-or-reef.js";import{KvRepository as m}from"../../../../../persistence/kv/repositories/kv-repository.js";import{logger as p}from"../../../../../tools/notifiers/logger.js";import{ipRateLimitKey as f}from"../utils/ip-key.js";import{checkEdgeRateLimit as c}from"./edge-limiter.js";import{resolveMcpRequestLimits as d}from"./request-limits.js";import{reportMcpRateLimited as l}from"./rate-limit-telemetry.js";async function w(n,a){if(!s())return;const i=f(n);if(!i)return;let e,r;try{const t=await m.create({baseDbDir:a.serverOutDir,sqldRemoteDatabaseUrl:o.SQLD_REMOTE_DATABASE_URL,sqldRemoteDatabaseAuthToken:o.SQLD_REMOTE_DATABASE_AUTH_TOKEN});r=d(),e=await c({kv:t,ip:i,limits:r,now:Date.now()})}catch(t){p.warn(`gateway rate-limit init error, failing open: ${String(t)}`);return}if(!e.allowed)return l({scope:e.scope,retryAfterSeconds:e.retryAfterSeconds,limit:e.scope==="rate"?r.ratePerMin:r.quotaPerDay}),new Response(JSON.stringify({jsonrpc:"2.0",error:{code:-32029,message:`Rate limit exceeded (${e.scope}); retry after ${e.retryAfterSeconds}s`},id:null}),{status:429,headers:{"Content-Type":"application/json","Retry-After":String(e.retryAfterSeconds)}})}export{w as applyEdgeRateLimit};
@@ -1,5 +1,5 @@
1
- import type { KvRepository } from '../../../../persistence/kv/repositories/kv-repository.js';
2
- import type { GatewayFetchLimits } from './limits.js';
1
+ import type { KvRepository } from '../../../../../persistence/kv/repositories/kv-repository.js';
2
+ import type { McpRequestLimits } from './request-limits.js';
3
3
  export type EdgeDecision = {
4
4
  allowed: true;
5
5
  } | {
@@ -10,7 +10,7 @@ export type EdgeDecision = {
10
10
  export declare function checkEdgeRateLimit(params: {
11
11
  kv: KvRepository;
12
12
  ip: string;
13
- limits: GatewayFetchLimits;
13
+ limits: McpRequestLimits;
14
14
  now: number;
15
15
  }): Promise<EdgeDecision>;
16
16
  //# sourceMappingURL=edge-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ import{checkAndIncrement as c}from"../utils/window-counter.js";const s=60,d=86400;async function w(i){const{kv:e,ip:t,limits:o,now:r}=i;await e.sync();const n=await c({kv:e,ip:t,windowSeconds:s,limit:o.ratePerMin,now:r});if(!n.allowed)return{allowed:!1,retryAfterSeconds:n.retryAfterSeconds,scope:"rate"};const a=await c({kv:e,ip:t,windowSeconds:d,limit:o.quotaPerDay,now:r});return a.allowed?{allowed:!0}:{allowed:!1,retryAfterSeconds:a.retryAfterSeconds,scope:"quota"}}export{w as checkEdgeRateLimit};
@@ -0,0 +1 @@
1
+ import{telemetry as o}from"../../../../../telemetry/index.js";import{logger as i}from"../../../../../tools/notifiers/logger.js";import{McpServerType as m}from"../../../constants.js";import{serverAttributes as p}from"../../../utils/telemetry-attributes.js";function d({scope:e,retryAfterSeconds:r,limit:t}){try{o.sendMcpRateLimitedMessage([{...p(m.Docs),scope:e,retry_after_seconds:r,limit:t}])}catch{}i.warn(`Gateway MCP rate limited [scope=${e}] retryAfterSeconds=${r} limit=${t}`)}export{d as reportMcpRateLimited};
@@ -0,0 +1,6 @@
1
+ export type McpRequestLimits = {
2
+ ratePerMin: number;
3
+ quotaPerDay: number;
4
+ };
5
+ export declare function resolveMcpRequestLimits(): McpRequestLimits;
6
+ //# sourceMappingURL=request-limits.d.ts.map
@@ -0,0 +1 @@
1
+ import{FEATURE as e,MCP_REQUEST_LIMIT_DEFAULTS as r}from"../../../../../../constants/common.js";import{readEntitlementNumber as t}from"../../../../../utils/entitlements/read-entitlement-number.js";function i(){return{ratePerMin:t(e.mcpRequestRatePerMin,r.ratePerMin),quotaPerDay:t(e.mcpRequestQuotaPerDay,r.quotaPerDay)}}export{i as resolveMcpRequestLimits};
@@ -0,0 +1 @@
1
+ import{isIP as l}from"node:net";import{getFeedbackClientIp as a}from"../../../../../web-server/utils/feedback-ip.js";import{extractMappedIpv4 as c}from"../../../../../web-server/utils/ssrf-blocklist.js";function I(t){const n=a(t);if(n)return f(n)}function f(t){if(l(t)===6){const n=c(t);return n||`${u(t)}::/64`}return t}function u(t){const n=t.split("%")[0],[e,i=""]=n.split("::"),r=e?e.split(":"):[],o=i?i.split(":"):[],s=8-r.length-o.length;return[...r,...Array(Math.max(0,s)).fill("0"),...o].slice(0,4).map(p=>parseInt(p||"0",16).toString(16)).join(":")}export{I as ipRateLimitKey};
@@ -1,4 +1,4 @@
1
- import type { KvRepository } from '../../../../persistence/kv/repositories/kv-repository.js';
1
+ import type { KvRepository } from '../../../../../persistence/kv/repositories/kv-repository.js';
2
2
  export type WindowCheck = {
3
3
  allowed: false;
4
4
  retryAfterSeconds: number;
@@ -0,0 +1 @@
1
+ import{logger as d}from"../../../../../tools/notifiers/logger.js";function g(e,t,r){return["mcp","request-rl",e,String(t),String(r)]}async function S(e){const{kv:t,ip:r,windowSeconds:n,limit:l,now:w}=e,o=Math.floor(w/1e3),c=Math.floor(o/n),i=g(r,n,c);try{const s=(await t.get(i))?.count??0;if(s>=l){const u=(c+1)*n-o;return{allowed:!1,retryAfterSeconds:Math.max(1,u)}}return await t.set(i,{count:s+1},{ttlInSeconds:n}),{allowed:!0}}catch(a){return d.warn(`gateway rate-limit store error, failing open: ${String(a)}`),{allowed:!0}}}export{S as checkAndIncrement};
@@ -1 +1 @@
1
- import{logger as h}from"../../../tools/notifiers/logger.js";import{getFeedbackClientIp as F}from"../../../web-server/utils/feedback-ip.js";import{getForwardedOrigin as A,getInternalOrigin as N}from"../../../web-server/utils/get-request-origin.js";import{GATEWAY_FETCH_LIMIT_DEFAULTS as v,DEFAULT_ANONYMOUS_VISITOR_TEAM as B,RbacFeatures as L}from"../../../../constants/common.js";import{isObject as H}from"../../../../utils/guards/is-object.js";import{getAccessibleSkills as V}from"../../../utils/skills/get-accessible-skills.js";import{getSkillUrl as $}from"../../../utils/skills/get-skill-url.js";import{createMcpHandler as j}from"@modelcontextprotocol/server";import{createDocsMcpServer as G}from"../servers/docs-server.js";import{filterApiDescriptionsByRbac as J,filterGraphqlDescriptionsByRbac as K}from"../utils.js";import{McpClientInputError as Y,createMethodNotAllowedError as z,withErrorHandling as W}from"./errors.js";import{constructForbiddenResponse as X,constructInvalidTokenResponse as Q,constructUnauthorizedResponse as Z,handleMcpAuth as q}from"../auth/auth-handlers.js";import{isAssistantAnonymousToken as x}from"../auth/assistant-anonymous-token.js";import{shouldHandleMcpAuth as ee}from"../../../utils/rbac/should-handle-mcp-auth.js";import{extractTokenFromAuthHeader as re}from"../utils/jwt.js";import{resolveMcpClient as oe}from"../utils/mcp-client.js";import{randomUUID as se}from"../../../utils/crypto/random-uuid.js";import{getPublicMcpEndpointStatus as ne}from"../utils/public-endpoint.js";import{narrowMcpConfigForCaller as ie}from"../gateway-mcp/caller-api-calls.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as te,SSE_KEEPALIVE_INTERVAL_MS as ce}from"../constants.js";import{withPathPrefix as ae}from"@redocly/theme/core/utils";import{PUBLIC_MCP_ROUTE as pe}from"@redocly/theme/core/constants/mcp";import{canAccessFeature as de}from"../../../utils/rbac.js";import{ANONYMOUS_AUTH_DETAILS as le}from"../../../constants/rbac.js";const M="X-Redocly-AI-Metadata";function me(r){if(r)try{const e=JSON.parse(r);if(H(e))return e;h.warn(`Ignoring ${M} header: not a JSON object.`)}catch{h.warn(`Ignoring ${M} header: invalid JSON.`)}}function b(){return{...le,email:"",idpAccessToken:void 0,idpId:void 0}}const fe=async(r,e,i)=>{const t=!!e?.config?.access?.requiresLogin,c=e?.config?.access?.rbac||{},E=i?.props?.config?.mcpMode??"tools",y=i?.props?.config?.gatewayFetchBudgetPerExec??v.budgetPerExec,S=i?.props?.config?.searchIgnoredRoutes??[],s=i,U=!!s?.props?.config?.isPublicEndpoint;let f;const T=r.headers.get("Authorization");if(U)e.user=b();else if(ee(t,c)){const{isAuthenticated:n,isTokenValid:p,isFirstPartyAnonymous:u,currentUser:d,accessToken:g}=await q(r,e);if(u)e.user=b();else if(n)d&&(e.user=d),f=g;else{const a=A(r),l=ne(e.config)==="served"?`${a}${ae(pe)}`:void 0;return p===!1?Q(a,l):Z(a,l)}}else if(T){const n=re(T)??void 0;f=(n?await x(n):!1)?void 0:n}const w={isAuthenticated:!!e.user?.isAuthenticated,teams:e.user?.teams||[B],claims:e.user?.claims??{}};return de(L.MCP,w,c,t)?await W(async()=>{const n=i?.props?.config?.elicitationEnabled??!1;if(r.method==="GET")return new Response(JSON.stringify({error:"Method Not Allowed",message:`In order to use this MCP server, you need to register it in your MCP client (VS Code, Cursor, Claude Code, etc.) using this URL: ${r.url}`}),{status:405,headers:{"Content-Type":"application/json"}});if(r.method!=="POST")return z();const p=s?.props?.config?.apiDescriptionsMap||{},u=s?.props?.config?.graphqlDescriptions||[],d=s?.props?.config?.mcpDocsServerName||te,{config:{mcp:g={}}}=e,a=J(p,e.user,c,t),l=K(u,e.user,c,t),C=e.config.products?Object.values(e.config.products).map(o=>o?.name):[],D=g.docs?.name||d,m=me(r.headers.get(M)),P=s?.props?.tools||[],R=s?.props?.skillsBaseUrl||A(r),_=V(s?.props?.skills||[],e.user,c,t).map(o=>({slug:o.slug,name:o.name,description:o.description,content:o.content,uri:$(R,o.slug)})),I=await ue(r),O={name:D,tools:P,skills:_,context:{...e,config:{...e.config,mcp:ie(e.config.mcp,m)},accessToken:f,outdir:e.outdir||"",baseUrl:e.baseUrl||A(r),internalBaseUrl:N(r),apiDescriptionsMap:a,graphqlDescriptions:l,products:C,metadata:m,mcpMode:E,elicitationEnabled:n,gatewayFetchBudgetPerExec:y,searchIgnoredRoutes:S,clientIp:F(r),...oe(r),requestId:se(),messageId:typeof m?.messageId=="string"?m.messageId:void 0}},k=await G({...O,getKv:e.getKv});return await j(async()=>k,{keepAliveMs:ce,onerror:o=>h.warn(`MCP handler: ${o.message}`)}).fetch(r,I!==void 0?{parsedBody:I}:void 0)},E):X()};async function ue(r){try{return await r.json()}catch(e){throw new Y(e instanceof Error?e.message:String(e),{cause:e})}}var Ve=fe;export{Ve as default};
1
+ import{logger as A}from"../../../tools/notifiers/logger.js";import{getFeedbackClientIp as N}from"../../../web-server/utils/feedback-ip.js";import{getForwardedOrigin as M,getInternalOrigin as v}from"../../../web-server/utils/get-request-origin.js";import{GATEWAY_FETCH_LIMIT_DEFAULTS as L,DEFAULT_ANONYMOUS_VISITOR_TEAM as B,RbacFeatures as H}from"../../../../constants/common.js";import{isObject as j}from"../../../../utils/guards/is-object.js";import{getAccessibleSkills as V}from"../../../utils/skills/get-accessible-skills.js";import{getSkillUrl as $}from"../../../utils/skills/get-skill-url.js";import{createMcpHandler as G}from"@modelcontextprotocol/server";import{createDocsMcpServer as J}from"../servers/docs-server.js";import{filterApiDescriptionsByRbac as K,filterGraphqlDescriptionsByRbac as Y}from"../utils.js";import{userSubject as z}from"../gateway-mcp/subject.js";import{ipRateLimitKey as W}from"../gateway-mcp/rate-limit/utils/ip-key.js";import{McpClientInputError as X,createMethodNotAllowedError as Q,withErrorHandling as Z}from"./errors.js";import{constructForbiddenResponse as q,constructInvalidTokenResponse as x,constructUnauthorizedResponse as ee,handleMcpAuth as re}from"../auth/auth-handlers.js";import{isAssistantAnonymousToken as oe}from"../auth/assistant-anonymous-token.js";import{shouldHandleMcpAuth as se}from"../../../utils/rbac/should-handle-mcp-auth.js";import{extractTokenFromAuthHeader as ne}from"../utils/jwt.js";import{resolveMcpClient as ie}from"../utils/mcp-client.js";import{randomUUID as te}from"../../../utils/crypto/random-uuid.js";import{getPublicMcpEndpointStatus as ce}from"../utils/public-endpoint.js";import{narrowMcpConfigForCaller as ae}from"../gateway-mcp/caller-api-calls.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as pe,SSE_KEEPALIVE_INTERVAL_MS as de}from"../constants.js";import{withPathPrefix as me}from"@redocly/theme/core/utils";import{PUBLIC_MCP_ROUTE as fe}from"@redocly/theme/core/constants/mcp";import{canAccessFeature as le}from"../../../utils/rbac.js";import{ANONYMOUS_AUTH_DETAILS as ue}from"../../../constants/rbac.js";const E="X-Redocly-AI-Metadata";function ge(r){if(r)try{const e=JSON.parse(r);if(j(e))return e;A.warn(`Ignoring ${E} header: not a JSON object.`)}catch{A.warn(`Ignoring ${E} header: invalid JSON.`)}}function I(){return{...ue,email:"",idpAccessToken:void 0,idpId:void 0}}const he=async(r,e,i)=>{const t=!!e?.config?.access?.requiresLogin,c=e?.config?.access?.rbac||{},l=i?.props?.config?.mcpMode??"tools",y=i?.props?.config?.gatewayFetchBudgetPerExec??L.budgetPerExec,C=i?.props?.config?.searchIgnoredRoutes??[],s=i,U=!!s?.props?.config?.isPublicEndpoint;let u;const T=r.headers.get("Authorization");if(U)e.user=I();else if(se(t,c)){const{isAuthenticated:n,isTokenValid:p,isFirstPartyAnonymous:g,currentUser:d,accessToken:h}=await re(r,e);if(g)e.user=I();else if(n)d&&(e.user=d),u=h;else{const a=M(r),m=ce(e.config)==="served"?`${a}${me(fe)}`:void 0;return p===!1?x(a,m):ee(a,m)}}else if(T){const n=ne(T)??void 0;u=(n?await oe(n):!1)?void 0:n}const w={isAuthenticated:!!e.user?.isAuthenticated,teams:e.user?.teams||[B],claims:e.user?.claims??{}};return le(H.MCP,w,c,t)?await Z(async()=>{const n=i?.props?.config?.elicitationEnabled??!1;if(r.method==="GET")return new Response(JSON.stringify({error:"Method Not Allowed",message:`In order to use this MCP server, you need to register it in your MCP client (VS Code, Cursor, Claude Code, etc.) using this URL: ${r.url}`}),{status:405,headers:{"Content-Type":"application/json"}});if(r.method!=="POST")return Q();const p=s?.props?.config?.apiDescriptionsMap||{},g=s?.props?.config?.graphqlDescriptions||[],d=s?.props?.config?.mcpDocsServerName||pe,{config:{mcp:h={}}}=e,a=K(p,e.user,c,t),m=Y(g,e.user,c,t),R=e.config.products?Object.values(e.config.products).map(o=>o?.name):[],D=h.docs?.name||d,f=ge(r.headers.get(E)),P=s?.props?.tools||[],_=s?.props?.skillsBaseUrl||M(r),O=V(s?.props?.skills||[],e.user,c,t).map(o=>({slug:o.slug,name:o.name,description:o.description,content:o.content,uri:$(_,o.slug)})),S=await Ae(r),b=s?.props?.fetchCountSecret,k={name:D,tools:P,skills:O,context:{...e,config:{...e.config,mcp:ae(e.config.mcp,f)},accessToken:u,outdir:e.outdir||"",baseUrl:e.baseUrl||M(r),internalBaseUrl:v(r),apiDescriptionsMap:a,graphqlDescriptions:m,products:R,metadata:f,mcpMode:l,elicitationEnabled:n,gatewayFetchBudgetPerExec:y,fetchCountRequest:l==="code"&&b?{caller:{subject:z(e.user),ip:W(r)},secret:b}:void 0,searchIgnoredRoutes:C,clientIp:N(r),...ie(r),requestId:te(),messageId:typeof f?.messageId=="string"?f.messageId:void 0}},F=await J({...k,getKv:e.getKv});return await G(async()=>F,{keepAliveMs:de,onerror:o=>A.warn(`MCP handler: ${o.message}`)}).fetch(r,S!==void 0?{parsedBody:S}:void 0)},l):q()};async function Ae(r){try{return await r.json()}catch(e){throw new X(e instanceof Error?e.message:String(e),{cause:e})}}var Ke=he;export{Ke as default};
@@ -1 +1 @@
1
- import{withPathPrefix as g}from"@redocly/theme/core/utils";import{importApiRoutesHandler as D}from"../../../api-routes/import-api-routes-handlers.js";import{enhanceContext as h}from"../../../api-routes/helpers/enhance-context.js";import{telemetry as A}from"../../../telemetry/index.js";import{KvService as E}from"../../../persistence/kv/services/kv-service.js";import{envConfig as l}from"../../../config/env-config.js";import{acceptsHtml as R}from"../../../utils/ai-agent-detection.js";import{handleErrorPageRender as T}from"../../../web-server/utils.js";import{MCP_DOCS_SERVER_HANDLER_ID as _,MCP_NOT_ALLOWED_TEMPLATE_ID as w}from"../../../constants/mcp.js";import{resolvePublicBaseUrl as S}from"../../../web-server/utils/resolve-public-base-url.js";import{getForwardedOrigin as q}from"../../../web-server/utils/get-request-origin.js";import{applyEdgeRateLimit as O}from"../gateway-mcp/rate-limit/apply-edge-rate-limit.js";async function N(a,t,e){const n=t.req.raw;if(a.requestHandlerId===_&&n.method==="GET"&&R(n.headers.get("accept"))&&e.globalData.mcpData?.docs?.enabled)return T(t,e,{slug:a.slug},405,w);const r=t.get("auth"),s=await O(n,e);if(s)return s;const o=await e.resolveRouteStaticData(a)||{},m=g(a.slug),{requestHandlers:c}=await D(e.serverOutDir),p=c[a.requestHandlerId],d=(await p()).default,u=async()=>await E.getInstance({baseDbDir:e.serverOutDir,sqldRemoteDatabaseUrl:l.SQLD_REMOTE_DATABASE_URL,sqldRemoteDatabaseAuthToken:l.SQLD_REMOTE_DATABASE_AUTH_TOKEN}),f=h({honoCtx:t,ctx:{user:{teams:r.teams,email:r.claims.email,claims:r.claims,idpAccessToken:r.idpAccessToken,idpId:r.claims.idpId,isAuthenticated:r.isAuthenticated},config:e.config,outdir:e.outdir,baseUrl:q(t.req.raw)},telemetry:A,getKv:u}),i=await d(t.req.raw,f,{...o,props:{...o.props,routeSlug:m,outdir:e.outdir,skills:e.config.skills?.hide===!0?[]:e.getSkills(),skillsBaseUrl:S(e,t)}});return i instanceof Response?i:typeof i=="string"?new Response(i,{headers:{"Content-Type":"text/plain"}}):new Response(JSON.stringify(i),{headers:{"Content-Type":"application/json"}})}export{N as handleMcpRequest};
1
+ import{withPathPrefix as g}from"@redocly/theme/core/utils";import{importApiRoutesHandler as h}from"../../../api-routes/import-api-routes-handlers.js";import{enhanceContext as D}from"../../../api-routes/helpers/enhance-context.js";import{telemetry as A}from"../../../telemetry/index.js";import{KvService as E}from"../../../persistence/kv/services/kv-service.js";import{envConfig as l}from"../../../config/env-config.js";import{acceptsHtml as R}from"../../../utils/ai-agent-detection.js";import{handleErrorPageRender as T}from"../../../web-server/utils.js";import{MCP_DOCS_SERVER_HANDLER_ID as _,MCP_NOT_ALLOWED_TEMPLATE_ID as w}from"../../../constants/mcp.js";import{resolvePublicBaseUrl as S}from"../../../web-server/utils/resolve-public-base-url.js";import{getForwardedOrigin as q}from"../../../web-server/utils/get-request-origin.js";import{applyEdgeRateLimit as C}from"../gateway-mcp/rate-limit/request-limiter/apply-edge-rate-limit.js";import{fetchCountSecret as O}from"../gateway-mcp/rate-limit/fetch-limiter/fetch-count-secret.js";async function G(a,t,e){const n=t.req.raw;if(a.requestHandlerId===_&&n.method==="GET"&&R(n.headers.get("accept"))&&e.globalData.mcpData?.docs?.enabled)return T(t,e,{slug:a.slug},405,w);const r=t.get("auth"),o=await C(n,e);if(o)return o;const s=await e.resolveRouteStaticData(a)||{},m=g(a.slug),{requestHandlers:c}=await h(e.serverOutDir),p=c[a.requestHandlerId],d=(await p()).default,u=async()=>await E.getInstance({baseDbDir:e.serverOutDir,sqldRemoteDatabaseUrl:l.SQLD_REMOTE_DATABASE_URL,sqldRemoteDatabaseAuthToken:l.SQLD_REMOTE_DATABASE_AUTH_TOKEN}),f=D({honoCtx:t,ctx:{user:{teams:r.teams,email:r.claims.email,claims:r.claims,idpAccessToken:r.idpAccessToken,idpId:r.claims.idpId,isAuthenticated:r.isAuthenticated},config:e.config,outdir:e.outdir,baseUrl:q(t.req.raw)},telemetry:A,getKv:u}),i=await d(t.req.raw,f,{...s,props:{...s.props,routeSlug:m,outdir:e.outdir,fetchCountSecret:O(),skills:e.config.skills?.hide===!0?[]:e.getSkills(),skillsBaseUrl:S(e,t)}});return i instanceof Response?i:typeof i=="string"?new Response(i,{headers:{"Content-Type":"text/plain"}}):new Response(JSON.stringify(i),{headers:{"Content-Type":"application/json"}})}export{G as handleMcpRequest};
@@ -1 +1 @@
1
- import{fileURLToPath as U}from"node:url";import{dirname as $,join as D}from"node:path";import{MCP_ROUTE as k,PUBLIC_MCP_ROUTE as M}from"@redocly/theme/core/constants/mcp";import{logger as p}from"../../tools/notifiers/logger.js";import{reporter as F}from"../../tools/notifiers/reporter.js";import{isDefined as B}from"../../../utils/guards/is-defined.js";import{MCP_CREDENTIALS_HANDLER_ID as A,MCP_CREDENTIALS_ROUTE as V,MCP_DOCS_SERVER_HANDLER_ID as T,MCP_NOT_ALLOWED_TEMPLATE_ID as x}from"../../constants/mcp.js";import{bundledOpenApiDocumentsToMcpOpenApi as J,bundledGraphqlDocumentsToMcpOpenApi as W,buildGraphqlDescriptions as X,collectIgnoredRoutes as z,filterIgnoredApiDescriptions as P,getCleanedUpApiDescriptions as K,isMcpInRedirects as Q}from"./utils.js";import{getMcpMode as R,isMcpElicitationEnabled as Y,isMcpGatewayEnabled as Z}from"./utils/mode.js";import{getPublicMcpEndpointStatus as _}from"./utils/public-endpoint.js";import{findGatedToolNames as ee}from"./utils/find-gated-tool-names.js";import{getDefaultToolSchemas as oe}from"./docs-mcp/tool-schemas.js";import{EXECUTE_TOOL_SCHEMA as te}from"./codemode/tools/execute-schema.js";import{isGatewayCapabilityEnabled as ie,isGatewayEligible as re}from"./gateway-mcp/eligibility.js";import{resolveGatewayFetchLimits as se}from"./gateway-mcp/rate-limit/limits.js";import{resetCustomMcpTools as ne,scanCustomMcpTools as ae}from"./custom-tools/scan-custom-tools.js";import{resolveSiteBranding as ce}from"./handlers/credentials-page/site-branding.js";import{telemetryTraceStep as pe}from"../../../cli/telemetry/helpers/trace-step.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as S}from"./constants.js";const b=$(U(import.meta.url));async function de(){let i;const m={registeredToolNames:[],warnedNoOutputSchemaTools:new Set};let u=0,f=!1;return{id:"mcp",async processContent(e,a){await pe("com.redocly.build.plugin.mcp",async s=>{const t=await a.getConfig(),{mcp:r,redirects:g}=t;if(s?.setAttribute("config",JSON.stringify(r||{})),s?.setAttribute("mcpMode",R()),s?.setAttribute("mcpGateway",String(Z())),r?.hide||r?.docs?.hide){ne(e,m);return}Q(g)&&await F.panicOnBuildContentError('The reserved route "/mcp" cannot be used in redirect configuration. Please remove any redirects involving "/mcp" from your redocly.yaml config.'),e.createTemplate(x,"@redocly/theme/layouts/McpNotAllowed");const l=!!t?.access?.requiresLogin,C=t?.access?.rbac||{},d=R(),c=Y(),{budgetPerExec:h}=se();if(!i){p.info("Configuring MCP servers...");const w=J(await e.loadApiDocuments(a)),O=W(await e.loadApiDocuments(a)),v=r?.docs?.ignore||[],L=K(P(w,v)),q=ie(r),E=new Map;for(const o of L){const H=o.definition.info?.title||"",j=o.definition.info?.version||"";E.set(o.relativePath,{name:H,description:typeof o.definition.info?.description=="string"?o.definition.info.description:"",version:j,servers:o.definition.servers||[],relativePath:o.relativePath,...q&&re(o.definition.info)?{fetchEligible:!0}:{}})}const y=X(P(O,v)),G=Object.values(t.products||{}).map(o=>o?.name).filter(B),I=t.l10n?.locales?.map(o=>o.code)??a.fs.localeFolders,N=oe({products:G,locales:I,requiresLogin:l,rbac:C,hasGraphqlApis:y.length>0,elicitationEnabled:c,hasCallableApis:E.values().some(o=>o.fetchEligible),feedbackHidden:t.feedback?.hide===!0});e.addMcpTools(D(b,"./docs-mcp/tools/index.js"),N),d==="code"&&e.addMcpTools(D(b,"./codemode/tools/execute.js"),[te]),i={apiDescriptionsMap:Object.fromEntries(E),graphqlDescriptions:y}}const n=await ae(e,a,m);s?.setAttribute("customToolsCount",String(n)),n!==u&&(u=n,n>0&&p.info(`Registered ${n} custom MCP tool${n===1?"":"s"} from @mcp folders`)),await le({actions:e,handlerPath:D(b,"./handlers/docs-mcp-handler.js"),apiDescriptionsMap:i.apiDescriptionsMap,graphqlDescriptions:i.graphqlDescriptions,mcpConfig:r,mcpMode:d,elicitationEnabled:c,gatewayFetchBudgetPerExec:h,publicEndpointStatus:_(t)}),f||(p.info("Registered Docs MCP Server endpoint at /mcp"),f=!0)})},async afterRoutesCreated(e,a){const s=await a.getConfig(),{mcp:t}=s,r=!t?.hide&&!t?.docs?.hide;e.markGatedMcpTools(await ee(e.getMcpTools())),e.setGlobalData({mcpData:{docs:{enabled:r,name:t?.docs?.name||S,publicEndpoint:_(s)==="served"}}})}}}var Oe=de;async function le({actions:i,handlerPath:m,apiDescriptionsMap:u,graphqlDescriptions:f,mcpConfig:e,mcpMode:a,elicitationEnabled:s,gatewayFetchBudgetPerExec:t,publicEndpointStatus:r}){i.createRequestHandler(T,m);const g=l=>async(C,d)=>{const c=z(d.getAllRoutes(),e?.docs?.ignore||[]);return c.length>0&&p.verbose(`mcp.docs.ignore: hiding ${c.length} page routes from MCP search`),{props:{config:{apiDescriptionsMap:u,graphqlDescriptions:f,mcpDocsServerName:e?.docs?.name||S,mcpMode:a,elicitationEnabled:s,gatewayFetchBudgetPerExec:t,searchIgnoredRoutes:c,...l?{isPublicEndpoint:l}:{}},serverOutDir:i.serverOutDir,tools:i.getMcpTools().map(({importPath:h,...n})=>n)}}};if(i.addApiRoute({slug:k,requestHandlerId:T,httpMethod:"all",getStaticData:g(!1)}),r==="no-protected-content"?p.info(`mcp.docs.publicEndpoint is enabled, but the project has no protected content - /mcp already serves anonymous users, skipping ${M}.`):r==="anonymous-denied"?p.info(`mcp.docs.publicEndpoint is enabled, but anonymous users are denied the MCP feature (rbac.features.mcp or requiresLogin) - skipping ${M}.`):r==="served"&&(i.addApiRoute({slug:M,requestHandlerId:T,httpMethod:"all",getStaticData:g(!0)}),p.verbose(`Registered public Docs MCP Server endpoint at ${M}`)),s){const l=D(b,"./handlers/mcp-credentials-handler.js");i.createRequestHandler(A,l),i.addApiRoute({slug:V,requestHandlerId:A,httpMethod:"all",getStaticData:async(C,d)=>{const{seo:c,colorMode:h}=d.getConfig(),{logo:n}=d.getGlobalData();return{props:{branding:ce({seo:c,logo:n,colorMode:h})}}}}),p.verbose("Registered MCP credentials endpoint at /mcp/credentials")}}export{Oe as default,de as mcpServerPlugin};
1
+ import{fileURLToPath as U}from"node:url";import{dirname as $,join as D}from"node:path";import{MCP_ROUTE as k,PUBLIC_MCP_ROUTE as M}from"@redocly/theme/core/constants/mcp";import{logger as p}from"../../tools/notifiers/logger.js";import{reporter as F}from"../../tools/notifiers/reporter.js";import{isDefined as B}from"../../../utils/guards/is-defined.js";import{MCP_CREDENTIALS_HANDLER_ID as A,MCP_CREDENTIALS_ROUTE as V,MCP_DOCS_SERVER_HANDLER_ID as T,MCP_NOT_ALLOWED_TEMPLATE_ID as x}from"../../constants/mcp.js";import{bundledOpenApiDocumentsToMcpOpenApi as J,bundledGraphqlDocumentsToMcpOpenApi as W,buildGraphqlDescriptions as X,collectIgnoredRoutes as z,filterIgnoredApiDescriptions as P,getCleanedUpApiDescriptions as K,isMcpInRedirects as Q}from"./utils.js";import{getMcpMode as R,isMcpElicitationEnabled as Y,isMcpGatewayEnabled as Z}from"./utils/mode.js";import{getPublicMcpEndpointStatus as _}from"./utils/public-endpoint.js";import{findGatedToolNames as ee}from"./utils/find-gated-tool-names.js";import{getDefaultToolSchemas as oe}from"./docs-mcp/tool-schemas.js";import{EXECUTE_TOOL_SCHEMA as te}from"./codemode/tools/execute-schema.js";import{isGatewayCapabilityEnabled as ie,isGatewayEligible as re}from"./gateway-mcp/eligibility.js";import{resolveGatewayFetchLimits as se}from"./gateway-mcp/rate-limit/fetch-limiter/fetch-limits.js";import{resetCustomMcpTools as ne,scanCustomMcpTools as ae}from"./custom-tools/scan-custom-tools.js";import{resolveSiteBranding as ce}from"./handlers/credentials-page/site-branding.js";import{telemetryTraceStep as pe}from"../../../cli/telemetry/helpers/trace-step.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as S}from"./constants.js";const b=$(U(import.meta.url));async function de(){let i;const m={registeredToolNames:[],warnedNoOutputSchemaTools:new Set};let u=0,f=!1;return{id:"mcp",async processContent(e,a){await pe("com.redocly.build.plugin.mcp",async s=>{const t=await a.getConfig(),{mcp:r,redirects:g}=t;if(s?.setAttribute("config",JSON.stringify(r||{})),s?.setAttribute("mcpMode",R()),s?.setAttribute("mcpGateway",String(Z())),r?.hide||r?.docs?.hide){ne(e,m);return}Q(g)&&await F.panicOnBuildContentError('The reserved route "/mcp" cannot be used in redirect configuration. Please remove any redirects involving "/mcp" from your redocly.yaml config.'),e.createTemplate(x,"@redocly/theme/layouts/McpNotAllowed");const l=!!t?.access?.requiresLogin,C=t?.access?.rbac||{},d=R(),c=Y(),{budgetPerExec:h}=se();if(!i){p.info("Configuring MCP servers...");const w=J(await e.loadApiDocuments(a)),O=W(await e.loadApiDocuments(a)),v=r?.docs?.ignore||[],L=K(P(w,v)),q=ie(r),E=new Map;for(const o of L){const H=o.definition.info?.title||"",j=o.definition.info?.version||"";E.set(o.relativePath,{name:H,description:typeof o.definition.info?.description=="string"?o.definition.info.description:"",version:j,servers:o.definition.servers||[],relativePath:o.relativePath,...q&&re(o.definition.info)?{fetchEligible:!0}:{}})}const y=X(P(O,v)),G=Object.values(t.products||{}).map(o=>o?.name).filter(B),I=t.l10n?.locales?.map(o=>o.code)??a.fs.localeFolders,N=oe({products:G,locales:I,requiresLogin:l,rbac:C,hasGraphqlApis:y.length>0,elicitationEnabled:c,hasCallableApis:E.values().some(o=>o.fetchEligible),feedbackHidden:t.feedback?.hide===!0});e.addMcpTools(D(b,"./docs-mcp/tools/index.js"),N),d==="code"&&e.addMcpTools(D(b,"./codemode/tools/execute.js"),[te]),i={apiDescriptionsMap:Object.fromEntries(E),graphqlDescriptions:y}}const n=await ae(e,a,m);s?.setAttribute("customToolsCount",String(n)),n!==u&&(u=n,n>0&&p.info(`Registered ${n} custom MCP tool${n===1?"":"s"} from @mcp folders`)),await le({actions:e,handlerPath:D(b,"./handlers/docs-mcp-handler.js"),apiDescriptionsMap:i.apiDescriptionsMap,graphqlDescriptions:i.graphqlDescriptions,mcpConfig:r,mcpMode:d,elicitationEnabled:c,gatewayFetchBudgetPerExec:h,publicEndpointStatus:_(t)}),f||(p.info("Registered Docs MCP Server endpoint at /mcp"),f=!0)})},async afterRoutesCreated(e,a){const s=await a.getConfig(),{mcp:t}=s,r=!t?.hide&&!t?.docs?.hide;e.markGatedMcpTools(await ee(e.getMcpTools())),e.setGlobalData({mcpData:{docs:{enabled:r,name:t?.docs?.name||S,publicEndpoint:_(s)==="served"}}})}}}var Oe=de;async function le({actions:i,handlerPath:m,apiDescriptionsMap:u,graphqlDescriptions:f,mcpConfig:e,mcpMode:a,elicitationEnabled:s,gatewayFetchBudgetPerExec:t,publicEndpointStatus:r}){i.createRequestHandler(T,m);const g=l=>async(C,d)=>{const c=z(d.getAllRoutes(),e?.docs?.ignore||[]);return c.length>0&&p.verbose(`mcp.docs.ignore: hiding ${c.length} page routes from MCP search`),{props:{config:{apiDescriptionsMap:u,graphqlDescriptions:f,mcpDocsServerName:e?.docs?.name||S,mcpMode:a,elicitationEnabled:s,gatewayFetchBudgetPerExec:t,searchIgnoredRoutes:c,...l?{isPublicEndpoint:l}:{}},serverOutDir:i.serverOutDir,tools:i.getMcpTools().map(({importPath:h,...n})=>n)}}};if(i.addApiRoute({slug:k,requestHandlerId:T,httpMethod:"all",getStaticData:g(!1)}),r==="no-protected-content"?p.info(`mcp.docs.publicEndpoint is enabled, but the project has no protected content - /mcp already serves anonymous users, skipping ${M}.`):r==="anonymous-denied"?p.info(`mcp.docs.publicEndpoint is enabled, but anonymous users are denied the MCP feature (rbac.features.mcp or requiresLogin) - skipping ${M}.`):r==="served"&&(i.addApiRoute({slug:M,requestHandlerId:T,httpMethod:"all",getStaticData:g(!0)}),p.verbose(`Registered public Docs MCP Server endpoint at ${M}`)),s){const l=D(b,"./handlers/mcp-credentials-handler.js");i.createRequestHandler(A,l),i.addApiRoute({slug:V,requestHandlerId:A,httpMethod:"all",getStaticData:async(C,d)=>{const{seo:c,colorMode:h}=d.getConfig(),{logo:n}=d.getGlobalData();return{props:{branding:ce({seo:c,logo:n,colorMode:h})}}}}),p.verbose("Registered MCP credentials endpoint at /mcp/credentials")}}export{Oe as default,de as mcpServerPlugin};
@@ -1,3 +1,3 @@
1
- import{McpServer as C}from"@modelcontextprotocol/server";import{logger as S}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as _,MCP_TOOL_WORKER_KEY as O}from"../../../workers/mcp-tool-worker-pool.js";import{secretStore as L}from"../gateway-mcp/secret-store.js";import{readAuthRequiredMeta as D,stripAuthRequiredMeta as E,upstreamAuthKey as b}from"../gateway-mcp/upstream-auth.js";import{acceptedCredentialsLink as K,buildCredentialsAcceptedHint as j,buildCredentialsToolHint as F,buildUpstreamCredentialFallback as q,buildUpstreamCredentialInputRequired as v,credentialElicitationAnswer as H,dedupeElicitation as N,isStatelessRequest as P,schemeLabelOf as $,supportsUrlElicitation as B}from"../gateway-mcp/elicitation.js";import{DEFAULT_SCHEME_NAME as y,resolveUpstreamApiAuth as G}from"../gateway-mcp/upstream-auth-scheme.js";import{MISSING_SESSION_HANDLE_MESSAGE as V,resolveSecretSubject as W}from"../gateway-mcp/subject.js";const Y=6e4;function ue(e,t){return new C({name:e,version:new Date().toISOString().slice(0,10)},{capabilities:{logging:{}},...t?{instructions:t}:{}})}function le(e,t){return r=>async(n,s)=>{S.info(`MCP tool called: ${r}`);const i=e.elicitationEnabled??!1,u=P(s.mcpReq),a=i?W(e.user,n.sessionHandle):{kind:"anonymous"},o=a.kind==="resolved"?a.subject:void 0;if(a.kind==="invalid")return{content:[{type:"text",text:a.message}],isError:!0};const c=async()=>{if(!(!i||!o||!t?.getKv))try{const m=await t.getKv();return await L.getSecretsForSubject(m,o)}catch(m){S.warn(`gateway mcp secret store error, continuing unauthenticated: ${String(m)}`);return}};let l;const d=async()=>{l=await c();const m={toolName:r,args:n,context:{...e,upstreamSecrets:l},extra:ee(s)};return await _.exec(O,[m],{timeout:Y})};let p=await d();const f=D(p);if(f.length>0&&i&&!o)return E(I(p,[{type:"text",text:V}]));if(f.length>0&&i&&o&&t?.getKv){S.info(`gateway mcp credentials prompt: era=${u?"2026-07-28":"2025"} clientElicitation=${J(t)}`);const m=await t.getKv(),h=await Q(e,f),w=f.map(U=>X(U,h)),k=Z(l),g=u?await x(e,s,w,o,m,k,h,t):await z(e,w,o,m,k,h);switch(g.kind){case"inputRequired":return g.result;case"retry":p=await d();break;case"prependText":p=I(p,[{type:"text",text:g.text},...p.content]);break;case"none":break}}return E(p)}}function I(e,t){const{structuredContent:r,...n}=e;return{...n,content:t,isError:!0}}function J(e){const t=e?.getClientCapabilities()?.elicitation;if(!t)return"none";const r=[t.form?"form":void 0,t.url?"url":void 0].filter(Boolean);return r.length>0?r.join("+"):"none"}async function Q(e,t){const r=new Map;for(const n of new Set(t.map(s=>s.api)))r.set(n,await G(e,n));return r}function X(e,t){const r=t.get(e.api);if(!r)return{...e,scheme:e.scheme??y};const n=e.scheme&&r.schemes[e.scheme]?e.scheme:r.defaultScheme;return{...e,scheme:n}}function Z(e){const t=new Set;for(const[r,n]of Object.entries(e??{}))for(const s of Object.keys(n))t.add(b({api:r,scheme:s}));return t}function R(e,t){return e.status===401&&!t.has(b(e))}function T(e,t){const r=t.get(e.api),n=e.sentScheme??e.scheme;return r&&n?$(r.schemes,n):void 0}function M(e,t){return e.map(r=>F(r,T(r,t))).join(`
1
+ import{McpServer as U}from"@modelcontextprotocol/server";import{logger as S}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as _,MCP_TOOL_WORKER_KEY as O}from"../../../workers/mcp-tool-worker-pool.js";import{secretStore as L}from"../gateway-mcp/secret-store.js";import{readAuthRequiredMeta as D,stripAuthRequiredMeta as E,upstreamAuthKey as b}from"../gateway-mcp/upstream-auth.js";import{acceptedCredentialsLink as K,buildCredentialsAcceptedHint as j,buildCredentialsToolHint as F,buildUpstreamCredentialFallback as q,buildUpstreamCredentialInputRequired as v,credentialElicitationAnswer as H,dedupeElicitation as N,isStatelessRequest as P,schemeLabelOf as $,supportsUrlElicitation as B}from"../gateway-mcp/elicitation.js";import{DEFAULT_SCHEME_NAME as y,resolveUpstreamApiAuth as G}from"../gateway-mcp/upstream-auth-scheme.js";import{MISSING_SESSION_HANDLE_MESSAGE as V,resolveSecretSubject as W}from"../gateway-mcp/subject.js";const Y=6e4;function ue(e,t){return new U({name:e,version:new Date().toISOString().slice(0,10)},{capabilities:{logging:{}},...t?{instructions:t}:{}})}function le(e,t){return r=>async(n,i)=>{S.info(`MCP tool called: ${r}`);const s=e.elicitationEnabled??!1,u=P(i.mcpReq),a=s?W(e.user,n.sessionHandle):{kind:"anonymous"},o=a.kind==="resolved"?a.subject:void 0;if(a.kind==="invalid")return{content:[{type:"text",text:a.message}],isError:!0};const c=async()=>{if(!(!s||!o||!t?.getKv))try{const m=await t.getKv();return await L.getSecretsForSubject(m,o)}catch(m){S.warn(`gateway mcp secret store error, continuing unauthenticated: ${String(m)}`);return}};let l;const d=async()=>{l=await c();const m={toolName:r,args:n,context:{...e,upstreamSecrets:l},extra:ee(i)};return await _.exec(O,[m],{timeout:Y})};let p=await d();const f=D(p);if(f.length>0&&s&&!o)return E(R(p,[{type:"text",text:V}]));if(f.length>0&&s&&o&&t?.getKv){S.info(`gateway mcp credentials prompt: era=${u?"2026-07-28":"2025"} clientElicitation=${J(t)}`);const m=await t.getKv(),h=await Q(e,f),w=f.map(C=>X(C,h)),k=Z(l),g=u?await z(e,i,w,o,m,k,h,t):await x(e,w,o,m,k,h);switch(g.kind){case"inputRequired":return g.result;case"retry":p=await d();break;case"prependText":p=R(p,[{type:"text",text:g.text},...p.content]);break;case"none":break}}return E(p)}}function R(e,t){const{structuredContent:r,...n}=e;return{...n,content:t,isError:!0}}function J(e){const t=e?.getClientCapabilities()?.elicitation;if(!t)return"none";const r=[t.form?"form":void 0,t.url?"url":void 0].filter(Boolean);return r.length>0?r.join("+"):"none"}async function Q(e,t){const r=new Map;for(const n of new Set(t.map(i=>i.api)))r.set(n,await G(e,n));return r}function X(e,t){const r=t.get(e.api);if(!r)return{...e,scheme:e.scheme??y};const n=e.scheme&&r.schemes[e.scheme]?e.scheme:r.defaultScheme;return{...e,scheme:n}}function Z(e){const t=new Set;for(const[r,n]of Object.entries(e??{}))for(const i of Object.keys(n))t.add(b({api:r,scheme:i}));return t}function I(e,t){return e.status===401&&!t.has(b(e))}function T(e,t){const r=t.get(e.api),n=e.sentScheme??e.scheme;return r&&n?$(r.schemes,n):void 0}function M(e,t){return e.map(r=>F(r,T(r,t))).join(`
2
2
 
3
- `)}function A(e,t,r,n,s){const[i,...u]=t,a=s.get(i.api),o=i.scheme??a?.defaultScheme??y,c=i.schemes&&i.schemes.length>1?i.schemes:void 0,l=c?void 0:T(i,s);return{subject:r,baseUrl:e.baseUrl??"",target:{api:i.api,name:i.name,scheme:o,...l?{schemeLabel:l}:{},...c?{acceptedSchemes:c}:{}},auth:a?.schemes[o]??{kind:"raw"},...a?{apiAuth:a}:{},kv:n,hasExistingSecret:!1,reason:{kind:"authFailure",status:i.status,...i.detail?{detail:i.detail}:{},otherApiNames:u.map(d=>d.name)}}}async function x(e,t,r,n,s,i,u,a){const o=H(t.mcpReq.inputResponses);if(o==="declined")return{kind:"none"};const c=r.filter(d=>R(d,i));if(c.length===0)return{kind:"prependText",text:M(r,u)};if(o==="accepted"){const d=await K(t.mcpReq.requestState(),{kv:s,subject:n,baseUrl:e.baseUrl??"",apis:c.map(f=>f.api)}),p=d?.apiName??c[0].name;return{kind:"prependText",text:j(p,d?.url)}}const l=A(e,c,n,s,u);return B(a?.getClientCapabilities())?{kind:"inputRequired",result:await v(l)}:{kind:"prependText",text:await q(l)}}async function z(e,t,r,n,s,i){const u=t.filter(c=>R(c,s));if(u.length===0)return{kind:"prependText",text:M(t,i)};const a=A(e,u,r,n,i);return{kind:"prependText",text:await N(`${r}:${b(u[0])}`,()=>q(a))}}function ee(e){return{sessionId:e.sessionId,authInfo:e.http?.authInfo,requestId:e.mcpReq.id,_meta:e.mcpReq._meta}}function de(e){return{user:e.user,config:e.config,outdir:e.outdir,baseUrl:e.baseUrl,params:e.params,query:e.query,cookies:e.cookies,apiDescriptionsMap:e.apiDescriptionsMap,graphqlDescriptions:e.graphqlDescriptions,products:e.products,accessToken:e.accessToken,metadata:e.metadata,upstreamAuthorization:e.upstreamAuthorization,mcpMode:e.mcpMode,elicitationEnabled:e.elicitationEnabled,gatewayFetchBudgetPerExec:e.gatewayFetchBudgetPerExec,clientIp:e.clientIp,client:e.client,clientVersion:e.clientVersion,requestId:e.requestId,messageId:e.messageId,upstreamSecrets:e.upstreamSecrets,searchIgnoredRoutes:e.searchIgnoredRoutes,internalBaseUrl:e.internalBaseUrl,sandboxToolSchemas:e.sandboxToolSchemas,fs:void 0}}export{ue as createMcpServer,le as createToolDispatch,de as extractSerializableContext};
3
+ `)}function A(e,t,r,n,i){const[s,...u]=t,a=i.get(s.api),o=s.scheme??a?.defaultScheme??y,c=s.schemes&&s.schemes.length>1?s.schemes:void 0,l=c?void 0:T(s,i);return{subject:r,baseUrl:e.baseUrl??"",target:{api:s.api,name:s.name,scheme:o,...l?{schemeLabel:l}:{},...c?{acceptedSchemes:c}:{}},auth:a?.schemes[o]??{kind:"raw"},...a?{apiAuth:a}:{},kv:n,hasExistingSecret:!1,reason:{kind:"authFailure",status:s.status,...s.detail?{detail:s.detail}:{},otherApiNames:u.map(d=>d.name)}}}async function z(e,t,r,n,i,s,u,a){const o=H(t.mcpReq.inputResponses);if(o==="declined")return{kind:"none"};const c=r.filter(d=>I(d,s));if(c.length===0)return{kind:"prependText",text:M(r,u)};if(o==="accepted"){const d=await K(t.mcpReq.requestState(),{kv:i,subject:n,baseUrl:e.baseUrl??"",apis:c.map(f=>f.api)}),p=d?.apiName??c[0].name;return{kind:"prependText",text:j(p,d?.url)}}const l=A(e,c,n,i,u);return B(a?.getClientCapabilities())?{kind:"inputRequired",result:await v(l)}:{kind:"prependText",text:await q(l)}}async function x(e,t,r,n,i,s){const u=t.filter(c=>I(c,i));if(u.length===0)return{kind:"prependText",text:M(t,s)};const a=A(e,u,r,n,s);return{kind:"prependText",text:await N(`${r}:${b(u[0])}`,()=>q(a))}}function ee(e){return{sessionId:e.sessionId,authInfo:e.http?.authInfo,requestId:e.mcpReq.id,_meta:e.mcpReq._meta}}function de(e){return{user:e.user,config:e.config,outdir:e.outdir,baseUrl:e.baseUrl,params:e.params,query:e.query,cookies:e.cookies,apiDescriptionsMap:e.apiDescriptionsMap,graphqlDescriptions:e.graphqlDescriptions,products:e.products,accessToken:e.accessToken,metadata:e.metadata,upstreamAuthorization:e.upstreamAuthorization,mcpMode:e.mcpMode,elicitationEnabled:e.elicitationEnabled,gatewayFetchBudgetPerExec:e.gatewayFetchBudgetPerExec,fetchCountRequest:e.fetchCountRequest,clientIp:e.clientIp,client:e.client,clientVersion:e.clientVersion,requestId:e.requestId,messageId:e.messageId,upstreamSecrets:e.upstreamSecrets,searchIgnoredRoutes:e.searchIgnoredRoutes,internalBaseUrl:e.internalBaseUrl,sandboxToolSchemas:e.sandboxToolSchemas,fs:void 0}}export{ue as createMcpServer,le as createToolDispatch,de as extractSerializableContext};
@@ -36,6 +36,7 @@ export type McpDocsStaticData = PageStaticData & {
36
36
  props: {
37
37
  config: McpDocsStaticDataPropsConfig;
38
38
  serverOutDir?: string;
39
+ fetchCountSecret?: string;
39
40
  tools?: McpToolSchema[];
40
41
  skills?: Skill[];
41
42
  skillsBaseUrl?: string;
@@ -88,6 +89,13 @@ export type McpToolExtra = {
88
89
  requestId: RequestId;
89
90
  _meta?: RequestMeta;
90
91
  };
92
+ export type FetchCountRequest = {
93
+ caller: {
94
+ subject?: string;
95
+ ip?: string;
96
+ };
97
+ secret: string;
98
+ };
91
99
  export type McpToolContext = Omit<ApiFunctionsContext, 'telemetry' | 'getKv' | keyof ApiFunctionsContextMethods> & {
92
100
  apiDescriptionsMap: Record<string, ApiDescriptionInfo>;
93
101
  graphqlDescriptions: GraphqlDescriptionInfo[];
@@ -98,6 +106,7 @@ export type McpToolContext = Omit<ApiFunctionsContext, 'telemetry' | 'getKv' | k
98
106
  mcpMode?: McpMode;
99
107
  elicitationEnabled?: boolean;
100
108
  gatewayFetchBudgetPerExec?: number;
109
+ fetchCountRequest?: FetchCountRequest;
101
110
  clientIp?: string;
102
111
  client?: string;
103
112
  clientVersion?: string;
@@ -1 +1 @@
1
- import*as v from"path";import{REDOCLY_ROUTE_RBAC as b,REDOCLY_TEAMS_RBAC as h}from"@redocly/config";import{VERSION_SEPARATOR as M}from"../constants/common.js";import{DEFAULT_LOCALE_PLACEHOLDER as _}from"../../constants/common.js";import{removeTrailingSlash as j}from"../../utils/url/remove-trailing-slash.js";import{isPrimitive as W}from"../../utils/guards/is-primitive.js";import{slash as w}from"../../utils/path/slash.js";import{isAbsoluteUrl as z}from"../../utils/url/is-absolute-url.js";import{normalizeRouteSlug as J}from"../../utils/path/normalize-route-slug.js";import{parsePathVersions as C}from"../../utils/path/parse-path-versions.js";import{logger as A}from"../tools/notifiers/logger.js";import{reporter as S}from"../tools/notifiers/reporter.js";import{shaDirPathShort as R}from"../utils/crypto/sha-dir-path-short.js";import{copyStaticFile as U,FileNotFoundError as E}from"../utils/fs.js";import{isIconPath as H,resolveAssetPath as Y}from"../utils/index.js";import{resolveSrcSet as G}from"../utils/resolve-src-set.js";import{isL10nPath as B}from"../fs/utils/is-l10n-path.js";import{getLocaleFromRelativePath as q}from"../fs/utils/get-locale-from-relative-path.js";async function N(e,i,n,t,a){if(!e)return;const f={...a,ignoreCustomSidebar:!0};if(W(e)){if(typeof e=="string"){const r=F(n.contentDir,i,a,e,t.fs);if(t.fs.exists(r)){if(n.getRouteByFsPath(r))return K({page:e},i,n,t,f);{const g=t.fs.getFileInfo(r);return g?U(n.contentDir,g.realRelativePath,n.outdir):void 0}}}return e}return Array.isArray(e)?I(D(e),i,n,t,f):Object.fromEntries(await Promise.all(Object.entries(e).map(async([r,l])=>[r,await N(l,i,n,t,a)])))}function D(e){return e.map(i=>({...i,items:i.items?D(i.items):void 0}))}async function I(e,i,n,t,a){if(Array.isArray(e))return(await Promise.all(e.map(f=>K(f,i,n,t,a)))).flatMap(f=>f)}async function y(e,i,n,t){let a,f;if(typeof e.icon=="object"){if("srcSet"in e.icon)try{a=await G(e.icon.srcSet,n.fs,{fromFileRelativePath:t.navFile,contentDir:i.contentDir,outdir:i.outdir})}catch(d){d instanceof E?await S.panicOnBuildContentError(`Cannot resolve "item.icon.srcSet" from ${t.navFile}: ${d.message}`):await S.panicOnBuild(`Cannot resolve "item.icon.srcSet" from ${t.navFile}: ${d.message}`)}}else if(typeof e.icon=="string")if(z(e.icon)||H(e.icon))try{f=await Y(e.icon,n.fs,{fromFileRelativePath:t.navFile,contentDir:i.contentDir,outdir:i.outdir})}catch(d){d instanceof E?await S.panicOnBuildContentError(`Cannot resolve "item.icon" from ${t.navFile}: file ${f} does not exist`):await S.panicOnBuild(`Cannot resolve "item.icon" from ${t.navFile}: ${d.message}`)}else f=e.icon;return{icon:f,srcSet:a}}function Q(e){return i=>{if(i.type==="link"||i.type==="group"){const n=i.metadata;return n?Object.entries(e).every(([t,a])=>Array.isArray(a)?a.some(f=>n[t]===f):n[t]===a):!1}return!0}}async function K(e,i,n,t,a){if(e?.tab!==void 0||e?.menu!==void 0){const s=`Navigation tabs ("tab"/"menu") are only allowed at the top level of a sidebars.yaml file: ${JSON.stringify(e)}`;return await S.panicOnBuildContentError(`Invalid nav item in %rp. ${s}`,a.navFile),{type:"error",label:s}}if(e?.directory&&e.items?.length)return{type:"error",label:`Can't have both "directory" and "items" in the nav item: ${JSON.stringify(e)}`};e?.directory&&e?.group&&(e={...e,directory:void 0,items:[{directory:e.directory}]});const f=e?.directory?F(n.contentDir,i,a,e.directory,t.fs):void 0;if(f!==void 0){const s=f===""?"":f+"/",{locale:u,ignoredRoutes:o}=a;let p=n.getAllRoutesForLocale(u).filter(c=>!o?.has(c.baseSlug||"")&&c.fsPath.startsWith(s)&&!c.excludeFromSidebar).sort((c,P)=>c.fsPath===P.fsPath?0:c.baseSlug.localeCompare(P.baseSlug,void 0,{numeric:!0}));const m=e.includeByMetadata?Q(e.includeByMetadata):Boolean;if(!p.length)return[];const T=(await Promise.all(p.map(async c=>{const P=c.versions?.find(x=>x.active),L=P&&{version:P.version,isDefault:P.default,versionFolderId:P.folderId},V=ee(v.posix.relative(f,c.fsPath)),O=c.getSidebar?.(c);return!a.ignoreCustomSidebar&&O?.length?{type:"group",fsPath:c.fsPath,metadata:c.metadata,link:c.slug,routeSlug:c.slug,label:await c.getNavText?.()||c.slug,[h]:c[h],[b]:c[b],sidebarItems:k(O,L,c[h],c[b]),...L,relativePathFromDirectory:v.relative(f,c.fsPath).replace(/@(?<!@[^/]*?@)[^/]+\//,"")}:{type:"link",fsPath:c.fsPath,metadata:c.metadata,[h]:c[h],[b]:c[b],label:await c.getNavText?.()||c.slug,link:c.slug,routeSlug:c.slug,...L,relativePathFromDirectory:V}}))).filter(m);return e.flatten?$(T,a):Z(T,a)}let d=typeof e=="string"?e:e?.page,r,l;if(d){if(d.includes("#")){const[s,u]=d.split("#");u?l=u:A.warn(`Invalid heading anchor format in sidebar: "${u}". Heading anchors should contain only alphanumeric characters, hyphens, and underscores.`),d=s}if(d=j(d),a.locale&&a.locale!==_){const s=J(v.join(a.locale.toLowerCase(),d));r=n.getRouteBySlug(s)}r||(r=n.getRouteBySlug(d)),r||(r=n.getRouteByFsPath(F(n.contentDir,i,a,d,t.fs)))}const g=r?.metadata;if(e?.$ref){let s=F(n.contentDir,i,a,e.$ref,t.fs);if(a.ref!==void 0&&(s=v.posix.join(a.ref,e.$ref)),!t.fs.exists(s))return await S.panicOnBuildContentError(`Failed to load ${s} file. Make sure sidebar $ref path is correct.`),[];const u=await t.cache.load(s,"yaml"),o=v.dirname(s);return await I(u.data,i,n,t,{...a,navFile:s,ref:o})||[]}if(!r){const s=a.excludedFromLinkCheckerPatterns?.catalog.some(o=>o.test(d||"")),u=a.excludedFromLinkCheckerPatterns?.apiFunctions.some(o=>o.test(d||e.href||""));(u||s)&&(e={...e,href:d||e.href,page:void 0,target:e.target??(u?"_blank":void 0)}),await X(e,a,t,i)}if(r&&!e.disconnect&&!a.ignoreCustomSidebar&&(l||r?.getSidebar)){const s=r.versions?.find(o=>o.active),u=s&&{version:s.version,isDefault:s.default,versionFolderId:s.folderId};if(l){let o=[];if(r.getSidebar&&(o=r.getSidebar(r,{...e,...await y(e,n,t,a)}),o&&o.length>0)){const p=o[0];p&&p.link&&(p.link=`${p.link}#${l}`)}if(!o||o.length===0){let p=e.group||e.label||l;o=[{type:"link",fsPath:r.fsPath,metadata:r.metadata,link:`${r.slug}#${l}`,routeSlug:r.slug,label:p,labelTranslationKey:e.labelTranslationKey,...await y(e,n,t,a),[h]:r[h],[b]:{slug:r.slug,fsPath:r.fsPath},...u}]}return k(o,u,r[h],r[b])}if(r?.getSidebar){const o=r.getSidebar(r,{...e,...await y(e,n,t,a)}),p=e.group&&o?.[0]?.routeSlug===r.slug?o.slice(1):o;return k(a.groupCustomSidebars||e.group?[{type:"group",fsPath:r.fsPath,link:r.slug,routeSlug:r.slug,label:e.group||e.label||await r.getNavText?.()||r.slug,items:p,labelTranslationKey:e.labelTranslationKey,metadata:g,badges:e.badges,...await y(e,n,t,a)}]:o,u,r[h],r[b])}}if(r&&!e.group){const s=l?`${r.slug}#${l}`:r.slug;let u=e.label||l||"";u||(u=await r.getNavText?.()||r.slug);const o=r.versions?.find(m=>m.active),p=o&&{version:o.version,isDefault:o.default,versionFolderId:o.folderId};return{type:"link",fsPath:r.fsPath,linkedSidebars:e.linkedSidebars,metadata:g,[h]:e?.rbac||r[h],[b]:{slug:r.slug,fsPath:r.fsPath},label:u,labelTranslationKey:e.labelTranslationKey,link:s,items:await I(e.items,i,n,t,a),separatorLine:e.separatorLine,linePosition:e.linePosition,routeSlug:e.disconnect?void 0:s,external:e.external,target:e.target,badges:e.badges,...await y(e,n,t,a),...p,additionalProps:e.additionalProps}}else if(e?.group){const s=r?.versions?.find(o=>o.active),u=C(a?.navFile);return{type:"group",fsPath:r?.fsPath,linkedSidebars:e.linkedSidebars,metadata:g,version:s?.version||u?.versionName,versionFolderId:s?.folderId||(u?.versionFolderPath?R(u.versionFolderPath):void 0),label:e.group,link:r?.slug,routeSlug:r?.slug,badges:e.badges,labelTranslationKey:e.groupTranslationKey,items:await I(e.items,i,n,t,a),expanded:e.expanded?e.expanded.toString():void 0,selectFirstItemOnExpand:e.selectFirstItemOnExpand,menuStyle:e.menuStyle,separatorLine:e.separatorLine,linePosition:e.linePosition,...await y(e,n,t,a),[h]:e?.rbac,[b]:{slug:r?.slug||"",fsPath:r?.fsPath||""},additionalProps:e.additionalProps}}else{if(e?.href||e?.label)return{type:"link",metadata:g,link:e.href||"",label:e?.label||e?.href||"",labelTranslationKey:e.labelTranslationKey,external:e.external,target:e.target,separatorLine:e.separatorLine,linePosition:e.linePosition,badges:e.badges,...await y(e,n,t,a),[h]:e?.rbac,additionalProps:e.additionalProps};if(e?.separator!=null||e?.separatorLine!=null){const s=C(a?.navFile);return{type:"separator",metadata:g,version:s?.versionName,versionFolderId:s?.versionFolderPath?R(s.versionFolderPath):void 0,label:e.separator,labelTranslationKey:e.separatorTranslationKey,separatorLine:e.separatorLine,linePosition:e.linePosition,...await y(e,n,t,a),[h]:e?.rbac,additionalProps:e.additionalProps}}else{const s=C(a?.navFile);return{type:"error",label:`Can't resolve page: ${JSON.stringify(e)}`,version:s?.versionName,versionFolderId:s?.versionFolderPath?R(s.versionFolderPath):void 0}}}}async function X(e,i,n,t){const{href:a,page:f}=e;if(a||!f)return;const d=F(n.fs.cwd,t,i,f,n.fs),r=n.fs.exists(d),l=await n.isPathIgnored(d),g=(r?"The page is ignored: ":"Can't resolve page: ")+f;(!r||l)&&S.reportBrokenLink({type:"BROKEN_LINK",brokenLinkType:"LINK",title:e.label||e.group||"",link:d,rawLink:f,message:g,sourceFileRelativePath:i.navFile,sourceFileLocation:{line:0}})}function F(e,i,n,t="",a){if(n.ref!==void 0)return w(v.relative(e,v.resolve(e,v.join(n.ref,t))));if(t.startsWith("/")){const f=B(i)?q(i):void 0,d=f?`${a.localizationFolder}/${f}/`:"",r=t.slice(1);return B(r)?r:d+w(r)}return w(v.relative(e,v.resolve(e,i,t)))}function Z(e,i){const n={};for(const r of e){const l=v.dirname(r.relativePathFromDirectory),g=r.version?M+r.version:"",s=v.basename(r.relativePathFromDirectory)+g;d(l,r,s)}const t=f(a(n));return $(t,i);function a(r){const l=[];for(const g of Object.keys(r)){const s=r[g];l.push({...s,items:s.items?a(s.items):void 0})}return l}function f(r){return r.sort((l,g)=>{const s=typeof l=="string"?l.toLowerCase():l.relativePathFromDirectory||l.label||"",u=typeof g=="string"?g.toLowerCase():g.relativePathFromDirectory||g.label||"";return s.startsWith("index.")?-1:u.startsWith("index.")?1:s.localeCompare(u,void 0,{numeric:!0})})}function d(r,l,g){r==="."&&(r="");const s=r.split("/").filter(Boolean);let u=n;for(const o of s){const p=o+(l?.version||"");u[p]||(u[p]={type:"group",version:l?.version,label:o,isDefault:l?.isDefault,versionFolderId:l.versionFolderId,items:{}}),u=u[p].items}u[g]=l}}function $(e,i){return e.flatMap(n=>n?.sidebarItems?i.groupCustomSidebars?{...n,items:n.sidebarItems,sidebarItems:void 0,relativePathFromDirectory:void 0}:n.sidebarItems:{...n,items:n.items?$(n.items,i):void 0})}function k(e,i,n,t){return e.map(a=>({...a,...i,[h]:a[h]||n,[b]:a[b]||t,items:a.type==="group"&&a.items?k(a.items,i,n):a.items}))}function ee(e){return e.replace(/@(?<!@[^/]*?@)[^/]+\//,"")}async function ye(e,i,n,t){const a=(e?.items||[]).filter(r=>r.linkedSidebars?.length).map(r=>({...r,items:void 0})),f=await N(a,i.contentDir,i,n,t),d=new Map;for(const r of f)if(r.linkedSidebars?.length)for(const l of r.linkedSidebars){if(d.has(l)){A.warn(`Only one navbar item can belong to sidebar. "${l}" on 'linkedSidebars' property on "${r.label}" will be ignored.`);continue}d.set(l,{label:r.label,link:r.link})}return d}export{ye as collectItemsLinkedToSidebars,D as normalizeItems,K as resolveItem,I as resolveItems,N as resolveLinksFromConfig};
1
+ import*as v from"path";import{REDOCLY_ROUTE_RBAC as b,REDOCLY_TEAMS_RBAC as h}from"@redocly/config";import{VERSION_SEPARATOR as M}from"../constants/common.js";import{DEFAULT_LOCALE_PLACEHOLDER as _}from"../../constants/common.js";import{removeTrailingSlash as j}from"../../utils/url/remove-trailing-slash.js";import{isPrimitive as W}from"../../utils/guards/is-primitive.js";import{slash as w}from"../../utils/path/slash.js";import{isAbsoluteUrl as z}from"../../utils/url/is-absolute-url.js";import{normalizeRouteSlug as J}from"../../utils/path/normalize-route-slug.js";import{parsePathVersions as C}from"../../utils/path/parse-path-versions.js";import{logger as A}from"../tools/notifiers/logger.js";import{reporter as S}from"../tools/notifiers/reporter.js";import{shaDirPathShort as R}from"../utils/crypto/sha-dir-path-short.js";import{copyStaticFile as U,FileNotFoundError as E}from"../utils/fs.js";import{isIconPath as H,resolveAssetPath as Y}from"../utils/index.js";import{resolveSrcSet as G}from"../utils/resolve-src-set.js";import{isL10nPath as B}from"../fs/utils/is-l10n-path.js";import{getLocaleFromRelativePath as q}from"../fs/utils/get-locale-from-relative-path.js";async function N(e,i,n,t,a){if(!e)return;const f={...a,ignoreCustomSidebar:!0};if(W(e)){if(typeof e=="string"){const r=F(n.contentDir,i,a,e,t.fs);if(t.fs.exists(r)){if(n.getRouteByFsPath(r))return K({page:e},i,n,t,f);{const g=t.fs.getFileInfo(r);return g?U(n.contentDir,g.realRelativePath,n.outdir):void 0}}}return e}return Array.isArray(e)?I(D(e),i,n,t,f):Object.fromEntries(await Promise.all(Object.entries(e).map(async([r,l])=>[r,await N(l,i,n,t,a)])))}function D(e){return e.map(i=>({...i,items:i.items?D(i.items):void 0}))}async function I(e,i,n,t,a){if(Array.isArray(e))return(await Promise.all(e.map(f=>K(f,i,n,t,a)))).flatMap(f=>f)}async function y(e,i,n,t){let a,f;if(typeof e.icon=="object"){if("srcSet"in e.icon)try{a=await G(e.icon.srcSet,n.fs,{fromFileRelativePath:t.navFile,contentDir:i.contentDir,outdir:i.outdir})}catch(d){d instanceof E?await S.panicOnBuildContentError(`Cannot resolve "item.icon.srcSet" from ${t.navFile}: ${d.message}`):await S.panicOnBuild(`Cannot resolve "item.icon.srcSet" from ${t.navFile}: ${d.message}`)}}else if(typeof e.icon=="string")if(z(e.icon)||H(e.icon))try{f=await Y(e.icon,n.fs,{fromFileRelativePath:t.navFile,contentDir:i.contentDir,outdir:i.outdir})}catch(d){d instanceof E?await S.panicOnBuildContentError(`Cannot resolve "item.icon" from ${t.navFile}: file ${f} does not exist`):await S.panicOnBuild(`Cannot resolve "item.icon" from ${t.navFile}: ${d.message}`)}else f=e.icon;return{icon:f,srcSet:a}}function Q(e){return i=>{if(i.type==="link"||i.type==="group"){const n=i.metadata;return n?Object.entries(e).every(([t,a])=>Array.isArray(a)?a.some(f=>n[t]===f):n[t]===a):!1}return!0}}async function K(e,i,n,t,a){if(e?.tab!==void 0||e?.menu!==void 0){const s=`Navigation tabs ("tab"/"menu") are only allowed at the top level of a sidebars.yaml file: ${JSON.stringify(e)}`;return await S.panicOnBuildContentError(`Invalid nav item in %rp. ${s}`,a.navFile),{type:"error",label:s}}if(e?.directory&&e.items?.length)return{type:"error",label:`Can't have both "directory" and "items" in the nav item: ${JSON.stringify(e)}`};e?.directory&&e?.group&&(e={...e,directory:void 0,items:[{directory:e.directory}]});const f=e?.directory?F(n.contentDir,i,a,e.directory,t.fs):void 0;if(f!==void 0){const s=f===""?"":f+"/",{locale:u,ignoredRoutes:o}=a,p=n.getAllRoutesForLocale(u).filter(c=>!o?.has(c.baseSlug||"")&&c.fsPath.startsWith(s)&&!c.excludeFromSidebar).sort((c,P)=>c.fsPath===P.fsPath?0:c.baseSlug.localeCompare(P.baseSlug,void 0,{numeric:!0})),m=e.includeByMetadata?Q(e.includeByMetadata):Boolean;if(!p.length)return[];const T=(await Promise.all(p.map(async c=>{const P=c.versions?.find(x=>x.active),L=P&&{version:P.version,isDefault:P.default,versionFolderId:P.folderId},V=ee(v.posix.relative(f,c.fsPath)),O=c.getSidebar?.(c);return!a.ignoreCustomSidebar&&O?.length?{type:"group",fsPath:c.fsPath,metadata:c.metadata,link:c.slug,routeSlug:c.slug,label:await c.getNavText?.()||c.slug,[h]:c[h],[b]:c[b],sidebarItems:k(O,L,c[h],c[b]),...L,relativePathFromDirectory:v.relative(f,c.fsPath).replace(/@(?<!@[^/]*?@)[^/]+\//,"")}:{type:"link",fsPath:c.fsPath,metadata:c.metadata,[h]:c[h],[b]:c[b],label:await c.getNavText?.()||c.slug,link:c.slug,routeSlug:c.slug,...L,relativePathFromDirectory:V}}))).filter(m);return e.flatten?$(T,a):Z(T,a)}let d=typeof e=="string"?e:e?.page,r,l;if(d){if(d.includes("#")){const[s,u]=d.split("#");u?l=u:A.warn(`Invalid heading anchor format in sidebar: "${u}". Heading anchors should contain only alphanumeric characters, hyphens, and underscores.`),d=s}if(d=j(d),a.locale&&a.locale!==_){const s=J(v.join(a.locale.toLowerCase(),d));r=n.getRouteBySlug(s)}r||(r=n.getRouteBySlug(d)),r||(r=n.getRouteByFsPath(F(n.contentDir,i,a,d,t.fs)))}const g=r?.metadata;if(e?.$ref){let s=F(n.contentDir,i,a,e.$ref,t.fs);if(a.ref!==void 0&&(s=v.posix.join(a.ref,e.$ref)),!t.fs.exists(s))return await S.panicOnBuildContentError(`Failed to load ${s} file. Make sure sidebar $ref path is correct.`),[];const u=await t.cache.load(s,"yaml"),o=v.dirname(s);return await I(u.data,i,n,t,{...a,navFile:s,ref:o})||[]}if(!r){const s=a.excludedFromLinkCheckerPatterns?.catalog.some(o=>o.test(d||"")),u=a.excludedFromLinkCheckerPatterns?.apiFunctions.some(o=>o.test(d||e.href||""));(u||s)&&(e={...e,href:d||e.href,page:void 0,target:e.target??(u?"_blank":void 0)}),await X(e,a,t,i)}if(r&&!e.disconnect&&!a.ignoreCustomSidebar&&(l||r?.getSidebar)){const s=r.versions?.find(o=>o.active),u=s&&{version:s.version,isDefault:s.default,versionFolderId:s.folderId};if(l){let o=[];if(r.getSidebar&&(o=r.getSidebar(r,{...e,...await y(e,n,t,a)}),o&&o.length>0)){const p=o[0];p&&p.link&&(p.link=`${p.link}#${l}`)}if(!o||o.length===0){const p=e.group||e.label||l;o=[{type:"link",fsPath:r.fsPath,metadata:r.metadata,link:`${r.slug}#${l}`,routeSlug:r.slug,label:p,labelTranslationKey:e.labelTranslationKey,...await y(e,n,t,a),[h]:r[h],[b]:{slug:r.slug,fsPath:r.fsPath},...u}]}return k(o,u,r[h],r[b])}if(r?.getSidebar){const o=r.getSidebar(r,{...e,...await y(e,n,t,a)}),p=e.group&&o?.[0]?.routeSlug===r.slug?o.slice(1):o;return k(a.groupCustomSidebars||e.group?[{type:"group",fsPath:r.fsPath,link:r.slug,routeSlug:r.slug,label:e.group||e.label||await r.getNavText?.()||r.slug,items:p,labelTranslationKey:e.labelTranslationKey,metadata:g,badges:e.badges,...await y(e,n,t,a)}]:o,u,r[h],r[b])}}if(r&&!e.group){const s=l?`${r.slug}#${l}`:r.slug;let u=e.label||l||"";u||(u=await r.getNavText?.()||r.slug);const o=r.versions?.find(m=>m.active),p=o&&{version:o.version,isDefault:o.default,versionFolderId:o.folderId};return{type:"link",fsPath:r.fsPath,linkedSidebars:e.linkedSidebars,metadata:g,[h]:e?.rbac||r[h],[b]:{slug:r.slug,fsPath:r.fsPath},label:u,labelTranslationKey:e.labelTranslationKey,link:s,items:await I(e.items,i,n,t,a),separatorLine:e.separatorLine,linePosition:e.linePosition,routeSlug:e.disconnect?void 0:s,external:e.external,target:e.target,badges:e.badges,...await y(e,n,t,a),...p,additionalProps:e.additionalProps}}else if(e?.group){const s=r?.versions?.find(o=>o.active),u=C(a?.navFile);return{type:"group",fsPath:r?.fsPath,linkedSidebars:e.linkedSidebars,metadata:g,version:s?.version||u?.versionName,versionFolderId:s?.folderId||(u?.versionFolderPath?R(u.versionFolderPath):void 0),label:e.group,link:r?.slug,routeSlug:r?.slug,badges:e.badges,labelTranslationKey:e.groupTranslationKey,items:await I(e.items,i,n,t,a),expanded:e.expanded?e.expanded.toString():void 0,selectFirstItemOnExpand:e.selectFirstItemOnExpand,menuStyle:e.menuStyle,separatorLine:e.separatorLine,linePosition:e.linePosition,...await y(e,n,t,a),[h]:e?.rbac,[b]:{slug:r?.slug||"",fsPath:r?.fsPath||""},additionalProps:e.additionalProps}}else{if(e?.href||e?.label)return{type:"link",metadata:g,link:e.href||"",label:e?.label||e?.href||"",labelTranslationKey:e.labelTranslationKey,external:e.external,target:e.target,separatorLine:e.separatorLine,linePosition:e.linePosition,badges:e.badges,...await y(e,n,t,a),[h]:e?.rbac,additionalProps:e.additionalProps};if(e?.separator!=null||e?.separatorLine!=null){const s=C(a?.navFile);return{type:"separator",metadata:g,version:s?.versionName,versionFolderId:s?.versionFolderPath?R(s.versionFolderPath):void 0,label:e.separator,labelTranslationKey:e.separatorTranslationKey,separatorLine:e.separatorLine,linePosition:e.linePosition,...await y(e,n,t,a),[h]:e?.rbac,additionalProps:e.additionalProps}}else{const s=C(a?.navFile);return{type:"error",label:`Can't resolve page: ${JSON.stringify(e)}`,version:s?.versionName,versionFolderId:s?.versionFolderPath?R(s.versionFolderPath):void 0}}}}async function X(e,i,n,t){const{href:a,page:f}=e;if(a||!f)return;const d=F(n.fs.cwd,t,i,f,n.fs),r=n.fs.exists(d),l=await n.isPathIgnored(d),g=(r?"The page is ignored: ":"Can't resolve page: ")+f;(!r||l)&&S.reportBrokenLink({type:"BROKEN_LINK",brokenLinkType:"LINK",title:e.label||e.group||"",link:d,rawLink:f,message:g,sourceFileRelativePath:i.navFile,sourceFileLocation:{line:0}})}function F(e,i,n,t="",a){if(n.ref!==void 0)return w(v.relative(e,v.resolve(e,v.join(n.ref,t))));if(t.startsWith("/")){const f=B(i)?q(i):void 0,d=f?`${a.localizationFolder}/${f}/`:"",r=t.slice(1);return B(r)?r:d+w(r)}return w(v.relative(e,v.resolve(e,i,t)))}function Z(e,i){const n={};for(const r of e){const l=v.dirname(r.relativePathFromDirectory),g=r.version?M+r.version:"",s=v.basename(r.relativePathFromDirectory)+g;d(l,r,s)}const t=f(a(n));return $(t,i);function a(r){const l=[];for(const g of Object.keys(r)){const s=r[g];l.push({...s,items:s.items?a(s.items):void 0})}return l}function f(r){return r.sort((l,g)=>{const s=typeof l=="string"?l.toLowerCase():l.relativePathFromDirectory||l.label||"",u=typeof g=="string"?g.toLowerCase():g.relativePathFromDirectory||g.label||"";return s.startsWith("index.")?-1:u.startsWith("index.")?1:s.localeCompare(u,void 0,{numeric:!0})})}function d(r,l,g){r==="."&&(r="");const s=r.split("/").filter(Boolean);let u=n;for(const o of s){const p=o+(l?.version||"");u[p]||(u[p]={type:"group",version:l?.version,label:o,isDefault:l?.isDefault,versionFolderId:l.versionFolderId,items:{}}),u=u[p].items}u[g]=l}}function $(e,i){return e.flatMap(n=>n?.sidebarItems?i.groupCustomSidebars?{...n,items:n.sidebarItems,sidebarItems:void 0,relativePathFromDirectory:void 0}:n.sidebarItems:{...n,items:n.items?$(n.items,i):void 0})}function k(e,i,n,t){return e.map(a=>({...a,...i,[h]:a[h]||n,[b]:a[b]||t,items:a.type==="group"&&a.items?k(a.items,i,n):a.items}))}function ee(e){return e.replace(/@(?<!@[^/]*?@)[^/]+\//,"")}async function ye(e,i,n,t){const a=(e?.items||[]).filter(r=>r.linkedSidebars?.length).map(r=>({...r,items:void 0})),f=await N(a,i.contentDir,i,n,t),d=new Map;for(const r of f)if(r.linkedSidebars?.length)for(const l of r.linkedSidebars){if(d.has(l)){A.warn(`Only one navbar item can belong to sidebar. "${l}" on 'linkedSidebars' property on "${r.label}" will be ignored.`);continue}d.set(l,{label:r.label,link:r.link})}return d}export{ye as collectItemsLinkedToSidebars,D as normalizeItems,K as resolveItem,I as resolveItems,N as resolveLinksFromConfig};
@@ -1 +1 @@
1
- import w from"node:path";import{regexFromString as h}from"@redocly/openapi-core";import{reporter as x}from"../../../tools/notifiers/reporter.js";import{lintDefinition as C}from"../lint.js";import{ExternalResolver as N}from"../../../fs/utils/external-ref-resolver.js";const I="Non-conformant";async function F(t,e){const{data:{levelsConfig:n,configs:i,targets:r}}=await e.cache.load("","scorecard-config"),o={};let s=I,d=!1;const m=await e.fs.read(t),{data:f}=await e.cache.load(t,"yaml"),g=new N(e.fs);let l={};if(r.length){const a=await e.getConfig(w.posix.dirname(t)),c=f?.info?.["x-metadata"],p={title:f.info.title,version:f.info.version,...c,...a.metadata};l=(await M(r,p))?.configs||{}}for(const a of n){const c=await C({parsed:f,content:m,relativePath:t},l[a.name]||i[a.name],e,g);o[a.name]=c,c.errors===0&&!d?s=a.name:d=!0}let u=n.findIndex(a=>a.name===s);return{levels:o,scorecardLevel:s,scorecardLevelIdx:u+1}}async function M(t,e){if(t)for(const n of t){const i=Object.entries(n.where?.metadata||{});let r=!0;for(const[o,s]of i)if(!await O(o,s,e)){r=!1;break}if(r)return n}}async function O(t,e,n){if(e.match(/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}Z)?\/(\d{4}-\d{2}-\d{2})?$/)){if(!n[t])return!1;const[i,r]=e.split("/"),o=new Date(n[t]);return isNaN(o.getTime())?!1:!(o<new Date(i)||r&&o>new Date(r))}else if(e.match(/^\/.*\//)){if(!n[t])return!1;try{return h(e).test(n[t])}catch{return await x.panicOnContentError(`Invalid regex in scorecard target "${t}": ${e}`),!1}}return n[t]===e}export{M as getTarget,F as scorecardLoader};
1
+ import w from"node:path";import{regexFromString as h}from"@redocly/openapi-core";import{reporter as x}from"../../../tools/notifiers/reporter.js";import{lintDefinition as C}from"../lint.js";import{ExternalResolver as N}from"../../../fs/utils/external-ref-resolver.js";const I="Non-conformant";async function F(t,e){const{data:{levelsConfig:n,configs:i,targets:r}}=await e.cache.load("","scorecard-config"),o={};let s=I,d=!1;const l=await e.fs.read(t),{data:f}=await e.cache.load(t,"yaml"),g=new N(e.fs);let m={};if(r.length){const a=await e.getConfig(w.posix.dirname(t)),c=f?.info?.["x-metadata"],p={title:f.info.title,version:f.info.version,...c,...a.metadata};m=(await M(r,p))?.configs||{}}for(const a of n){const c=await C({parsed:f,content:l,relativePath:t},m[a.name]||i[a.name],e,g);o[a.name]=c,c.errors===0&&!d?s=a.name:d=!0}const u=n.findIndex(a=>a.name===s);return{levels:o,scorecardLevel:s,scorecardLevelIdx:u+1}}async function M(t,e){if(t)for(const n of t){const i=Object.entries(n.where?.metadata||{});let r=!0;for(const[o,s]of i)if(!await O(o,s,e)){r=!1;break}if(r)return n}}async function O(t,e,n){if(e.match(/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}Z)?\/(\d{4}-\d{2}-\d{2})?$/)){if(!n[t])return!1;const[i,r]=e.split("/"),o=new Date(n[t]);return isNaN(o.getTime())?!1:!(o<new Date(i)||r&&o>new Date(r))}else if(e.match(/^\/.*\//)){if(!n[t])return!1;try{return h(e).test(n[t])}catch{return await x.panicOnContentError(`Invalid regex in scorecard target "${t}": ${e}`),!1}}return n[t]===e}export{M as getTarget,F as scorecardLoader};
@@ -1 +1 @@
1
- import{useMemo as p,useState as H,useCallback as U}from"react";import{isDefined as G}from"../../../../utils/guards/is-defined.js";import{useCatalogClassic as J}from"../../../../client/app/hooks";import{usePageSharedData as L}from"../../../../client/providers/page-data/hooks";import{NON_CONFORMANT as v,OTHER_TEAMS_LABEL as K}from"./utils";function j(S,d){return Object.entries(S).find(([g])=>d.startsWith(g))?.[1]}function ee(S){const{catalogConfig:d,scorecardConfig:g,scorecardInfo:c}=S.pageProps,w=L("catalog"),A=w.some(e=>e.metadata?.publishedAt||e.metadata?.createdAt),T=g.teamMetadataProperty?.label||"Team",f=g.teamMetadataProperty?.property||"team",y=g.teamMetadataProperty?.default,P=w.some(e=>e.metadata?.[f]||e.metadata?.owner),k=p(()=>({...d,filters:[{title:"Scorecard level",property:"scorecardLevel",options:[v,...c.levelNames],type:"checkboxes"},A?{title:"Published date",property:"publishedAt",type:"date-range"}:void 0,P&&!d.filters?.find(e=>e.property===f)?{title:T,property:f,type:"select",...y&&{missingCategoryName:y}}:void 0,...d.filters||[]].filter(G)}),[d,P,A,T,y,f,c.levelNames]),h=L("scorecard"),q=p(()=>w.filter(e=>!!j(h,e.link||"")),[w,h]),{filterTerm:M,setFilterTerm:D,groups:N,filters:E}=J(k,q),u=p(()=>N.flatMap(e=>e.items).map(e=>({api:{...e,publishedAt:e.publishedAt||e.createdAt,team:e[f]||e.owner},scorecard:j(h,e.link)})).filter(e=>e.scorecard),[N,h,f]),C=c.levelNames,z=p(()=>{const e={total:u.length,levels:{}};return[v,...C].forEach(r=>{e.levels[r]=u.filter(s=>s.scorecard?.scorecardLevel===r).length}),e},[u,C]),O=p(()=>C.map(e=>{const r={error:new Set,warn:new Set},s={errors:0,warnings:0};for(const t of u){if(!t.scorecard)continue;const o=t.scorecard.levels[e];if(o){s.errors+=o.errors||0,s.warnings+=o.warnings||0;for(const n of o.problems)r[n.severity]?.add(n.ruleId)}}return{name:e,count:s,uniqueCount:{errors:r.error.size,warnings:r.warn.size}}}),[u,C]),R=p(()=>O.reduce((e,r)=>e+r.count.errors+r.count.warnings,0),[O]),V=p(()=>{const e={};for(const r of c.rules)e[r]={count:0,apis:[],ruleId:r,status:0,apisCount:0,warningApisCount:0,errorApisCount:0,teams:{},levels:c.levelNames.reduce((s,t)=>(s[t]={warnings:0,errors:0},s),{})};for(const r of u){if(!r.scorecard)continue;const{scorecard:s,api:t}=r,o=new Set;for(const n of Object.keys(s?.levels||{})){const l=s.levels[n];for(const m of l?.problems||[]){const a=m.ruleId;if(!o.has(a))if(e[a]=e[a]||{count:0,apis:[],ruleId:a,status:0,apisCount:0,levels:{}},e[a].count++,typeof t.team=="string"&&(e[a].teams[t.team]=(e[a].teams[t.team]||0)+1),e[a].levels[n]=e[a].levels[n]||{warnings:0,errors:0},m.severity==="error"?(e[a].levels[n].errors++,e[a].errorApisCount++):(e[a].levels[n].warnings++,e[a].warningApisCount++),!e[a].apis.find(b=>b.link===t.link))e[a].apis.push({...t,count:1,severity:m.severity,problems:[m]});else{const b=e[a].apis.find(_=>_.link===t.link);b&&(b.count++,b.problems.push(m))}}for(const m of l?.problems||[])o.add(m.ruleId)}}for(const r of Object.values(e))r.apisCount=r.apis.length,r.status=r.apisCount>0?1:0,r.warningApisCount=r.apis.filter(s=>["warn","warning"].includes(s.severity)).length,r.errorApisCount=r.apis.filter(s=>s.severity==="error").length;return Object.values(e).sort((r,s)=>{let t=r.ruleId,o=s.ruleId;return t<o?-1:t>o?1:0})},[u,c.levelNames,c.rules]),[i,B]=H({property:"below",direction:"desc"}),W=U(e=>{i.property===e?B(r=>({...r,direction:r.direction==="asc"?"desc":"asc"})):B({property:e,direction:"asc"})},[i]),{sortedStatsByTeam:x,statsByTeam:I}=p(()=>{const e={},r={};for(const s of u){if(!s.scorecard)continue;const{api:t}=s,o=t.team?String(t.team):y||K;if(!o)continue;e[o]=e[o]||{team:o,below:0,minimum:0,highest:0,levels:{}};const n=r[o]=r[o]||{};for(const l of[v,...c.levelNames]){e[o].levels[l]=e[o].levels[l]||{errors:0,warnings:0,uniqueErrors:0,uniqueWarnings:0,apis:0},n[l]=n[l]||{error:new Set,warn:new Set},e[o].levels[l].errors+=s.scorecard?.levels[l]?.errors||0,e[o].levels[l].warnings+=s.scorecard?.levels[l]?.warnings||0,e[o].levels[l].apis+=s.scorecard.scorecardLevel===l?1:0;for(const m of s.scorecard?.levels[l]?.problems||[])n[l][m.severity]?.add(m.ruleId)}}for(const s of Object.keys(e))for(const t of Object.keys(e[s].levels))e[s].levels[t].uniqueErrors=r[s][t].error.size,e[s].levels[t].uniqueWarnings=r[s][t].warn.size;return{sortedStatsByTeam:Object.values(e).sort((s,t)=>{let o=s[i.property],n=t[i.property];return["team","below","minimum","highest"].includes(i.property)||(o=s.levels[i.property].errors*1e3+s.levels[i.property].warnings,n=t.levels[i.property].errors*1e3+t.levels[i.property].warnings),o<n?i.direction==="asc"?-1:1:o>n?i.direction==="asc"?1:-1:0}),statsByTeam:e}},[u,c.levelNames,i.direction,i.property,y]),F=p(()=>Object.values(I).sort((e,r)=>r.levels[v].apis-e.levels[v].apis).filter(e=>e.levels[v].apis>0),[I]);return{apis:u,summary:z,problemsByStatus:O,totalProblems:R,problemsByRule:V,sortedStatsByTeam:x,handleSortRulesTeams:W,nonConformantByTeam:F,filterTerm:M,setFilterTerm:D,groups:N,filters:E,scorecardInfo:c,catalogConfig:k,hasTeam:P,hasPublishedAt:A,teamLabel:T,teamProperty:f}}export{ee as useData};
1
+ import{useMemo as p,useState as H,useCallback as U}from"react";import{isDefined as G}from"../../../../utils/guards/is-defined.js";import{useCatalogClassic as J}from"../../../../client/app/hooks";import{usePageSharedData as L}from"../../../../client/providers/page-data/hooks";import{NON_CONFORMANT as v,OTHER_TEAMS_LABEL as K}from"./utils";function j(S,d){return Object.entries(S).find(([g])=>d.startsWith(g))?.[1]}function ee(S){const{catalogConfig:d,scorecardConfig:g,scorecardInfo:c}=S.pageProps,w=L("catalog"),A=w.some(e=>e.metadata?.publishedAt||e.metadata?.createdAt),T=g.teamMetadataProperty?.label||"Team",f=g.teamMetadataProperty?.property||"team",y=g.teamMetadataProperty?.default,P=w.some(e=>e.metadata?.[f]||e.metadata?.owner),k=p(()=>({...d,filters:[{title:"Scorecard level",property:"scorecardLevel",options:[v,...c.levelNames],type:"checkboxes"},A?{title:"Published date",property:"publishedAt",type:"date-range"}:void 0,P&&!d.filters?.find(e=>e.property===f)?{title:T,property:f,type:"select",...y&&{missingCategoryName:y}}:void 0,...d.filters||[]].filter(G)}),[d,P,A,T,y,f,c.levelNames]),h=L("scorecard"),q=p(()=>w.filter(e=>!!j(h,e.link||"")),[w,h]),{filterTerm:M,setFilterTerm:D,groups:N,filters:E}=J(k,q),u=p(()=>N.flatMap(e=>e.items).map(e=>({api:{...e,publishedAt:e.publishedAt||e.createdAt,team:e[f]||e.owner},scorecard:j(h,e.link)})).filter(e=>e.scorecard),[N,h,f]),C=c.levelNames,z=p(()=>{const e={total:u.length,levels:{}};return[v,...C].forEach(r=>{e.levels[r]=u.filter(s=>s.scorecard?.scorecardLevel===r).length}),e},[u,C]),O=p(()=>C.map(e=>{const r={error:new Set,warn:new Set},s={errors:0,warnings:0};for(const t of u){if(!t.scorecard)continue;const o=t.scorecard.levels[e];if(o){s.errors+=o.errors||0,s.warnings+=o.warnings||0;for(const n of o.problems)r[n.severity]?.add(n.ruleId)}}return{name:e,count:s,uniqueCount:{errors:r.error.size,warnings:r.warn.size}}}),[u,C]),R=p(()=>O.reduce((e,r)=>e+r.count.errors+r.count.warnings,0),[O]),V=p(()=>{const e={};for(const r of c.rules)e[r]={count:0,apis:[],ruleId:r,status:0,apisCount:0,warningApisCount:0,errorApisCount:0,teams:{},levels:c.levelNames.reduce((s,t)=>(s[t]={warnings:0,errors:0},s),{})};for(const r of u){if(!r.scorecard)continue;const{scorecard:s,api:t}=r,o=new Set;for(const n of Object.keys(s?.levels||{})){const l=s.levels[n];for(const m of l?.problems||[]){const a=m.ruleId;if(!o.has(a))if(e[a]=e[a]||{count:0,apis:[],ruleId:a,status:0,apisCount:0,levels:{}},e[a].count++,typeof t.team=="string"&&(e[a].teams[t.team]=(e[a].teams[t.team]||0)+1),e[a].levels[n]=e[a].levels[n]||{warnings:0,errors:0},m.severity==="error"?(e[a].levels[n].errors++,e[a].errorApisCount++):(e[a].levels[n].warnings++,e[a].warningApisCount++),!e[a].apis.find(b=>b.link===t.link))e[a].apis.push({...t,count:1,severity:m.severity,problems:[m]});else{const b=e[a].apis.find(_=>_.link===t.link);b&&(b.count++,b.problems.push(m))}}for(const m of l?.problems||[])o.add(m.ruleId)}}for(const r of Object.values(e))r.apisCount=r.apis.length,r.status=r.apisCount>0?1:0,r.warningApisCount=r.apis.filter(s=>["warn","warning"].includes(s.severity)).length,r.errorApisCount=r.apis.filter(s=>s.severity==="error").length;return Object.values(e).sort((r,s)=>{const t=r.ruleId,o=s.ruleId;return t<o?-1:t>o?1:0})},[u,c.levelNames,c.rules]),[i,B]=H({property:"below",direction:"desc"}),W=U(e=>{i.property===e?B(r=>({...r,direction:r.direction==="asc"?"desc":"asc"})):B({property:e,direction:"asc"})},[i]),{sortedStatsByTeam:x,statsByTeam:I}=p(()=>{const e={},r={};for(const s of u){if(!s.scorecard)continue;const{api:t}=s,o=t.team?String(t.team):y||K;if(!o)continue;e[o]=e[o]||{team:o,below:0,minimum:0,highest:0,levels:{}};const n=r[o]=r[o]||{};for(const l of[v,...c.levelNames]){e[o].levels[l]=e[o].levels[l]||{errors:0,warnings:0,uniqueErrors:0,uniqueWarnings:0,apis:0},n[l]=n[l]||{error:new Set,warn:new Set},e[o].levels[l].errors+=s.scorecard?.levels[l]?.errors||0,e[o].levels[l].warnings+=s.scorecard?.levels[l]?.warnings||0,e[o].levels[l].apis+=s.scorecard.scorecardLevel===l?1:0;for(const m of s.scorecard?.levels[l]?.problems||[])n[l][m.severity]?.add(m.ruleId)}}for(const s of Object.keys(e))for(const t of Object.keys(e[s].levels))e[s].levels[t].uniqueErrors=r[s][t].error.size,e[s].levels[t].uniqueWarnings=r[s][t].warn.size;return{sortedStatsByTeam:Object.values(e).sort((s,t)=>{let o=s[i.property],n=t[i.property];return["team","below","minimum","highest"].includes(i.property)||(o=s.levels[i.property].errors*1e3+s.levels[i.property].warnings,n=t.levels[i.property].errors*1e3+t.levels[i.property].warnings),o<n?i.direction==="asc"?-1:1:o>n?i.direction==="asc"?1:-1:0}),statsByTeam:e}},[u,c.levelNames,i.direction,i.property,y]),F=p(()=>Object.values(I).sort((e,r)=>r.levels[v].apis-e.levels[v].apis).filter(e=>e.levels[v].apis>0),[I]);return{apis:u,summary:z,problemsByStatus:O,totalProblems:R,problemsByRule:V,sortedStatsByTeam:x,handleSortRulesTeams:W,nonConformantByTeam:F,filterTerm:M,setFilterTerm:D,groups:N,filters:E,scorecardInfo:c,catalogConfig:k,hasTeam:P,hasPublishedAt:A,teamLabel:T,teamProperty:f}}export{ee as useData};