@redocly/redoc-reef 0.136.0-next.5 → 0.136.0-next.6

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 (62) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/client/app/Sidebar/helpers/hide-empty-separators.d.ts +13 -0
  3. package/dist/client/app/Sidebar/helpers/hide-empty-separators.js +1 -0
  4. package/dist/client/app/Sidebar/utils.js +1 -1
  5. package/dist/client/app/utils/same-resolved-route.d.ts +5 -0
  6. package/dist/client/app/utils/same-resolved-route.js +1 -0
  7. package/dist/client/providers/post-message/PostMessageProvider.js +1 -1
  8. package/dist/constants/common.d.ts +10 -1
  9. package/dist/constants/common.js +1 -1
  10. package/dist/server/config/env-schemas/feature-flags.d.ts +1 -1
  11. package/dist/server/constants/entitlements.js +1 -1
  12. package/dist/server/plugins/mcp/auth/auth-handlers.d.ts +1 -0
  13. package/dist/server/plugins/mcp/auth/auth-handlers.js +1 -1
  14. package/dist/server/plugins/mcp/gateway-mcp/elicitation.d.ts +3 -1
  15. package/dist/server/plugins/mcp/gateway-mcp/elicitation.js +2 -2
  16. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-bridge.d.ts +4 -2
  17. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-bridge.js +1 -1
  18. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-errors.d.ts +11 -0
  19. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-errors.js +1 -0
  20. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-telemetry.d.ts +13 -0
  21. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-telemetry.js +1 -0
  22. package/dist/server/plugins/mcp/gateway-mcp/fetch/redact.d.ts +4 -0
  23. package/dist/server/plugins/mcp/gateway-mcp/fetch/redact.js +1 -0
  24. package/dist/server/plugins/mcp/gateway-mcp/fetch/ssrf-blocklist.d.ts +12 -0
  25. package/dist/server/plugins/mcp/gateway-mcp/fetch/ssrf-blocklist.js +1 -1
  26. package/dist/server/plugins/mcp/gateway-mcp/fetch/ssrf-guard.js +1 -1
  27. package/dist/server/plugins/mcp/gateway-mcp/fetch/ssrf-safe-lookup.d.ts +3 -1
  28. package/dist/server/plugins/mcp/gateway-mcp/fetch/ssrf-safe-lookup.js +1 -1
  29. package/dist/server/plugins/mcp/gateway-mcp/fetch-capability.js +1 -1
  30. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/apply-edge-rate-limit.d.ts +3 -0
  31. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/apply-edge-rate-limit.js +1 -0
  32. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/edge-limiter.d.ts +16 -0
  33. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/edge-limiter.js +1 -0
  34. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/ip-key.d.ts +2 -0
  35. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/ip-key.js +1 -0
  36. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/limits.d.ts +7 -0
  37. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/limits.js +1 -0
  38. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/window-counter.d.ts +15 -0
  39. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/window-counter.js +1 -0
  40. package/dist/server/plugins/mcp/gateway-mcp/secret-store.d.ts +7 -7
  41. package/dist/server/plugins/mcp/gateway-mcp/secret-store.js +1 -1
  42. package/dist/server/plugins/mcp/handlers/docs-mcp-handler.js +1 -1
  43. package/dist/server/plugins/mcp/handlers/errors.js +1 -1
  44. package/dist/server/plugins/mcp/handlers/handle-mcp-request.js +1 -1
  45. package/dist/server/plugins/mcp/handlers/mcp-credentials-handler.js +24 -24
  46. package/dist/server/plugins/mcp/index.js +1 -1
  47. package/dist/server/plugins/mcp/servers/docs-server.d.ts +3 -1
  48. package/dist/server/plugins/mcp/servers/docs-server.js +1 -1
  49. package/dist/server/plugins/mcp/servers/mcp-server.d.ts +2 -0
  50. package/dist/server/plugins/mcp/servers/mcp-server.js +1 -1
  51. package/dist/server/plugins/mcp/servers/session-manager.js +1 -1
  52. package/dist/server/plugins/mcp/types.d.ts +3 -0
  53. package/dist/server/plugins/mcp/utils/mode.js +1 -1
  54. package/dist/server/plugins/mcp/utils/telemetry-attributes.d.ts +8 -0
  55. package/dist/server/plugins/mcp/utils/telemetry-attributes.js +1 -0
  56. package/dist/server/plugins/mcp/workers/mcp-tool-telemetry.js +1 -1
  57. package/dist/server/plugins/openapi-docs/utils.js +1 -1
  58. package/dist/server/utils/rbac.d.ts +4 -0
  59. package/dist/server/utils/rbac.js +1 -1
  60. package/dist/server/workers/worker-pool.js +1 -1
  61. package/dist/types/entitlements.d.ts +3 -0
  62. package/package.json +7 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @redocly/redoc-reef
2
2
 
3
+ ## 0.136.0-next.6
4
+
5
+ ### Minor Changes
6
+
7
+ - 8c1feb665fa: Added team-based access control for the MCP server using the `rbac.features.mcp` configuration to restrict access per team, similar to how `rbac.features.aiSearch` controls AI search access.
8
+
9
+ ### Patch Changes
10
+
11
+ - ee1f316d325: Fixed an issue where mock server requests in hosted projects failed with a 500 error due to restricted API access.
12
+ - 5b0ce5ffa64: Fixed editor-to-Webview navigation for API operations rendered as anchors on a single page
13
+ - 696e6673957: Fixed an issue on iOS devices where the on-screen keyboard caused the search and AI Assistant's inputs to overflow the screen making the send button inaccessible.
14
+ - 1ba0268431e: Fixed an issue where callbacks and other collapsible panels in API docs could not be expanded.
15
+ - 8e9a1f74be4: Fixed an issue where multiple adjacent separators in the sidebar rendered only the last one.
16
+ - Updated dependencies [696e6673957]
17
+ - Updated dependencies [1ba0268431e]
18
+ - Updated dependencies [8e9a1f74be4]
19
+ - @redocly/theme@0.68.0-next.4
20
+ - @redocly/openapi-docs@3.24.0-next.6
21
+ - @redocly/asyncapi-docs@1.13.0-next.6
22
+ - @redocly/graphql-docs@1.13.0-next.6
23
+ - @redocly/portal-plugin-mock-server@0.21.0-next.6
24
+
3
25
  ## 0.136.0-next.5
4
26
 
5
27
  ### Minor Changes
@@ -0,0 +1,13 @@
1
+ import type { ItemState } from '@redocly/theme/core/types';
2
+ /**
3
+ * Removes separators marked with `hideWhenEmpty` (set by OpenAPI sidebar
4
+ * generation) whose section is empty — i.e. the separator has no non-separator
5
+ * sibling before the next separator or the end of the array. RBAC filtering and
6
+ * empty-group pruning already ran server-side, so an empty section here means
7
+ * the item immediately after the separator is another separator or absent.
8
+ *
9
+ * Authored separators carry no `hideWhenEmpty` flag and are always kept,
10
+ * including deliberate consecutive separators.
11
+ */
12
+ export declare function hideEmptyFlaggedSeparators(items: ItemState[]): ItemState[];
13
+ //# sourceMappingURL=hide-empty-separators.d.ts.map
@@ -0,0 +1 @@
1
+ const n=e=>e.type==="separator";function o(e){return e.filter((r,a)=>{if(!n(r)||!r.hideWhenEmpty)return!0;const t=e[a+1];return t!==void 0&&!n(t)})}export{o as hideEmptyFlaggedSeparators};
@@ -1 +1 @@
1
- import{withPathPrefix as p}from"@redocly/theme/core/utils";import{removeTrailingSlash as a}from"../../../utils/url/remove-trailing-slash";import{isAbsoluteUrl as c}from"../../../utils/url/is-absolute-url";function P(t,n,r=""){return t.map(e=>{const s=m(e,r,n),o=u(e,r,n);return{...e,items:P(e.items||[],n,r),active:s&&!o,hasActiveSubItem:o}}).filter(Boolean)}function m(t,n="",r){if(!t.link||c(t.link))return!1;const[e,s]=t.link.split("#"),o=r.pathname,i=k(o,e),h=decodeURIComponent(a(p(i))),l=decodeURIComponent(a(o));if(n)return h===a(n)||s===n;if(s){const f=r.hash?r.hash.substring(1):"";return s===f}else return h===l}function u(t,n="",r){return(t.items||[]).some(s=>m(s,n,r)||u(s,n,r))}function k(t,n){const r=new URL(t,"http://localhost");return new URL(n,r.href).pathname}export{P as mapItemsPropsToState};
1
+ import{withPathPrefix as f}from"@redocly/theme/core/utils";import{removeTrailingSlash as m}from"../../../utils/url/remove-trailing-slash";import{isAbsoluteUrl as c}from"../../../utils/url/is-absolute-url";import{hideEmptyFlaggedSeparators as P}from"./helpers/hide-empty-separators";function k(n,r,t=""){const s=n.map(e=>{const o=i(e,t,r),a=l(e,t,r);return{...e,items:k(e.items||[],r,t),active:o&&!a,hasActiveSubItem:a}}).filter(Boolean);return P(s)}function i(n,r="",t){if(!n.link||c(n.link))return!1;const[s,e]=n.link.split("#"),o=t.pathname,a=U(o,s),h=decodeURIComponent(m(f(a))),u=decodeURIComponent(m(o));if(r)return h===m(r)||e===r;if(e){const p=t.hash?t.hash.substring(1):"";return e===p}else return h===u}function l(n,r="",t){return(n.items||[]).some(e=>i(e,r,t)||l(e,r,t))}function U(n,r){const t=new URL(n,"http://localhost");return new URL(r,t.href).pathname}export{k as mapItemsPropsToState};
@@ -0,0 +1,5 @@
1
+ export declare function isSameResolvedRoute(resolvedRoute: string, location: {
2
+ pathname: string;
3
+ hash: string;
4
+ }, isExplicitUrl: boolean): boolean;
5
+ //# sourceMappingURL=same-resolved-route.d.ts.map
@@ -0,0 +1 @@
1
+ import{normalizeRouteSlug as n}from"../../../utils/path/normalize-route-slug";function r(m,e,o){const[h,s]=m.split("#"),t=n(h)===n(e.pathname),a=(s??"")===e.hash.substring(1);return o?t&&a:t&&(!s||a)}export{r as isSameResolvedRoute};
@@ -1 +1 @@
1
- import s,{createContext as E,useEffect as U}from"react";import{useLocation as L,useNavigate as x}from"react-router-dom";import{useColorSwitcher as O}from"@redocly/theme/core/hooks";import{withPathPrefix as T}from"@redocly/theme/core/utils";import{ServerRoutes as z}from"../../../constants/api";import{removeTrailingSlash as D}from"../../../utils/url/remove-trailing-slash";import{normalizeRouteSlug as P}from"../../../utils/path/normalize-route-slug";import{loadAndNavigate as I}from"../../app/utils/loadAndNavigate";import{resolveRouteBySlug as N}from"../../app/utils/resolveRouteBySlug";import{syncScrollWithEditor as A}from"../../app/utils/syncScrollWithEditor";import{PostMessageConnection as H}from"./PostMessageConnection";import{isHostnameOnDomain as i}from"../../app/utils/isHostnameOnDomain";import{usePageData as _}from"../../app/hooks/usePageData";const B=E({sendMessage:()=>{}});function ae({children:l,enabled:t}){const R=x(),n=L(),c=s.useRef(null),{switchColorMode:h}=O(),p=_()?.templateId,u=s.useCallback(e=>{c.current?.send(e)},[]),f=s.useCallback(async e=>{console.info("[PM] connection initialized."),c.current=new H(e.source,e.origin);const a=await N(n.pathname+n.hash,p).catch(o=>{console.error("Initial route resolve failed with error: ",o.message)});u({type:"initialized",initialRoute:a})},[u,n.pathname,n.hash,p]),g=s.useCallback(async e=>{const{path:a="/",line:o=1,url:m}=e;let r=m;if(!m)try{const d=await fetch(T(z.RESOLVE_ROUTE_BY_PATH),{method:"POST",body:JSON.stringify(a+(o?`:${o}`:""))}),{route:S}=await d.json();r=S?.slug??S?.url??void 0}catch(d){console.error(d)}if(!r)return;r=r==="/"?"/":D(r);const[v,C]=r.split("#"),y=P(v)===P(n.pathname),M=(C??"")===n.hash.substring(1);(m?y&&M:y)||(console.info("[PM] Update route to: ",r),await I({navigate:R,to:r,origin:"pm"}))},[n]),w=s.useCallback(async e=>{const{action:a}=e;switch(a){case"back":window.history.back();break;case"forward":window.history.forward();break;case"reload":window.location.reload();break}},[]),b=s.useCallback(e=>{window.scrollTo({top:e.position.absolute,behavior:"instant"})},[]),k=s.useCallback(e=>{h(e.colorSchema)},[h]);return U(()=>{if(!t)return;const e=async a=>{if(!j(a.origin))return;const o=a.data;switch(o.type){case"initialize":f(a);break;case"route-update":if(!c.current)return;g(o);break;case"location-update":if(!c.current)return;w(o);break;case"scroll-update":if(!c.current)return;b(o);break;case"editor-scroll-update":if(!c.current)return;A(o.percentScrolled,o.sequenceNumber);break;case"color-schema-update":if(!c.current)return;k(o);break}};return window.addEventListener("message",e),()=>{window.removeEventListener("message",e)}},[t,f,g,w,b,k]),s.createElement(B.Provider,{value:{sendMessage:u}},l)}function j(l){const t=new URL(l);return i(t.hostname,"localhost")||i(t.hostname,"127.0.0.1")||i(t.hostname,"blueharvest.cloud")||i(t.hostname,"bhstage.cloud")||i(t.hostname,"cloud.redocly.com")||i(t.hostname,"cloud.eu.redocly.com")||i(t.hostname,"cba.au.redocly.com")}export{B as PostMessageContext,ae as PostMessageProvider};
1
+ import n,{createContext as v,useEffect as P}from"react";import{useLocation as S,useNavigate as C}from"react-router-dom";import{useColorSwitcher as M}from"@redocly/theme/core/hooks";import{withPathPrefix as E}from"@redocly/theme/core/utils";import{ServerRoutes as U}from"../../../constants/api";import{removeTrailingSlash as L}from"../../../utils/url/remove-trailing-slash";import{loadAndNavigate as x}from"../../app/utils/loadAndNavigate";import{resolveRouteBySlug as O}from"../../app/utils/resolveRouteBySlug";import{isSameResolvedRoute as T}from"../../app/utils/same-resolved-route";import{syncScrollWithEditor as D}from"../../app/utils/syncScrollWithEditor";import{PostMessageConnection as I}from"./PostMessageConnection";import{isHostnameOnDomain as c}from"../../app/utils/isHostnameOnDomain";import{usePageData as N}from"../../app/hooks/usePageData";const z=v({sendMessage:()=>{}});function Z({children:l,enabled:t}){const R=C(),i=S(),s=n.useRef(null),{switchColorMode:h}=M(),p=N()?.templateId,u=n.useCallback(e=>{s.current?.send(e)},[]),f=n.useCallback(async e=>{console.info("[PM] connection initialized."),s.current=new I(e.source,e.origin);const a=await O(i.pathname+i.hash,p).catch(o=>{console.error("Initial route resolve failed with error: ",o.message)});u({type:"initialized",initialRoute:a})},[u,i.pathname,i.hash,p]),w=n.useCallback(async e=>{const{path:a="/",line:o=1,url:d}=e;let r=d;if(!d)try{const m=await fetch(E(U.RESOLVE_ROUTE_BY_PATH),{method:"POST",body:JSON.stringify(a+(o?`:${o}`:""))}),{route:y}=await m.json();r=y?.slug??y?.url??void 0}catch(m){console.error(m)}if(!r)return;r=r==="/"?"/":L(r),T(r,i,!!d)||(console.info("[PM] Update route to: ",r),await x({navigate:R,to:r,origin:"pm"}))},[i]),g=n.useCallback(async e=>{const{action:a}=e;switch(a){case"back":window.history.back();break;case"forward":window.history.forward();break;case"reload":window.location.reload();break}},[]),b=n.useCallback(e=>{window.scrollTo({top:e.position.absolute,behavior:"instant"})},[]),k=n.useCallback(e=>{h(e.colorSchema)},[h]);return P(()=>{if(!t)return;const e=async a=>{if(!A(a.origin))return;const o=a.data;switch(o.type){case"initialize":f(a);break;case"route-update":if(!s.current)return;w(o);break;case"location-update":if(!s.current)return;g(o);break;case"scroll-update":if(!s.current)return;b(o);break;case"editor-scroll-update":if(!s.current)return;D(o.percentScrolled,o.sequenceNumber);break;case"color-schema-update":if(!s.current)return;k(o);break}};return window.addEventListener("message",e),()=>{window.removeEventListener("message",e)}},[t,f,w,g,b,k]),n.createElement(z.Provider,{value:{sendMessage:u}},l)}function A(l){const t=new URL(l);return c(t.hostname,"localhost")||c(t.hostname,"127.0.0.1")||c(t.hostname,"blueharvest.cloud")||c(t.hostname,"bhstage.cloud")||c(t.hostname,"cloud.redocly.com")||c(t.hostname,"cloud.eu.redocly.com")||c(t.hostname,"cba.au.redocly.com")}export{z as PostMessageContext,Z as PostMessageProvider};
@@ -51,7 +51,8 @@ export declare enum FEEDBACK_TYPES {
51
51
  SCALE = "scale"
52
52
  }
53
53
  export declare enum RbacFeatures {
54
- AI_SEARCH = "aiSearch"
54
+ AI_SEARCH = "aiSearch",
55
+ MCP = "mcp"
55
56
  }
56
57
  export declare const DEV_LOGIN_SLUG = "/login";
57
58
  export declare const INVITE_SLUG = "/invite";
@@ -106,6 +107,14 @@ export declare const FEATURE: {
106
107
  readonly mcp: 'mcp';
107
108
  readonly mcpGateway: 'mcpGateway';
108
109
  readonly mcpMode: 'mcpMode';
110
+ readonly gatewayFetchRatePerMin: 'gatewayFetchRatePerMin';
111
+ readonly gatewayFetchQuotaPerDay: 'gatewayFetchQuotaPerDay';
112
+ readonly gatewayFetchBudgetPerExec: 'gatewayFetchBudgetPerExec';
109
113
  readonly banner: 'banner';
110
114
  };
115
+ export declare const GATEWAY_FETCH_LIMIT_DEFAULTS: {
116
+ readonly ratePerMin: 60;
117
+ readonly quotaPerDay: 2000;
118
+ readonly budgetPerExec: 25;
119
+ };
111
120
  //# sourceMappingURL=common.d.ts.map
@@ -1 +1 @@
1
- const a={AUTHORIZATION:"authorization",IDP_ID_TOKEN:"idp_id_token",IDP_ACCESS_TOKEN:"idp_access_token",IDP_REFRESH_TOKEN:"idp_refresh_token",FEDERATED_ACCESS_TOKEN:"federated_access_token",FEDERATED_ID_TOKEN:"federated_id_token"},n="post_logout",_="openapi_docs",E="asyncapi_docs",p="graphql_docs",i="/_spec-gql",A="markdoc",S="tags",c="*",O={[c]:"read"},d=["openid","email"],L=1440*60,I=720*60*60,T="anonymous",R="authenticated",l="**",C="Reunite",x="translations.yaml",P="redocly.yaml",D="@redocly/theme",N="@theme",m="@skills",U="SKILL.md",u="/.well-known/agent-skills",g="/.well-known/agent-card.json",M="/a2a",h="/.well-known/mcp/server-card.json",F="/app-data.json",k="/eject-component";var t;(function(o){o.BUILD="build",o.DEVELOP="develop",o.PREPARE="prepare"})(t||(t={}));var e;(function(o){o.RATING="rating",o.SENTIMENT="sentiment",o.COMMENT="comment",o.MOOD="mood",o.PROBLEM="problem",o.SCALE="scale"})(e||(e={}));var r;(function(o){o.AI_SEARCH="aiSearch"})(r||(r={}));const b="/login",G="/invite",v={AUTH0_PASS:"/auth/auth0-pass"},H={NotExist:"ENOENT"},y="default_locale",K="sidebar-",w="PUBLIC_",B="branch.",f=["BROKEN_LINK","MARKDOC","RESOLVE"],V=["logo","navbar","products","footer","sidebar","scripts","links","feedback","search","aiAssistant","colorMode","palette","navigation","codeSnippet","markdown","openapi","graphql","analytics","userMenu","versionPicker","breadcrumbs","catalog","entitiesCatalog","scorecard","scorecards","scorecardClassic","mcp","banner"],s="redocly_category",j="redocly_product",q="redocly_version",X="redocly_teams",Q=s,W={asyncapi:"asyncapi",apiFunctions:"apiFunctions",reactPages:"reactPages",catalog:"catalog",catalogClassic:"catalogClassic",lint:"lint",scorecard:"scorecard",scorecards:"scorecards",l10n:"l10n",openapi:"openapi",graphql:"graphql",markdown:"markdown",devOnboarding:"devOnboarding",seo:"seo",redirects:"redirects",customPlugins:"customPlugins",themeEjecting:"themeEjecting",products:"products",breadcrumbs:"breadcrumbs",mockServer:"mockServer",sso:"sso",rbac:"rbac",analytics:"analytics",removeAttribution:"removeAttribution",advancedSearch:"advancedSearch",soap:"soap",ssoDirect:"ssoDirect",codeWalkthrough:"codeWalkthrough",aiSearchLimit:"aiSearchLimit",kvStorageLimit:"kvStorageLimit",mcp:"mcp",mcpGateway:"mcpGateway",mcpMode:"mcpMode",banner:"banner"};export{M as A2A_SLUG,g as AGENT_CARD_SLUG,u as AGENT_SKILLS_BASE,E as ASYNC_API_DOCS_TEMPLATE_ID,a as AuthCookieNames,B as BRANCH_ENV_PREFIX,P as CONFIG_FILE_NAME,A as CUSTOM_MARKDOC_OPTIONS_PATH,S as CUSTOM_MARKDOC_TAGS_PATH,T as DEFAULT_ANONYMOUS_VISITOR_TEAM,R as DEFAULT_AUTHENTICATED_TEAM,L as DEFAULT_COOKIE_EXPIRATION,y as DEFAULT_LOCALE_PLACEHOLDER,l as DEFAULT_RBAC_SCOPE,C as DEFAULT_SSO_IDP_TITLE,D as DEFAULT_THEME_NAME,b as DEV_LOGIN_SLUG,k as EJECT_COMPONENT_URL,v as ExternalRoutes,W as FEATURE,e as FEEDBACK_TYPES,H as FsErrors,F as GLOBAL_DATA_URL,i as GRAPHQL_SPEC_SLUG,p as GRAPHQL_TEMPLATE_ID,G as INVITE_SLUG,f as MARKDOC_ERROR_TYPES,h as MCP_SERVER_CARD_SLUG,_ as OPENAPI_DOCS_TEMPLATE_ID,I as POST_LOGOUT_COOKIE_EXPIRATION,n as POST_LOGOUT_COOKIE_NAME,w as PUBLIC_ENV_PREFIX,O as PUBLIC_RBAC_SCOPE_ITEM,c as RBAC_ALL_OTHER_TEAMS,d as REQUIRED_OIDC_SCOPES,r as RbacFeatures,s as SEARCH_CATEGORY_FIELD,Q as SEARCH_GROUP_FACET_FIELD,j as SEARCH_PRODUCT_FIELD,X as SEARCH_RBAC_FIELD,q as SEARCH_VERSION_FIELD,K as SIDEBAR_PREFIX,x as TRANSLATIONS_FILE_NAME,V as UI_ACCESSIBLE_CONFIG_PROPS,m as USER_SKILLS_ALIAS,U as USER_SKILL_FILENAME,N as USER_THEME_ALIAS,t as cliCommandNames};
1
+ const s={AUTHORIZATION:"authorization",IDP_ID_TOKEN:"idp_id_token",IDP_ACCESS_TOKEN:"idp_access_token",IDP_REFRESH_TOKEN:"idp_refresh_token",FEDERATED_ACCESS_TOKEN:"federated_access_token",FEDERATED_ID_TOKEN:"federated_id_token"},n="post_logout",_="openapi_docs",E="asyncapi_docs",p="graphql_docs",i="/_spec-gql",A="markdoc",S="tags",c="*",d={[c]:"read"},L=["openid","email"],O=1440*60,T=720*60*60,I="anonymous",R="authenticated",l="**",x="Reunite",C="translations.yaml",P="redocly.yaml",D="@redocly/theme",N="@theme",g="@skills",m="SKILL.md",u="/.well-known/agent-skills",M="/.well-known/agent-card.json",U="/a2a",F="/.well-known/mcp/server-card.json",h="/app-data.json",y="/eject-component";var t;(function(e){e.BUILD="build",e.DEVELOP="develop",e.PREPARE="prepare"})(t||(t={}));var o;(function(e){e.RATING="rating",e.SENTIMENT="sentiment",e.COMMENT="comment",e.MOOD="mood",e.PROBLEM="problem",e.SCALE="scale"})(o||(o={}));var r;(function(e){e.AI_SEARCH="aiSearch",e.MCP="mcp"})(r||(r={}));const k="/login",b="/invite",G={AUTH0_PASS:"/auth/auth0-pass"},v={NotExist:"ENOENT"},H="default_locale",w="sidebar-",K="PUBLIC_",B="branch.",f=["BROKEN_LINK","MARKDOC","RESOLVE"],V=["logo","navbar","products","footer","sidebar","scripts","links","feedback","search","aiAssistant","colorMode","palette","navigation","codeSnippet","markdown","openapi","graphql","analytics","userMenu","versionPicker","breadcrumbs","catalog","entitiesCatalog","scorecard","scorecards","scorecardClassic","mcp","banner"],a="redocly_category",j="redocly_product",q="redocly_version",Q="redocly_teams",X=a,W={asyncapi:"asyncapi",apiFunctions:"apiFunctions",reactPages:"reactPages",catalog:"catalog",catalogClassic:"catalogClassic",lint:"lint",scorecard:"scorecard",scorecards:"scorecards",l10n:"l10n",openapi:"openapi",graphql:"graphql",markdown:"markdown",devOnboarding:"devOnboarding",seo:"seo",redirects:"redirects",customPlugins:"customPlugins",themeEjecting:"themeEjecting",products:"products",breadcrumbs:"breadcrumbs",mockServer:"mockServer",sso:"sso",rbac:"rbac",analytics:"analytics",removeAttribution:"removeAttribution",advancedSearch:"advancedSearch",soap:"soap",ssoDirect:"ssoDirect",codeWalkthrough:"codeWalkthrough",aiSearchLimit:"aiSearchLimit",kvStorageLimit:"kvStorageLimit",mcp:"mcp",mcpGateway:"mcpGateway",mcpMode:"mcpMode",gatewayFetchRatePerMin:"gatewayFetchRatePerMin",gatewayFetchQuotaPerDay:"gatewayFetchQuotaPerDay",gatewayFetchBudgetPerExec:"gatewayFetchBudgetPerExec",banner:"banner"},z={ratePerMin:60,quotaPerDay:2e3,budgetPerExec:25};export{U as A2A_SLUG,M as AGENT_CARD_SLUG,u as AGENT_SKILLS_BASE,E as ASYNC_API_DOCS_TEMPLATE_ID,s as AuthCookieNames,B as BRANCH_ENV_PREFIX,P as CONFIG_FILE_NAME,A as CUSTOM_MARKDOC_OPTIONS_PATH,S as CUSTOM_MARKDOC_TAGS_PATH,I as DEFAULT_ANONYMOUS_VISITOR_TEAM,R as DEFAULT_AUTHENTICATED_TEAM,O as DEFAULT_COOKIE_EXPIRATION,H as DEFAULT_LOCALE_PLACEHOLDER,l as DEFAULT_RBAC_SCOPE,x as DEFAULT_SSO_IDP_TITLE,D as DEFAULT_THEME_NAME,k as DEV_LOGIN_SLUG,y as EJECT_COMPONENT_URL,G as ExternalRoutes,W as FEATURE,o as FEEDBACK_TYPES,v as FsErrors,z as GATEWAY_FETCH_LIMIT_DEFAULTS,h as GLOBAL_DATA_URL,i as GRAPHQL_SPEC_SLUG,p as GRAPHQL_TEMPLATE_ID,b as INVITE_SLUG,f as MARKDOC_ERROR_TYPES,F as MCP_SERVER_CARD_SLUG,_ as OPENAPI_DOCS_TEMPLATE_ID,T as POST_LOGOUT_COOKIE_EXPIRATION,n as POST_LOGOUT_COOKIE_NAME,K as PUBLIC_ENV_PREFIX,d as PUBLIC_RBAC_SCOPE_ITEM,c as RBAC_ALL_OTHER_TEAMS,L as REQUIRED_OIDC_SCOPES,r as RbacFeatures,a as SEARCH_CATEGORY_FIELD,X as SEARCH_GROUP_FACET_FIELD,j as SEARCH_PRODUCT_FIELD,Q as SEARCH_RBAC_FIELD,q as SEARCH_VERSION_FIELD,w as SIDEBAR_PREFIX,C as TRANSLATIONS_FILE_NAME,V as UI_ACCESSIBLE_CONFIG_PROPS,g as USER_SKILLS_ALIAS,m as USER_SKILL_FILENAME,N as USER_THEME_ALIAS,t as cliCommandNames};
@@ -17,7 +17,7 @@ export declare const featureFlagsSchema: z.ZodObject<{
17
17
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT: z.ZodOptional<z.ZodString>;
18
18
  /** Escape hatch: disables the Node Permission Model sandbox on API function and MCP tool worker processes (hosted Reunite environments only). */
19
19
  REDOCLY_DISABLE_API_FUNCTION_SANDBOX: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
20
- /** Experimental: prompt for upstream API credentials via URL-mode elicitation. Requires the gateway capability. */
20
+ /** Prompt for upstream API credentials via URL-mode elicitation. Enabled by default wherever the gateway capability is enabled; set to `false` to disable. */
21
21
  REDOCLY_EXP_MCP_ELICITATION_ENABLED: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
22
22
  }, "strip", z.ZodTypeAny, {
23
23
  NEW_CATALOG_ENABLED?: boolean | undefined;
@@ -1 +1 @@
1
- const e={pro:{rbac:!1,sso:!1,analytics:!0,removeAttribution:!1,advancedSearch:!1,ssoDirect:!1,aiSearchLimit:0,kvStorageLimit:.5,mcp:!1,mcpGateway:!1,mcpMode:"tools"},enterprise:{rbac:!0,sso:!0,analytics:!0,removeAttribution:!0,advancedSearch:!0,ssoDirect:!1,aiSearchLimit:750,kvStorageLimit:5,mcp:!0,mcpGateway:!0,mcpMode:"code"}},t=["@redocly/portal-plugin-async-api/plugin.js","@redocly/portal-plugin-mock-server/plugin.js","@redocly/portal-plugin-soap-api/plugin.js"],a={"code-walkthrough":"codeWalkthrough",step:"codeWalkthrough",toggle:"codeWalkthrough",input:"codeWalkthrough"};export{a as GATED_MARKDOC_TAGS,t as GATED_PLUGINS,e as PLAN_GATES_DEFAULTS};
1
+ import{GATEWAY_FETCH_LIMIT_DEFAULTS as e}from"../../constants/common.js";const a={pro:{rbac:!1,sso:!1,analytics:!0,removeAttribution:!1,advancedSearch:!1,ssoDirect:!1,aiSearchLimit:0,kvStorageLimit:.5,mcp:!1,mcpGateway:!1,mcpMode:"tools",gatewayFetchRatePerMin:e.ratePerMin,gatewayFetchQuotaPerDay:e.quotaPerDay,gatewayFetchBudgetPerExec:e.budgetPerExec},enterprise:{rbac:!0,sso:!0,analytics:!0,removeAttribution:!0,advancedSearch:!0,ssoDirect:!1,aiSearchLimit:750,kvStorageLimit:5,mcp:!0,mcpGateway:!0,mcpMode:"code",gatewayFetchRatePerMin:e.ratePerMin,gatewayFetchQuotaPerDay:e.quotaPerDay,gatewayFetchBudgetPerExec:e.budgetPerExec}},r=["@redocly/portal-plugin-async-api/plugin.js","@redocly/portal-plugin-mock-server/plugin.js","@redocly/portal-plugin-soap-api/plugin.js"],o={"code-walkthrough":"codeWalkthrough",step:"codeWalkthrough",toggle:"codeWalkthrough",input:"codeWalkthrough"};export{o as GATED_MARKDOC_TAGS,r as GATED_PLUGINS,a as PLAN_GATES_DEFAULTS};
@@ -9,5 +9,6 @@ export declare function handleMcpAuth(request: Request, context: ApiFunctionsCon
9
9
  accessToken?: string;
10
10
  }>;
11
11
  export declare function constructUnauthorizedResponse(baseUrl: string): Response;
12
+ export declare function constructForbiddenResponse(): Response;
12
13
  export declare function constructInvalidTokenResponse(): Response;
13
14
  //# sourceMappingURL=auth-handlers.d.ts.map
@@ -1 +1 @@
1
- import{extractTokenFromAuthHeader as u}from"../../../plugins/mcp/utils/jwt.js";import{getUserParamsFromCookies as d}from"../../../web-server/auth.js";import{ServerRoutes as l}from"../../../../constants/api.js";import{DEFAULT_ANONYMOUS_VISITOR_TEAM as i,RBAC_ALL_OTHER_TEAMS as a}from"../../../../constants/common.js";import{withPathPrefix as h}from"@redocly/theme/core/utils";function c(e){return!e||typeof e!="object"||Object.keys(e).length===0?!1:!(e[i]&&e[i]!=="none"||e[a]&&e[a]!=="none")}function f(e,t){if(!t||Object.keys(t).length===0)return e;const r=t.content;if(r&&Object.keys(r).length>0&&Object.values(r).some(c))return!0;const o=t.teamFoldersBaseRoles;return c(o)?!0:e}function O(e){const t=e.getConfig().access;return f(!!t?.requiresLogin,t?.rbac||{})}async function _(e,t){const r=e.headers.get("Authorization");if(!r)return{isAuthenticated:!1};const o=u(r),s=t?.config?.ssoDirect||{},n=o?await d(s,{authorization:o}):{};return o&&n&&n.isAuthenticated?{isAuthenticated:!0,isTokenValid:!0,currentUser:{teams:n.teams||[],email:n.email||"",claims:n,isAuthenticated:!0,idpAccessToken:n.idpAccessToken||void 0,idpId:n.idpId||void 0},accessToken:o}:{isAuthenticated:!1,isTokenValid:!1}}function g(e){return e=e.replace(/^http:\/\//,"https://"),new Response(JSON.stringify({error:"unauthorized",message:"Authentication required"}),{status:401,headers:{"Content-Type":"application/json","WWW-Authenticate":`Bearer resource_metadata="${e}${l.MCP_OAUTH_PROTECTED_RESOURCE}${h("/mcp")}"`,"Access-Control-Allow-Origin":"*"}})}function x(){return new Response(JSON.stringify({error:"invalid_token",message:"Invalid or expired token"}),{status:401,headers:{"Content-Type":"application/json","WWW-Authenticate":'Bearer error="invalid_token", error_description="Invalid or expired token"',"Access-Control-Allow-Origin":"*"}})}export{x as constructInvalidTokenResponse,g as constructUnauthorizedResponse,_ as handleMcpAuth,O as isMcpContentProtected,f as shouldHandleMcpAuth};
1
+ import{extractTokenFromAuthHeader as u}from"../../../plugins/mcp/utils/jwt.js";import{getUserParamsFromCookies as d}from"../../../web-server/auth.js";import{ServerRoutes as l}from"../../../../constants/api.js";import{DEFAULT_ANONYMOUS_VISITOR_TEAM as a,RBAC_ALL_OTHER_TEAMS as c,RbacFeatures as f}from"../../../../constants/common.js";import{withPathPrefix as p}from"@redocly/theme/core/utils";function i(e){return!e||typeof e!="object"||Object.keys(e).length===0?!1:!(e[a]&&e[a]!=="none"||e[c]&&e[c]!=="none")}function h(e,t){if(!t||Object.keys(t).length===0)return e;const r=t.content;if(r&&Object.keys(r).length>0&&Object.values(r).some(i))return!0;const s=t.teamFoldersBaseRoles;if(i(s))return!0;const o=t.features;return i(o?.[f.MCP])?!0:e}function g(e){const t=e.getConfig().access;return h(!!t?.requiresLogin,t?.rbac||{})}async function C(e,t){const r=e.headers.get("Authorization");if(!r)return{isAuthenticated:!1};const s=u(r),o=t?.config?.ssoDirect||{},n=s?await d(o,{authorization:s}):{};return s&&n&&n.isAuthenticated?{isAuthenticated:!0,isTokenValid:!0,currentUser:{teams:n.teams||[],email:n.email||"",claims:n,isAuthenticated:!0,idpAccessToken:n.idpAccessToken||void 0,idpId:n.idpId||void 0},accessToken:s}:{isAuthenticated:!1,isTokenValid:!1}}function _(e){return e=e.replace(/^http:\/\//,"https://"),new Response(JSON.stringify({error:"unauthorized",message:"Authentication required"}),{status:401,headers:{"Content-Type":"application/json","WWW-Authenticate":`Bearer resource_metadata="${e}${l.MCP_OAUTH_PROTECTED_RESOURCE}${p("/mcp")}"`,"Access-Control-Allow-Origin":"*"}})}function y(){return new Response(JSON.stringify({error:"forbidden",message:"You do not have access to this MCP server"}),{status:403,headers:{"Content-Type":"application/json","Access-Control-Allow-Origin":"*"}})}function x(){return new Response(JSON.stringify({error:"invalid_token",message:"Invalid or expired token"}),{status:401,headers:{"Content-Type":"application/json","WWW-Authenticate":'Bearer error="invalid_token", error_description="Invalid or expired token"',"Access-Control-Allow-Origin":"*"}})}export{y as constructForbiddenResponse,x as constructInvalidTokenResponse,_ as constructUnauthorizedResponse,C as handleMcpAuth,g as isMcpContentProtected,h as shouldHandleMcpAuth};
@@ -1,5 +1,6 @@
1
1
  import type { UpstreamAuthRequiredEvent } from './upstream-auth.js';
2
2
  import type { UpstreamAuthScheme } from './upstream-auth-scheme.js';
3
+ import type { KvService } from '@redocly/config';
3
4
  export declare const ELICITATION_TIMEOUT_MS = 300000;
4
5
  export type SendElicitRequest = (request: object, resultSchema: unknown, options: {
5
6
  timeout: number;
@@ -14,12 +15,13 @@ export type ElicitationOutcome = {
14
15
  } | {
15
16
  status: 'skipped';
16
17
  };
17
- export declare function runUpstreamCredentialElicitation({ sendRequest, clientSupportsElicitation, sessionId, baseUrl, events, auth, }: {
18
+ export declare function runUpstreamCredentialElicitation({ sendRequest, clientSupportsElicitation, sessionId, baseUrl, events, auth, kv, }: {
18
19
  sendRequest: SendElicitRequest;
19
20
  clientSupportsElicitation: boolean;
20
21
  sessionId: string;
21
22
  baseUrl: string;
22
23
  events: UpstreamAuthRequiredEvent[];
23
24
  auth: UpstreamAuthScheme;
25
+ kv: KvService;
24
26
  }): Promise<ElicitationOutcome>;
25
27
  //# sourceMappingURL=elicitation.d.ts.map
@@ -1,2 +1,2 @@
1
- import{ElicitResultSchema as m}from"@redocly/mcp-typescript-sdk/types.js";import{logger as d}from"../../../tools/notifiers/logger.js";import{secretStore as p,ELICITATION_TTL_MS as h}from"./secret-store.js";const f=3e5;async function $({sendRequest:e,clientSupportsElicitation:t,sessionId:r,baseUrl:i,events:n,auth:s}){const a=n[0],l=p.createElicitation({sessionId:r,api:a.api,name:a.name,auth:s}),c=g(i,l);if(!t)return{status:"fallback",text:u(n,c)};try{return(await e({method:"elicitation/create",params:{mode:"url",message:`The "${a.name}" API requires credentials. Open the link to provide them securely; they never pass through the chat.`,url:c,elicitationId:l}},m,{timeout:f})).action==="accept"?{status:"accepted"}:{status:"skipped"}}catch(o){return d.warn(`URL-mode elicitation failed, falling back to markdown link: ${o instanceof Error?o.message:String(o)}`),{status:"fallback",text:u(n,c)}}}function g(e,t){if(!e)return`/mcp/credentials?token=${t}`;try{const r=new URL("/mcp/credentials",e);return r.searchParams.set("token",t),r.toString()}catch{return`${e}/mcp/credentials?token=${t}`}}function u(e,t){const[r,...i]=e,n=[`The upstream API "${r.name}" returned 401 Unauthorized.`,"",`**Action required: show the user the link below and ask them to open it in a browser to provide the API credentials (valid ${Math.round(h/6e4)} minutes):**`,"",t,"","Then call this tool again - the credentials are applied server-side and never appear in the conversation."];if(i.length>0){const s=i.map(a=>`"${a.name}"`).join(", ");n.push("",`The remaining APIs (${s}) will prompt for credentials on subsequent calls.`)}return n.join(`
2
- `)}export{f as ELICITATION_TIMEOUT_MS,$ as runUpstreamCredentialElicitation};
1
+ import{ElicitResultSchema as d}from"@redocly/mcp-typescript-sdk/types.js";import{logger as p}from"../../../tools/notifiers/logger.js";import{secretStore as h,ELICITATION_TTL_MS as f}from"./secret-store.js";const g=3e5;async function $({sendRequest:e,clientSupportsElicitation:t,sessionId:r,baseUrl:a,events:n,auth:o,kv:s}){const c=n[0],u=await h.createElicitation(s,{sessionId:r,api:c.api,name:c.name,auth:o}),l=T(a,u);if(!t)return{status:"fallback",text:m(n,l)};try{return(await e({method:"elicitation/create",params:{mode:"url",message:`The "${c.name}" API requires credentials. Open the link to provide them securely; they never pass through the chat.`,url:l,elicitationId:u}},d,{timeout:g})).action==="accept"?{status:"accepted"}:{status:"skipped"}}catch(i){return p.warn(`URL-mode elicitation failed, falling back to markdown link: ${i instanceof Error?i.message:String(i)}`),{status:"fallback",text:m(n,l)}}}function T(e,t){if(!e)return`/mcp/credentials?token=${t}`;try{const r=new URL("/mcp/credentials",e);return r.searchParams.set("token",t),r.toString()}catch{return`${e}/mcp/credentials?token=${t}`}}function m(e,t){const[r,...a]=e,n=[`The upstream API "${r.name}" returned 401 Unauthorized.`,"",`**Action required: show the user the link below and ask them to open it in a browser to provide the API credentials (valid ${Math.round(f/6e4)} minutes):**`,"",t,"","Then call this tool again - the credentials are applied server-side and never appear in the conversation."];if(a.length>0){const o=a.map(s=>`"${s.name}"`).join(", ");n.push("",`The remaining APIs (${o}) will prompt for credentials on subsequent calls.`)}return n.join(`
2
+ `)}export{g as ELICITATION_TIMEOUT_MS,$ as runUpstreamCredentialElicitation};
@@ -2,7 +2,7 @@ import { Agent, type Dispatcher } from 'undici';
2
2
  import type { SandboxFetch } from '../../codemode/types.js';
3
3
  import type { UpstreamAuthRequiredEvent } from '../upstream-auth.js';
4
4
  import type { StoredSecret } from '../secret-store.js';
5
- export declare const ssrfSafeAgent: Agent;
5
+ export declare function getDefaultAgent(): Agent;
6
6
  export type FetchBridgeApi = {
7
7
  slug: string;
8
8
  name: string;
@@ -18,9 +18,11 @@ type FetchBridgeOptions = {
18
18
  dispatcher?: Dispatcher;
19
19
  timeoutMs?: number;
20
20
  maxResponseBytes?: number;
21
+ maxFetchesPerExec?: number;
22
+ clientIp?: string;
21
23
  };
22
24
  /** Build the host-side `fetch` the gateway sandbox calls. */
23
- export declare function buildFetchBridge({ allowedHosts, apis, authorization, onAuthRequired, fetchImpl, dispatcher, timeoutMs, maxResponseBytes, }: FetchBridgeOptions): SandboxFetch;
25
+ export declare function buildFetchBridge({ allowedHosts, apis, authorization, onAuthRequired, fetchImpl, dispatcher, timeoutMs, maxResponseBytes, maxFetchesPerExec, clientIp, }: FetchBridgeOptions): SandboxFetch;
24
26
  export declare function readCappedBody(response: Response, maxBytes: number): Promise<string>;
25
27
  export {};
26
28
  //# sourceMappingURL=fetch-bridge.d.ts.map
@@ -1 +1 @@
1
- import{Agent as y}from"undici";import{assertUrlAllowed as T}from"./ssrf-guard.js";import{createSsrfSafeLookup as E}from"./ssrf-safe-lookup.js";const A=3e4,L=1024*1024,S=new y({connect:{lookup:E()}});function F({allowedHosts:t,apis:e=[],authorization:r,onAuthRequired:o,fetchImpl:c=fetch,dispatcher:n=S,timeoutMs:d=A,maxResponseBytes:i=L}){const a=[...t,...e.flatMap(l=>l.matchers)];return async l=>{const h=T(l.url,a),m=e.filter(s=>s.matchers.some(f=>f.test(h.hostname))),u=m.length===1?m[0]:void 0,b=v(l.headers,u?.secret,r),g=new AbortController,p=setTimeout(()=>g.abort(),d);try{const s=await c(h,{method:l.method??"GET",headers:b,body:l.body,redirect:"manual",signal:g.signal,dispatcher:n});s.status===401&&u&&o&&o({api:u.slug,name:u.name});const f=await k(s,i);return{status:s.status,statusText:s.statusText,ok:s.ok,headers:_(s.headers),bodyText:f}}finally{clearTimeout(p)}}}async function k(t,e){const r=Number(t.headers.get("content-length"));if(Number.isFinite(r)&&r>e)throw w(e,r);if(!t.body)return"";const o=t.body.getReader(),c=new TextDecoder;let n=0,d="";try{for(;;){const{done:i,value:a}=await o.read();if(i)break;if(n+=a.byteLength,n>e)throw w(e,n);d+=c.decode(a,{stream:!0})}return d+c.decode()}finally{await o.cancel().catch(()=>{})}}function w(t,e){return new Error(`Response is too large (over ${t} bytes, got ${e}); request less data`)}function v(t,e,r){const o={},c=new Set;for(const[d,i]of Object.entries(t??{})){const a=d.toLowerCase();a==="host"||a==="content-length"||(c.add(a),o[d]=i)}const n=e??(r?{header:"Authorization",value:r}:void 0);return n&&!c.has(n.header.toLowerCase())&&(o[n.header]=n.value),o}function _(t){const e={};return t.forEach((r,o)=>{e[o.toLowerCase()]=r}),e}export{F as buildFetchBridge,k as readCappedBody,S as ssrfSafeAgent};
1
+ import{Agent as j}from"undici";import{envConfig as P}from"../../../../config/env-config.js";import{logger as _}from"../../../../tools/notifiers/logger.js";import{assertUrlAllowed as C}from"./ssrf-guard.js";import{createSsrfSafeLookup as W}from"./ssrf-safe-lookup.js";import{SsrfBlockedError as H}from"./ssrf-blocklist.js";import{GatewayFetchBlockedError as U,GatewayFetchSystemError as J,GatewayFetchUserError as L}from"./fetch-errors.js";import{redactHeaders as $,redactSecretsFromString as K}from"./redact.js";import{reportGatewayFetch as x}from"./fetch-telemetry.js";const O="x-forwarded-for",V=3e4,X=1024*1024;let A;function Y(){if(!A){const t=P.isDevelopMode;t&&_.warn("Gateway MCP SSRF guard: loopback upstreams are ALLOWED (develop mode only, never in runtime)."),A=new j({connect:{lookup:W({allowLoopback:t})}})}return A}function he({allowedHosts:t,apis:e=[],authorization:r,onAuthRequired:n,fetchImpl:s=fetch,dispatcher:d=Y(),timeoutMs:c=V,maxResponseBytes:h=X,maxFetchesPerExec:f,clientIp:m}){const k=[...t,...e.flatMap(o=>o.matchers)],B=[r,...e.map(o=>o.secret?.value)].filter(o=>!!o),v=e.map(o=>o.secret?.header).filter(o=>!!o);let D=0;return async o=>{const g=o.method??"GET",T=Date.now();let F,b,E={},M,S;try{if(f!==void 0){const l=Math.max(0,f);if(D+=1,D>l)throw new L(`Outbound fetch budget exceeded (max ${l} fetches per execute); split the work across calls`)}const u=C(o.url,k);F=u.hostname;const y=e.filter(l=>l.matchers.some(N=>N.test(u.hostname))),a=y.length===1?y[0]:void 0;b=a?.slug,E=re(o.headers,a?.secret,r,m);const w=new AbortController;S=setTimeout(()=>w.abort(),c);const i=await s(u,{method:g,headers:E,body:o.body,redirect:"manual",signal:w.signal,dispatcher:d});M=i.status,i.status===401&&a&&n&&n({api:a.slug,name:a.name});const R=await te(i,h);let p=oe(i.headers);if(z(i.status)){p=$(p,v);const{location:l}=p;l&&!q(l,u,k)&&delete p.location}return x({host:u.hostname,method:g,outcome:"success",durationMs:Date.now()-T,api:b,status:i.status,bytes:Buffer.byteLength(R,"utf8")}),{status:i.status,statusText:i.statusText,ok:i.ok,headers:p,bodyText:R}}catch(u){const{outcome:y,userSafe:a}=Q(u),w=F??(a instanceof U?a.host:void 0)??Z(o.url)??"invalid-url";throw x({host:w,method:g,outcome:y,durationMs:Date.now()-T,api:b,status:M}),a||(_.error(`Gateway MCP fetch failed [${g} ${w}]: ${K(ee(u),B)} headers=${JSON.stringify($(E,v))}`),new J("Upstream fetch failed due to an internal error"))}finally{S!==void 0&&clearTimeout(S)}}}function I(t){let e=t;for(let r=0;r<5&&e instanceof Error;r++){if(e instanceof L||e instanceof H)return e;e=e.cause}}function Q(t){const e=I(t);return{outcome:e instanceof U||e instanceof H?"blocked":e?"user_error":"system_error",userSafe:e}}function Z(t){try{return new URL(t).hostname||void 0}catch{return}}function z(t){return t>=300&&t<400}function q(t,e,r){try{return!!C(new URL(t,e).href,r)}catch{return!1}}function ee(t){if(!(t instanceof Error))return String(t);const e=t.cause;return e instanceof Error?`${t.message}: ${e.message}`:t.message}async function te(t,e){const r=Number(t.headers.get("content-length"));if(Number.isFinite(r)&&r>e)throw G(e,r);if(!t.body)return"";const n=t.body.getReader(),s=new TextDecoder;let d=0,c="";try{for(;;){const{done:h,value:f}=await n.read();if(h)break;if(d+=f.byteLength,d>e)throw G(e,d);c+=s.decode(f,{stream:!0})}return c+s.decode()}finally{await n.cancel().catch(()=>{})}}function G(t,e){return new L(`Response is too large (over ${t} bytes, got ${e}); request less data`)}function re(t,e,r,n){const s={},d=new Set;for(const[h,f]of Object.entries(t??{})){const m=h.toLowerCase();m==="host"||m==="content-length"||m!==O&&(d.add(m),s[h]=f)}n&&(s[O]=n);const c=e??(r?{header:"Authorization",value:r}:void 0);return c&&!d.has(c.header.toLowerCase())&&(s[c.header]=c.value),s}function oe(t){const e={};return t.forEach((r,n)=>{e[n.toLowerCase()]=r}),e}export{he as buildFetchBridge,Y as getDefaultAgent,te as readCappedBody};
@@ -0,0 +1,11 @@
1
+ export declare class GatewayFetchUserError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare class GatewayFetchBlockedError extends GatewayFetchUserError {
5
+ readonly host?: string;
6
+ constructor(message: string, host?: string);
7
+ }
8
+ export declare class GatewayFetchSystemError extends Error {
9
+ constructor(message: string);
10
+ }
11
+ //# sourceMappingURL=fetch-errors.d.ts.map
@@ -0,0 +1 @@
1
+ class s extends Error{constructor(r){super(r),this.name="GatewayFetchUserError"}}class o extends s{host;constructor(r,t){super(r),this.name="GatewayFetchBlockedError",this.host=t}}class a extends Error{constructor(r){super(r),this.name="GatewayFetchSystemError"}}export{o as GatewayFetchBlockedError,a as GatewayFetchSystemError,s as GatewayFetchUserError};
@@ -0,0 +1,13 @@
1
+ export type GatewayFetchOutcome = 'success' | 'blocked' | 'user_error' | 'system_error';
2
+ type GatewayFetchEvent = {
3
+ host: string;
4
+ method: string;
5
+ outcome: GatewayFetchOutcome;
6
+ durationMs: number;
7
+ api?: string;
8
+ status?: number;
9
+ bytes?: number;
10
+ };
11
+ export declare function reportGatewayFetch({ host, method, outcome, durationMs, api, status, bytes, }: GatewayFetchEvent): void;
12
+ export {};
13
+ //# sourceMappingURL=fetch-telemetry.d.ts.map
@@ -0,0 +1 @@
1
+ import{telemetry as t}from"../../../../telemetry/index.js";import{logger as m}from"../../../../tools/notifiers/logger.js";import{McpServerType as c}from"../../constants.js";import{serverAttributes as p}from"../../utils/telemetry-attributes.js";function g({host:d,method:f,outcome:o,durationMs:i,api:e,status:r,bytes:n}){t.sendMcpGatewayFetchPerformedMessage([{...p(c.Docs),host:d,method:f,outcome:o,...e!==void 0&&{api:e},...r!==void 0&&{status:r},...n!==void 0&&{bytes:n},duration_ms:i}]),m.info(`Gateway MCP fetch [${f} ${d}] outcome=${o}`+(e!==void 0?` api=${e}`:"")+(r!==void 0?` status=${r}`:"")+(n!==void 0?` bytes=${n}`:"")+` durationMs=${i}`)}export{g as reportGatewayFetch};
@@ -0,0 +1,4 @@
1
+ export declare const REDACTED = "\u00AB[redacted]\u00BB";
2
+ export declare function redactSecretsFromString(text: string, secretValues: readonly string[]): string;
3
+ export declare function redactHeaders(headers: Record<string, string>, extraSensitiveNames?: readonly string[]): Record<string, string>;
4
+ //# sourceMappingURL=redact.d.ts.map
@@ -0,0 +1 @@
1
+ const s="\xAB[redacted]\xBB",c=["authorization","proxy-authorization","cookie","set-cookie"];function a(r,n){let t=r;for(const e of n)e.trim()&&(t=t.split(e).join(s));return t}function u(r,n=[]){const t=new Set([...c,...n].map(o=>o.toLowerCase())),e={};for(const[o,i]of Object.entries(r))e[o]=t.has(o.toLowerCase())?s:i;return e}export{s as REDACTED,u as redactHeaders,a as redactSecretsFromString};
@@ -2,9 +2,21 @@ export declare class SsrfBlockedError extends Error {
2
2
  code: string;
3
3
  constructor(message: string);
4
4
  }
5
+ /**
6
+ * If `ip` is an IPv4-mapped IPv6 address (`::ffff:0:0/96`), returns the
7
+ * embedded IPv4 in dotted form. Otherwise returns undefined.
8
+ *
9
+ * Examples:
10
+ * - `::ffff:1.2.3.4` -> `'1.2.3.4'`
11
+ * - `::ffff:a9fe:a9fe` -> `'169.254.169.254'`
12
+ * - `::1` -> `undefined`
13
+ */
14
+ export declare function extractMappedIpv4(ip: string): string | undefined;
5
15
  /**
6
16
  * True if `ip` (a literal IPv4/IPv6 string) is in a range unsafe to reach. Non-IP input is
7
17
  * treated as blocked (fail closed). Handles IPv4-mapped IPv6 by checking the embedded IPv4.
8
18
  */
9
19
  export declare function isBlockedIp(ip: string): boolean;
20
+ /** True if `ip` (a literal IPv4/IPv6 string) is a loopback address. Non-IP input returns false. */
21
+ export declare function isLoopback(ip: string): boolean;
10
22
  //# sourceMappingURL=ssrf-blocklist.d.ts.map
@@ -1 +1 @@
1
- import{BlockList as l,isIP as i}from"node:net";class E extends Error{code="SSRF_BLOCKED";constructor(t){super(t),this.name="SsrfBlockedError"}}const x=[["0.0.0.0",8],["10.0.0.0",8],["100.64.0.0",10],["127.0.0.0",8],["169.254.0.0",16],["172.16.0.0",12],["192.0.0.0",24],["192.0.2.0",24],["192.168.0.0",16],["198.18.0.0",15],["198.51.100.0",24],["203.0.113.0",24],["224.0.0.0",4],["240.0.0.0",4],["255.255.255.255",32]],h=[["::",128],["::1",128],["64:ff9b::",96],["64:ff9b:1::",48],["100::",64],["2001::",32],["2001:db8::",32],["2002::",16],["fc00::",7],["fe80::",10],["fec0::",10],["ff00::",8]],c=(()=>{const n=new l;for(const[t,r]of x)n.addSubnet(t,r,"ipv4");for(const[t,r]of h)n.addSubnet(t,r,"ipv6");return n})();function m(n){if(i(n)!==6)return;const t=S(n);if(!(t.length===8&&t[0]===0&&t[1]===0&&t[2]===0&&t[3]===0&&t[4]===0&&t[5]===65535))return;const e=t[6]??0,f=t[7]??0;return[e>>8&255,e&255,f>>8&255,f&255].join(".")}function S(n){const r=n.replace(/:(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/,(s,d,p,u,a)=>`:${((+d&255)<<8|+p&255).toString(16)}:${((+u&255)<<8|+a&255).toString(16)}`).split("::");if(r.length>2)return[];const e=r[0]?r[0].split(":"):[],f=r[1]?r[1].split(":"):[],o=r.length===2?8-e.length-f.length:0;return o<0?[]:[...e,...Array(o).fill("0"),...f].map(s=>Number.parseInt(s,16))}function B(n){const t=i(n);if(t===0||c.check(n,t===4?"ipv4":"ipv6"))return!0;if(t===6){const r=m(n);if(r&&c.check(r,"ipv4"))return!0}return!1}export{E as SsrfBlockedError,B as isBlockedIp};
1
+ import{BlockList as c,isIP as o}from"node:net";class g extends Error{code="SSRF_BLOCKED";constructor(t){super(t),this.name="SsrfBlockedError"}}const m=[["0.0.0.0",8],["10.0.0.0",8],["100.64.0.0",10],["127.0.0.0",8],["169.254.0.0",16],["172.16.0.0",12],["192.0.0.0",24],["192.0.2.0",24],["192.168.0.0",16],["198.18.0.0",15],["198.51.100.0",24],["203.0.113.0",24],["224.0.0.0",4],["240.0.0.0",4],["255.255.255.255",32]],S=[["::",128],["::1",128],["64:ff9b::",96],["64:ff9b:1::",48],["100::",64],["2001::",32],["2001:db8::",32],["2002::",16],["fc00::",7],["fe80::",10],["fec0::",10],["ff00::",8]],d=(()=>{const r=new c;for(const[t,e]of m)r.addSubnet(t,e,"ipv4");for(const[t,e]of S)r.addSubnet(t,e,"ipv6");return r})(),p=(()=>{const r=new c;return r.addSubnet("127.0.0.0",8,"ipv4"),r.addAddress("::1","ipv6"),r})();function u(r){if(o(r)!==6)return;const t=v(r);if(!(t.length===8&&t[0]===0&&t[1]===0&&t[2]===0&&t[3]===0&&t[4]===0&&t[5]===65535))return;const n=t[6]??0,f=t[7]??0;return[n>>8&255,n&255,f>>8&255,f&255].join(".")}function v(r){const e=r.replace(/:(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/,(i,a,l,x,h)=>`:${((+a&255)<<8|+l&255).toString(16)}:${((+x&255)<<8|+h&255).toString(16)}`).split("::");if(e.length>2)return[];const n=e[0]?e[0].split(":"):[],f=e[1]?e[1].split(":"):[],s=e.length===2?8-n.length-f.length:0;return s<0?[]:[...n,...Array(s).fill("0"),...f].map(i=>Number.parseInt(i,16))}function B(r){const t=o(r);if(t===0||d.check(r,t===4?"ipv4":"ipv6"))return!0;if(t===6){const e=u(r);if(e&&d.check(e,"ipv4"))return!0}return!1}function _(r){const t=o(r);if(t===0)return!1;if(p.check(r,t===4?"ipv4":"ipv6"))return!0;if(t===6){const e=u(r);if(e&&p.check(e,"ipv4"))return!0}return!1}export{g as SsrfBlockedError,u as extractMappedIpv4,B as isBlockedIp,_ as isLoopback};
@@ -1 +1 @@
1
- import{isIP as n}from"node:net";import{isHostAllowed as l}from"./allowed-hosts.js";import{isBlockedIp as i}from"./ssrf-blocklist.js";function w(r,t){let o;try{o=new URL(r)}catch{throw new Error(`Invalid URL: ${r}`)}if(o.protocol!=="http:"&&o.protocol!=="https:")throw new Error(`Only http(s) URLs are allowed, got ${o.protocol}`);if(o.username||o.password)throw new Error("URLs must not contain embedded credentials");const e=s(o.hostname);if(c(e)||!l(e,t))throw new Error(`Host "${e}" is not allowed`);return o}function c(r){const t=s(r.toLowerCase()).split("%")[0];return t==="localhost"||t.endsWith(".localhost")?!0:n(t)!==0?i(t):!1}function s(r){return r.replace(/^\[|\]$/g,"")}export{w as assertUrlAllowed,c as isBlockedAddress};
1
+ import{isIP as l}from"node:net";import{isHostAllowed as i}from"./allowed-hosts.js";import{isBlockedIp as a}from"./ssrf-blocklist.js";import{GatewayFetchBlockedError as c,GatewayFetchUserError as s}from"./fetch-errors.js";function m(o,r){let t;try{t=new URL(o)}catch{throw new s(`Invalid URL: ${o}`)}if(t.protocol!=="http:"&&t.protocol!=="https:")throw new s(`Only http(s) URLs are allowed, got ${t.protocol}`);if(t.username||t.password)throw new s("URLs must not contain embedded credentials");const e=n(t.hostname);if(d(e)||!i(e,r))throw new c(`Host "${e}" is not allowed`,e);return t}function d(o){const r=n(o.toLowerCase()).split("%")[0];return r==="localhost"||r.endsWith(".localhost")?!0:l(r)!==0?a(r):!1}function n(o){return o.replace(/^\[|\]$/g,"")}export{m as assertUrlAllowed,d as isBlockedAddress};
@@ -7,5 +7,7 @@ import { type LookupFunction } from 'node:net';
7
7
  * so that every socket connection — including those made while following redirects — is re-validated
8
8
  * against the blocklist at connect time.
9
9
  */
10
- export declare function createSsrfSafeLookup(): LookupFunction;
10
+ export declare function createSsrfSafeLookup({ allowLoopback, }?: {
11
+ allowLoopback?: boolean;
12
+ }): LookupFunction;
11
13
  //# sourceMappingURL=ssrf-safe-lookup.d.ts.map
@@ -1 +1 @@
1
- import{lookup as $}from"node:dns";import{isIP as v}from"node:net";import{isBlockedIp as l,SsrfBlockedError as n}from"./ssrf-blocklist.js";function S(){return(r,p,o)=>{const f=p,s=t=>{if(f.all){o(null,t);return}const[e]=t;if(!e){o(new n(`Hostname "${r}" did not resolve`),"",0);return}o(null,e.address,e.family)},u=v(r);if(u!==0){if(l(r)){o(new n(`Hostname "${r}" is a blocked address`),"",0);return}s([{address:r,family:u}]);return}$(r,{...f,all:!0},(t,e)=>{if(t){o(t,"",0);return}const d=e.filter(({address:i})=>l(i));if(d.length>0){const i=d.map(({address:m})=>m).join(", ");o(new n(`Hostname "${r}" resolves to a blocked address: ${i}`),"",0);return}if(!e.length){o(new n(`Hostname "${r}" did not resolve`),"",0);return}s(e)})}}export{S as createSsrfSafeLookup};
1
+ import{lookup as c}from"node:dns";import{isIP as k}from"node:net";import{isBlockedIp as v,isLoopback as w,SsrfBlockedError as n}from"./ssrf-blocklist.js";function L({allowLoopback:p=!1}={}){const f=o=>v(o)&&!(p&&w(o));return(o,m,r)=>{const s=m,l=t=>{if(s.all){r(null,t);return}const[e]=t;if(!e){r(new n(`Hostname "${o}" did not resolve`),"",0);return}r(null,e.address,e.family)},u=k(o);if(u!==0){if(f(o)){r(new n(`Hostname "${o}" is a blocked address`),"",0);return}l([{address:o,family:u}]);return}c(o,{...s,all:!0},(t,e)=>{if(t){r(t,"",0);return}const d=e.filter(({address:i})=>f(i));if(d.length>0){const i=d.map(({address:$})=>$).join(", ");r(new n(`Hostname "${o}" resolves to a blocked address: ${i}`),"",0);return}if(!e.length){r(new n(`Hostname "${o}" did not resolve`),"",0);return}l(e)})}}export{L as createSsrfSafeLookup};
@@ -1 +1 @@
1
- import{buildAllowedHostMatchers as i}from"./fetch/allowed-hosts.js";import{buildFetchBridge as l}from"./fetch/fetch-bridge.js";function a(e){return Object.values(e.apiDescriptionsMap??{}).filter(r=>r.fetchEligible)}function n(e){return a(e).length>0}function c(e,r){const s=a(e).map(t=>({slug:t.relativePath,name:t.name,matchers:i(t.servers??[]),secret:e.upstreamSecrets?.[t.relativePath]}));return l({allowedHosts:i([]),apis:s,authorization:e.upstreamAuthorization,onAuthRequired:r})}export{c as buildExecuteHostFetch,a as getCallableApis,n as isFetchCapable};
1
+ import{buildAllowedHostMatchers as a}from"./fetch/allowed-hosts.js";import{buildFetchBridge as s}from"./fetch/fetch-bridge.js";function i(e){return Object.values(e.apiDescriptionsMap??{}).filter(r=>r.fetchEligible)}function u(e){return i(e).length>0}function n(e,r){const l=i(e).map(t=>({slug:t.relativePath,name:t.name,matchers:a(t.servers??[]),secret:e.upstreamSecrets?.[t.relativePath]}));return s({allowedHosts:a([]),apis:l,authorization:e.upstreamAuthorization,onAuthRequired:r,maxFetchesPerExec:e.gatewayFetchBudgetPerExec,clientIp:e.clientIp})}export{n as buildExecuteHostFetch,i as getCallableApis,u as isFetchCapable};
@@ -0,0 +1,3 @@
1
+ import type { Store } from '../../../../store.js';
2
+ export declare function applyEdgeRateLimit(request: Request, store: Store): Promise<Response | undefined>;
3
+ //# sourceMappingURL=apply-edge-rate-limit.d.ts.map
@@ -0,0 +1 @@
1
+ import{CacheService as a}from"../../../../persistence/cache/services/cache-service.js";import{logger as o}from"../../../../tools/notifiers/logger.js";import{ipRateLimitKey as s}from"./ip-key.js";import{checkEdgeRateLimit as c}from"./edge-limiter.js";import{resolveGatewayFetchLimits as d}from"./limits.js";async function g(i,n){const r=s(i);if(!r)return;let e;try{const t=await a.getInstance({baseDbDir:n.serverOutDir,databaseType:"local"});e=await c({cache:t,ip:r,limits:d(),now:Date.now()})}catch(t){o.warn(`gateway rate-limit init error, failing open: ${String(t)}`);return}if(!e.allowed)return 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{g as applyEdgeRateLimit};
@@ -0,0 +1,16 @@
1
+ import type { CacheService } from '../../../../persistence/cache/services/cache-service.js';
2
+ import type { GatewayFetchLimits } from './limits.js';
3
+ export type EdgeDecision = {
4
+ allowed: true;
5
+ } | {
6
+ allowed: false;
7
+ retryAfterSeconds: number;
8
+ scope: 'rate' | 'quota';
9
+ };
10
+ export declare function checkEdgeRateLimit(params: {
11
+ cache: CacheService;
12
+ ip: string;
13
+ limits: GatewayFetchLimits;
14
+ now: number;
15
+ }): Promise<EdgeDecision>;
16
+ //# sourceMappingURL=edge-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ import{checkAndIncrement as a}from"./window-counter.js";const s=60,d=86400;async function w(i){const{cache:e,ip:t,limits:o,now:r}=i,n=await a({cache:e,ip:t,windowSeconds:s,limit:o.ratePerMin,now:r});if(!n.allowed)return{allowed:!1,retryAfterSeconds:n.retryAfterSeconds,scope:"rate"};const c=await a({cache:e,ip:t,windowSeconds:d,limit:o.quotaPerDay,now:r});return c.allowed?{allowed:!0}:{allowed:!1,retryAfterSeconds:c.retryAfterSeconds,scope:"quota"}}export{w as checkEdgeRateLimit};
@@ -0,0 +1,2 @@
1
+ export declare function ipRateLimitKey(req: Request): string | undefined;
2
+ //# sourceMappingURL=ip-key.d.ts.map
@@ -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"../fetch/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};
@@ -0,0 +1,7 @@
1
+ export type GatewayFetchLimits = {
2
+ ratePerMin: number;
3
+ quotaPerDay: number;
4
+ budgetPerExec: number;
5
+ };
6
+ export declare function resolveGatewayFetchLimits(): GatewayFetchLimits;
7
+ //# sourceMappingURL=limits.d.ts.map
@@ -0,0 +1 @@
1
+ import{EntitlementsProvider as u}from"../../../../entitlements/entitlements-provider.js";import{FEATURE as r,GATEWAY_FETCH_LIMIT_DEFAULTS as a}from"../../../../../constants/common.js";function n(e,i,o){const t=e.entitlements?.[i];return typeof t=="number"&&Number.isFinite(t)?t:o}function P(){const e=u.instance();return{ratePerMin:n(e,r.gatewayFetchRatePerMin,a.ratePerMin),quotaPerDay:n(e,r.gatewayFetchQuotaPerDay,a.quotaPerDay),budgetPerExec:n(e,r.gatewayFetchBudgetPerExec,a.budgetPerExec)}}export{P as resolveGatewayFetchLimits};
@@ -0,0 +1,15 @@
1
+ import type { CacheService } from '../../../../persistence/cache/services/cache-service.js';
2
+ export type WindowCheck = {
3
+ allowed: false;
4
+ retryAfterSeconds: number;
5
+ } | {
6
+ allowed: true;
7
+ };
8
+ export declare function checkAndIncrement(params: {
9
+ cache: CacheService;
10
+ ip: string;
11
+ windowSeconds: number;
12
+ limit: number;
13
+ now: number;
14
+ }): Promise<WindowCheck>;
15
+ //# sourceMappingURL=window-counter.d.ts.map
@@ -0,0 +1 @@
1
+ import{logger as f}from"../../../../tools/notifiers/logger.js";const s="gateway-fetch-rl";async function m(i){const{cache:e,ip:l,windowSeconds:t,limit:w,now:d}=i,n=Math.floor(d/1e3),o=Math.floor(n/t),r=`${l}:${t}:${o}`;try{const a=(await e.get({key:r,namespace:s}))?.count??0;if(a>=w){const u=(o+1)*t-n;return{allowed:!1,retryAfterSeconds:Math.max(1,u)}}return await e.set({key:r,namespace:s,value:{count:a+1},ttlInSeconds:t}),{allowed:!0}}catch(c){return f.warn(`gateway rate-limit store error, failing open: ${String(c)}`),{allowed:!0}}}export{m as checkAndIncrement};
@@ -1,3 +1,4 @@
1
+ import type { KvService } from '@redocly/config';
1
2
  import type { UpstreamAuthScheme } from './upstream-auth-scheme.js';
2
3
  export declare const ELICITATION_TTL_MS: number;
3
4
  export type StoredSecret = {
@@ -12,13 +13,12 @@ export type ElicitationTarget = {
12
13
  };
13
14
  export declare class SecretStore {
14
15
  #private;
15
- constructor();
16
- setSecret(sessionId: string, api: string, secret: StoredSecret): void;
17
- getSecretsForSession(sessionId: string): Record<string, StoredSecret>;
18
- deleteSession(sessionId: string): void;
19
- createElicitation(target: ElicitationTarget): string;
20
- peekElicitation(token: string): ElicitationTarget | undefined;
21
- consumeElicitation(token: string): ElicitationTarget | undefined;
16
+ setSecret(kv: KvService, sessionId: string, api: string, secret: StoredSecret): Promise<void>;
17
+ getSecretsForSession(kv: KvService, sessionId: string): Promise<Record<string, StoredSecret>>;
18
+ deleteSession(kv: KvService, sessionId: string): Promise<void>;
19
+ createElicitation(kv: KvService, target: ElicitationTarget): Promise<string>;
20
+ peekElicitation(kv: KvService, token: string): Promise<ElicitationTarget | undefined>;
21
+ consumeElicitation(kv: KvService, token: string): Promise<ElicitationTarget | undefined>;
22
22
  }
23
23
  export declare const secretStore: SecretStore;
24
24
  //# sourceMappingURL=secret-store.d.ts.map
@@ -1 +1 @@
1
- import{createCipheriv as f,createDecipheriv as u,createHash as d,hkdfSync as l,randomBytes as p,randomUUID as y}from"node:crypto";const A=14400*1e3,g=900*1e3,S=60*1e3,x="mcp-upstream-secrets";class m{#e=new Map;#t=new Map;#n=p(32);constructor(){setInterval(()=>this.#r(),S).unref?.()}setSecret(e,t,i){const s=this.#s(e),r=p(12),o=f("aes-256-gcm",s,r),c=Buffer.concat([o.update(JSON.stringify(i),"utf8"),o.final()]),n=this.#e.get(this.#i(e))??{byApi:new Map};n.byApi.set(t,{iv:r,tag:o.getAuthTag(),data:c,expiresAt:Date.now()+A}),this.#e.set(this.#i(e),n)}getSecretsForSession(e){const t=this.#i(e),i=this.#e.get(t);if(!i)return{};const s=Date.now(),r=this.#s(e),o={};for(const[c,n]of i.byApi){if(n.expiresAt<=s){i.byApi.delete(c);continue}try{const a=u("aes-256-gcm",r,n.iv);a.setAuthTag(n.tag);const h=Buffer.concat([a.update(n.data),a.final()]).toString("utf8");o[c]=JSON.parse(h)}catch{i.byApi.delete(c)}}return i.byApi.size===0&&this.#e.delete(t),o}deleteSession(e){this.#e.delete(this.#i(e))}createElicitation(e){const t=y();return this.#t.set(t,{...e,expiresAt:Date.now()+g}),t}peekElicitation(e){const t=this.#t.get(e);if(!t)return;if(t.expiresAt<=Date.now()){this.#t.delete(e);return}const{expiresAt:i,...s}=t;return s}consumeElicitation(e){const t=this.peekElicitation(e);return t&&this.#t.delete(e),t}#r(){const e=Date.now();for(const[t,i]of this.#e){for(const[s,r]of i.byApi)r.expiresAt<=e&&i.byApi.delete(s);i.byApi.size===0&&this.#e.delete(t)}for(const[t,i]of this.#t)i.expiresAt<=e&&this.#t.delete(t)}#i(e){return d("sha256").update(e).digest("hex")}#s(e){return Buffer.from(l("sha256",e,this.#n,x,32))}}const T=new m;export{g as ELICITATION_TTL_MS,m as SecretStore,T as secretStore};
1
+ import{createCipheriv as o,createDecipheriv as f,createHash as u,hkdfSync as a,randomBytes as h,randomUUID as m}from"node:crypto";import{JWT_SECRET_KEY as c}from"../../../constants/common.js";const l=14400*1e3,p=900*1e3,y="mcp-upstream-secrets",S="mcp-elicitation",d="mcp-elicitation";class g{async setSecret(t,e,r,n){const i={api:r,...this.#c(this.#a(e),n,r)};await t.set(this.#n(e,r),i,{ttlInSeconds:l/1e3})}async getSecretsForSession(t,e){const r=this.#a(e),{items:n}=await t.list({prefix:this.#i(e)}),i={};for(const s of n)if(s.value)try{i[s.value.api]=this.#r(r,s.value,s.value.api)}catch{continue}return i}async deleteSession(t,e){const{items:r}=await t.list({prefix:this.#i(e)}),n=r.flatMap(i=>i.value?[this.#n(e,i.value.api)]:[]);n.length!==0&&await t.transaction(async i=>{for(const s of n)await i.delete(s)})}async createElicitation(t,e){const r=m();return await t.set(this.#t(r),this.#c(this.#e(),e),{ttlInSeconds:p/1e3}),r}async peekElicitation(t,e){const r=await t.get(this.#t(e));if(r)try{return this.#r(this.#e(),r)}catch{return}}async consumeElicitation(t,e){const r=this.#t(e),n=await t.transaction(async i=>{const s=await i.get(r);return s&&await i.delete(r),s});if(n)try{return this.#r(this.#e(),n)}catch{return}}#i(t){return["mcp","secret",this.#s(t)]}#n(t,e){return["mcp","secret",this.#s(t),e]}#t(t){return["mcp","elicitation",t]}#s(t){return u("sha256").update(t).digest("hex")}#a(t){return Buffer.from(a("sha256",c,t,y,32))}#e(){return Buffer.from(a("sha256",c,d,S,32))}#c(t,e,r){const n=h(12),i=o("aes-256-gcm",t,n);r&&i.setAAD(Buffer.from(r,"utf8"));const s=Buffer.concat([i.update(JSON.stringify(e),"utf8"),i.final()]);return{iv:n.toString("base64"),tag:i.getAuthTag().toString("base64"),data:s.toString("base64")}}#r(t,e,r){const n=f("aes-256-gcm",t,Buffer.from(e.iv,"base64"));r&&n.setAAD(Buffer.from(r,"utf8")),n.setAuthTag(Buffer.from(e.tag,"base64"));const i=Buffer.concat([n.update(Buffer.from(e.data,"base64")),n.final()]).toString("utf8");return JSON.parse(i)}}const v=new g;export{p as ELICITATION_TTL_MS,g as SecretStore,v as secretStore};
@@ -1 +1 @@
1
- import{toFetchResponse as j,toReqRes as H}from"fetch-to-node";import{isInitializeRequest as L}from"@redocly/mcp-typescript-sdk/types.js";import{logger as S}from"../../../tools/notifiers/logger.js";import{isObject as B}from"../../../../utils/guards/is-object.js";import{getAccessibleSkills as J}from"../../../utils/skills/get-accessible-skills.js";import{getSkillUrl as P}from"../../../utils/skills/get-skill-url.js";import{createDocsMcpServer as A}from"../servers/docs-server.js";import{mcpSessionManager as g}from"../servers/session-manager.js";import{filterApiDescriptionsByRbac as _,filterGraphqlDescriptionsByRbac as z}from"../utils.js";import{createMethodNotAllowedError as V,withErrorHandling as F}from"./errors.js";import{attachSseKeepalive as G}from"./sse-keepalive.js";import{constructInvalidTokenResponse as $,constructUnauthorizedResponse as K,handleMcpAuth as X,shouldHandleMcpAuth as Q}from"../auth/auth-handlers.js";import{extractTokenFromAuthHeader as W}from"../utils/jwt.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as Y}from"../constants.js";const h="X-Redocly-AI-Metadata";function Z(e){if(e)try{const o=JSON.parse(e);if(B(o))return o;S.warn(`Ignoring ${h} header: not a JSON object.`)}catch{S.warn(`Ignoring ${h} header: invalid JSON.`)}}const q=async(e,o,a)=>{const t=!!o?.config?.access?.requiresLogin,r=o?.config?.access?.rbac||{},l=a?.props?.config?.mcpMode??"tools";let c;const R=e.headers.get("Authorization");if(Q(t,r)){const{isAuthenticated:f,isTokenValid:p,currentUser:n,accessToken:u}=await X(e,o);if(!f)return K(new URL(e.url).origin);if(!p)return $();n&&(o.user=n),c=u}else R&&(c=W(R||"")??void 0);let i;return await F(async()=>{const f=e.headers.get("mcp-session-id")??void 0,p=a?.props?.config?.elicitationEnabled??!1;if(p&&f){const s=g.get(f);if(!s)return new Response(JSON.stringify({jsonrpc:"2.0",error:{code:-32001,message:"Session not found"},id:null}),{status:404,headers:{"Content-Type":"application/json"}});const d=e.method==="POST"?await e.json():void 0;return await w(e,s,d)}if(e.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: ${e.url}`}),{status:405,headers:{"Content-Type":"application/json"}});if(e.method!=="POST")return V();const n=a,u=n?.props?.config?.apiDescriptionsMap||{},y=n?.props?.config?.graphqlDescriptions||[],D=n?.props?.config?.mcpDocsServerName||Y,{config:{mcp:T={}}}=o,b=_(u,o.user,r,t),C=z(y,o.user,r,t),E=o.config.products?Object.values(o.config.products).map(s=>s?.name):[],v=T.docs?.name||D,U=Z(e.headers.get(h)),O=n?.props?.tools||[],k=n?.props?.skillsBaseUrl||new URL(e.url).origin,I=J(n?.props?.skills||[],o.user,r,t).map(s=>({slug:s.slug,name:s.name,description:s.description,content:s.content,uri:P(k,s.slug)})),m=await e.json(),N=p&&L(m),M={name:v,tools:O,skills:I,context:{...o,accessToken:c,outdir:o.outdir||"",baseUrl:o.baseUrl||new URL(e.url).origin,apiDescriptionsMap:b,graphqlDescriptions:C,products:E,metadata:U,mcpMode:l,elicitationEnabled:p}};if(N){const s=await A({...M,sessionHooks:{onInitialized:d=>{g.set(d,s),i=void 0},onClosed:d=>{g.close(d)}}});return i=s,await w(e,s,m)}return i=await A(M),await w(e,i,m)},async()=>{i&&(await i.cleanup(),i=void 0)},l)};async function w(e,o,a){const{req:t,res:r}=H(e),l=G(r);try{await o.transport.handleRequest(t,r,a)}catch(c){throw l.stop(),c}return j(r)}var me=q;export{me as default};
1
+ import{toFetchResponse as L,toReqRes as P}from"fetch-to-node";import{isInitializeRequest as j}from"@redocly/mcp-typescript-sdk/types.js";import{logger as S}from"../../../tools/notifiers/logger.js";import{getFeedbackClientIp as H}from"../../../web-server/utils/feedback-ip.js";import{GATEWAY_FETCH_LIMIT_DEFAULTS as B}from"../../../../constants/common.js";import{isObject as J}from"../../../../utils/guards/is-object.js";import{getAccessibleSkills as K}from"../../../utils/skills/get-accessible-skills.js";import{getSkillUrl as V}from"../../../utils/skills/get-skill-url.js";import{createDocsMcpServer as T}from"../servers/docs-server.js";import{mcpSessionManager as h}from"../servers/session-manager.js";import{filterApiDescriptionsByRbac as z,filterGraphqlDescriptionsByRbac as G}from"../utils.js";import{createMethodNotAllowedError as $,withErrorHandling as Y}from"./errors.js";import{attachSseKeepalive as W}from"./sse-keepalive.js";import{constructForbiddenResponse as X,constructInvalidTokenResponse as Q,constructUnauthorizedResponse as Z,handleMcpAuth as q,shouldHandleMcpAuth as x}from"../auth/auth-handlers.js";import{extractTokenFromAuthHeader as ee}from"../utils/jwt.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as oe}from"../constants.js";import{canAccessFeature as se}from"../../../utils/rbac.js";import{DEFAULT_ANONYMOUS_VISITOR_TEAM as re,RbacFeatures as ne}from"../../../../constants/common.js";const A="X-Redocly-AI-Metadata";function ie(o){if(o)try{const e=JSON.parse(o);if(J(e))return e;S.warn(`Ignoring ${A} header: not a JSON object.`)}catch{S.warn(`Ignoring ${A} header: invalid JSON.`)}}const te=async(o,e,a)=>{const i=!!e?.config?.access?.requiresLogin,r=e?.config?.access?.rbac||{},d=a?.props?.config?.mcpMode??"tools",u=a?.props?.config?.gatewayFetchBudgetPerExec??B.budgetPerExec;let m;const w=o.headers.get("Authorization");if(x(i,r)){const{isAuthenticated:l,isTokenValid:c,currentUser:n,accessToken:f}=await q(o,e);if(!l)return Z(new URL(o.url).origin);if(!c)return Q();n&&(e.user=n),m=f}else w&&(m=ee(w||"")??void 0);const E={isAuthenticated:!!e.user?.isAuthenticated,teams:e.user?.teams||[re],claims:e.user?.claims??{}};if(!se(ne.MCP,E,r,i))return X();let t;return await Y(async()=>{const l=o.headers.get("mcp-session-id")??void 0,c=a?.props?.config?.elicitationEnabled??!1;if(c&&l){const s=h.get(l);if(!s)return new Response(JSON.stringify({jsonrpc:"2.0",error:{code:-32001,message:"Session not found"},id:null}),{status:404,headers:{"Content-Type":"application/json"}});const p=o.method==="POST"?await o.json():void 0;return await R(o,s,p)}if(o.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: ${o.url}`}),{status:405,headers:{"Content-Type":"application/json"}});if(o.method!=="POST")return $();const n=a,f=n?.props?.config?.apiDescriptionsMap||{},y=n?.props?.config?.graphqlDescriptions||[],b=n?.props?.config?.mcpDocsServerName||oe,{config:{mcp:C={}}}=e,D=z(f,e.user,r,i),v=G(y,e.user,r,i),I=e.config.products?Object.values(e.config.products).map(s=>s?.name):[],U=C.docs?.name||b,O=ie(o.headers.get(A)),F=n?.props?.tools||[],N=n?.props?.skillsBaseUrl||new URL(o.url).origin,k=K(n?.props?.skills||[],e.user,r,i).map(s=>({slug:s.slug,name:s.name,description:s.description,content:s.content,uri:V(N,s.slug)})),g=await o.json(),_=c&&j(g),M={name:U,tools:F,skills:k,context:{...e,accessToken:m,outdir:e.outdir||"",baseUrl:e.baseUrl||new URL(o.url).origin,apiDescriptionsMap:D,graphqlDescriptions:v,products:I,metadata:O,mcpMode:d,elicitationEnabled:c,gatewayFetchBudgetPerExec:u,clientIp:H(o)}};if(_){const s=await T({...M,getKv:e.getKv,sessionHooks:{onInitialized:p=>{h.set(p,s),t=void 0},onClosed:p=>{h.close(p)}}});return t=s,await R(o,s,g)}return t=await T({...M,getKv:e.getKv}),await R(o,t,g)},async()=>{t&&(await t.cleanup(),t=void 0)},d)};async function R(o,e,a){const{req:i,res:r}=P(o),d=W(r);try{await e.transport.handleRequest(i,r,a)}catch(u){throw d.stop(),u}return L(r)}var Ce=te;export{Ce as default};
@@ -1 +1 @@
1
- import{telemetry as d}from"../../../telemetry/index.js";import{McpErrorCodes as s,McpServerType as n}from"../constants.js";class a extends Error{code;data;requestId;constructor(e,t,o,c){super(t),this.name="McpError",this.code=e,this.data=o,this.requestId=c}createErrorResponse(){const e={jsonrpc:"2.0",error:{code:this.code,message:this.message,...this.data?{data:this.data}:{}},id:this.requestId??null},t=i(this.code);return new Response(JSON.stringify(e),{status:t,headers:{"Content-Type":"application/json"}})}}function i(r){switch(r){case s.InvalidRequest:case s.InvalidParams:return 400;case s.MethodNotFound:return 404;case s.ServerError:return 405;case s.InternalError:default:return 500}}function E(r){return new a(s.ServerError,"Method not allowed",void 0,r).createErrorResponse()}function u(r,e,t="tools"){const o=r instanceof Error?r.message:String(r),c=r instanceof Error?r.stack:void 0;return d.sendMcpErrorMessage([{id:n.Docs,object:"mcpServer",uri:`urn:redocly:realm:mcp:server:${n.Docs}`,server_type:n.Docs,message:o,stack:c||"",mode:t}]),new a(s.InternalError,"Internal server error mcp",o,e).createErrorResponse()}function v(r="Invalid request",e){return new a(s.InvalidRequest,r,void 0,e).createErrorResponse()}function f(r="Invalid parameters",e){return new a(s.InvalidParams,r,void 0,e).createErrorResponse()}async function h(r,e,t="tools"){try{return await r()}catch(o){return d.sendMcpErrorMessage([{id:n.Docs,object:"mcpServer",uri:`urn:redocly:realm:mcp:server:${n.Docs}`,server_type:n.Docs,message:o?.message||"",stack:o?.stack||"",mode:t}]),e&&e(),u(o,void 0,t)}}export{a as McpError,u as createInternalServerError,f as createInvalidParamsError,v as createInvalidRequestError,E as createMethodNotAllowedError,h as withErrorHandling};
1
+ import{telemetry as c}from"../../../telemetry/index.js";import{McpErrorCodes as s,McpServerType as d}from"../constants.js";import{serverAttributes as i}from"../utils/telemetry-attributes.js";class n extends Error{code;data;requestId;constructor(e,t,o,a){super(t),this.name="McpError",this.code=e,this.data=o,this.requestId=a}createErrorResponse(){const e={jsonrpc:"2.0",error:{code:this.code,message:this.message,...this.data?{data:this.data}:{}},id:this.requestId??null},t=u(this.code);return new Response(JSON.stringify(e),{status:t,headers:{"Content-Type":"application/json"}})}}function u(r){switch(r){case s.InvalidRequest:case s.InvalidParams:return 400;case s.MethodNotFound:return 404;case s.ServerError:return 405;case s.InternalError:default:return 500}}function h(r){return new n(s.ServerError,"Method not allowed",void 0,r).createErrorResponse()}function p(r,e,t="tools"){const o=r instanceof Error?r.message:String(r),a=r instanceof Error?r.stack:void 0;return c.sendMcpErrorMessage([{...i(d.Docs),message:o,stack:a||"",mode:t}]),new n(s.InternalError,"Internal server error mcp",o,e).createErrorResponse()}function I(r="Invalid request",e){return new n(s.InvalidRequest,r,void 0,e).createErrorResponse()}function v(r="Invalid parameters",e){return new n(s.InvalidParams,r,void 0,e).createErrorResponse()}async function g(r,e,t="tools"){try{return await r()}catch(o){return c.sendMcpErrorMessage([{...i(d.Docs),message:o?.message||"",stack:o?.stack||"",mode:t}]),e&&e(),p(o,void 0,t)}}export{n as McpError,p as createInternalServerError,v as createInvalidParamsError,I as createInvalidRequestError,h as createMethodNotAllowedError,g as withErrorHandling};
@@ -1 +1 @@
1
- import{withPathPrefix as f}from"@redocly/theme/core/utils";import{importApiRoutesHandler as D}from"../../../api-routes/import-api-routes-handlers.js";import{enhanceContext as g}from"../../../api-routes/helpers/enhance-context.js";import{telemetry as h}from"../../../telemetry/index.js";import{KvService as A}from"../../../persistence/kv/services/kv-service.js";import{envConfig as o}from"../../../config/env-config.js";import{acceptsHtml as R}from"../../../utils/ai-agent-detection.js";import{handleErrorPageRender as E}from"../../../web-server/utils.js";import{MCP_NOT_ALLOWED_TEMPLATE_ID as T}from"../constants.js";import{MCP_DOCS_SERVER_HANDLER_ID as _}from"../index.js";import{resolvePublicBaseUrl as w}from"../../../web-server/utils/resolve-public-base-url.js";async function P(a,t,e){const s=t.req.raw;if(a.requestHandlerId===_&&s.method==="GET"&&R(s.headers.get("accept"))&&e.globalData.mcpData?.docs?.enabled)return E(t,e,{slug:a.slug},405,T);const r=t.get("auth"),n=await e.resolveRouteStaticData(a)||{},l=f(a.slug),{requestHandlers:m}=await D(e.serverOutDir),c=m[a.requestHandlerId],p=(await c()).default,d=async()=>await A.getInstance({baseDbDir:e.serverOutDir,sqldRemoteDatabaseUrl:o.SQLD_REMOTE_DATABASE_URL,sqldRemoteDatabaseAuthToken:o.SQLD_REMOTE_DATABASE_AUTH_TOKEN}),u=g({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:new URL(t.req.url).origin},telemetry:h,getKv:d}),i=await p(t.req.raw,u,{...n,props:{...n.props,routeSlug:l,outdir:e.outdir,skills:e.config.skills?.hide===!0?[]:e.getSkills(),skillsBaseUrl:w(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{P as handleMcpRequest};
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 R}from"../../../telemetry/index.js";import{KvService as A}from"../../../persistence/kv/services/kv-service.js";import{envConfig as l}from"../../../config/env-config.js";import{acceptsHtml as E}from"../../../utils/ai-agent-detection.js";import{handleErrorPageRender as T}from"../../../web-server/utils.js";import{MCP_NOT_ALLOWED_TEMPLATE_ID as _}from"../constants.js";import{MCP_DOCS_SERVER_HANDLER_ID as w}from"../index.js";import{resolvePublicBaseUrl as S}from"../../../web-server/utils/resolve-public-base-url.js";import{applyEdgeRateLimit as q}from"../gateway-mcp/rate-limit/apply-edge-rate-limit.js";async function B(a,t,e){const n=t.req.raw;if(a.requestHandlerId===w&&n.method==="GET"&&E(n.headers.get("accept"))&&e.globalData.mcpData?.docs?.enabled)return T(t,e,{slug:a.slug},405,_);const r=t.get("auth"),s=await q(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 A.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:new URL(t.req.url).origin},telemetry:R,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{B as handleMcpRequest};
@@ -1,4 +1,4 @@
1
- import{secretStore as d}from"../gateway-mcp/secret-store.js";import{buildAllowedHostMatchers as C}from"../gateway-mcp/fetch/allowed-hosts.js";import{assertUrlAllowed as P}from"../gateway-mcp/fetch/ssrf-guard.js";import{readCappedBody as $,ssrfSafeAgent as I}from"../gateway-mcp/fetch/fetch-bridge.js";const p={"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store","X-Frame-Options":"DENY","Referrer-Policy":"no-referrer","Content-Security-Policy":"default-src 'none'; style-src 'unsafe-inline'; form-action 'self'"},_=3e4,E=64*1024;let v=fetch;function j(e){v=e}const T=async e=>{if(e.method==="GET"){const t=new URL(e.url).searchParams.get("token")??"",n=d.peekElicitation(t);return n?new Response(B(n,t),{status:200,headers:p}):l()}if(e.method==="POST"){const t=await e.formData(),n=String(t.get("token")??"");return String(t.get("mode")??"token")==="client-credentials"?R(t,n):z(t,n)}return new Response("Method Not Allowed",{status:405,headers:{Allow:"GET, POST"}})};async function z(e,t){const n=String(e.get("secret")??"");if(!n)return l();const o=d.consumeElicitation(t);return o?(d.setSecret(o.sessionId,o.api,F(o.auth,n)),new Response(w(o.name),{status:200,headers:p})):l()}async function R(e,t){const n=String(e.get("client_id")??"").trim(),o=String(e.get("client_secret")??"").trim();if(!n||!o)return l();const y=d.peekElicitation(t);if(!y||y.auth.kind!=="oauth2ClientCredentials")return l();const r=d.consumeElicitation(t);if(!r||r.auth.kind!=="oauth2ClientCredentials")return l();const h=r.auth;try{const x=C([{url:h.tokenUrl}]),A=P(h.tokenUrl,x),g=new AbortController,S=setTimeout(()=>g.abort(),_);let f;try{f=await v(A,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"client_credentials",client_id:n,client_secret:o,...h.scopes.length>0?{scope:h.scopes.join(" ")}:{}}),redirect:"manual",signal:g.signal,dispatcher:I})}finally{clearTimeout(S)}if(!f.ok)return m(r.name);let k;try{k=await $(f,E)}catch{return m(r.name)}let u;try{u=JSON.parse(k)}catch{return m(r.name)}const b=typeof u=="object"&&u!==null&&"access_token"in u?u.access_token:void 0;return typeof b!="string"||!b?m(r.name):(d.setSecret(r.sessionId,r.api,{header:"Authorization",value:`Bearer ${b}`}),new Response(w(r.name),{status:200,headers:p}))}catch{return m(r.name)}}function F(e,t){return e.kind==="apiKey"?{header:e.header,value:t}:e.kind==="bearer"||e.kind==="oauth2ClientCredentials"?{header:"Authorization",value:O(t)}:{header:"Authorization",value:t}}function O(e){const t=e.trim();return t.toLowerCase().startsWith("bearer")?t:`Bearer ${t}`}function a(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;")}function i(e){return`<!doctype html>
1
+ import{secretStore as l}from"../gateway-mcp/secret-store.js";import{buildAllowedHostMatchers as P}from"../gateway-mcp/fetch/allowed-hosts.js";import{assertUrlAllowed as $}from"../gateway-mcp/fetch/ssrf-guard.js";import{readCappedBody as I,getDefaultAgent as _}from"../gateway-mcp/fetch/fetch-bridge.js";const m={"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store","X-Frame-Options":"DENY","Referrer-Policy":"no-referrer","Content-Security-Policy":"default-src 'none'; style-src 'unsafe-inline'; form-action 'self'"},E=3e4,T=64*1024;let v=fetch;function j(e){v=e}const z=async(e,t)=>{const o=await t.getKv();if(e.method==="GET"){const n=new URL(e.url).searchParams.get("token")??"",r=await l.peekElicitation(o,n);return r?new Response(M(r,n),{status:200,headers:m}):u()}if(e.method==="POST"){const n=await e.formData(),r=String(n.get("token")??"");return String(n.get("mode")??"token")==="client-credentials"?F(o,n,r):R(o,n,r)}return new Response("Method Not Allowed",{status:405,headers:{Allow:"GET, POST"}})};async function R(e,t,o){const n=String(t.get("secret")??"");if(!n)return u();const r=await l.consumeElicitation(e,o);return r?(await l.setSecret(e,r.sessionId,r.api,O(r.auth,n)),new Response(x(r.name),{status:200,headers:m})):u()}async function F(e,t,o){const n=String(t.get("client_id")??"").trim(),r=String(t.get("client_secret")??"").trim();if(!n||!r)return u();const b=await l.peekElicitation(e,o);if(!b||b.auth.kind!=="oauth2ClientCredentials")return u();const a=await l.consumeElicitation(e,o);if(!a||a.auth.kind!=="oauth2ClientCredentials")return u();const f=a.auth;try{const A=P([{url:f.tokenUrl}]),S=$(f.tokenUrl,A),k=new AbortController,C=setTimeout(()=>k.abort(),E);let y;try{y=await v(S,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"client_credentials",client_id:n,client_secret:r,...f.scopes.length>0?{scope:f.scopes.join(" ")}:{}}),redirect:"manual",signal:k.signal,dispatcher:_()})}finally{clearTimeout(C)}if(!y.ok)return h(a.name);let w;try{w=await I(y,T)}catch{return h(a.name)}let p;try{p=JSON.parse(w)}catch{return h(a.name)}const g=typeof p=="object"&&p!==null&&"access_token"in p?p.access_token:void 0;return typeof g!="string"||!g?h(a.name):(await l.setSecret(e,a.sessionId,a.api,{header:"Authorization",value:`Bearer ${g}`}),new Response(x(a.name),{status:200,headers:m}))}catch{return h(a.name)}}function O(e,t){return e.kind==="apiKey"?{header:e.header,value:t}:e.kind==="bearer"||e.kind==="oauth2ClientCredentials"?{header:"Authorization",value:B(t)}:{header:"Authorization",value:t}}function B(e){const t=e.trim();return t.toLowerCase().startsWith("bearer")?t:`Bearer ${t}`}function i(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;")}function s(e){return`<!doctype html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="utf-8">
@@ -34,34 +34,34 @@ footer{border-top-color:#1f2937;color:#6b7280}
34
34
  </style>
35
35
  </head>
36
36
  <body><div class="card">${e}</div></body>
37
- </html>`}function s(e){return`<h1>\u{1F512} ${a(e)}</h1>`}function c(){return"<footer>Secured by Redocly &middot; credentials never enter the AI conversation</footer>"}function B(e,t){switch(e.auth.kind){case"bearer":return M(e.name,t);case"apiKey":return q(e.name,t,e.auth.header);case"oauth2ClientCredentials":return H(e.name,t,e.auth.scopes);default:return N(e.name,t)}}function M(e,t){return i(`
38
- ${s(`Credentials for ${e}`)}
37
+ </html>`}function c(e){return`<h1>\u{1F512} ${i(e)}</h1>`}function d(){return"<footer>Secured by Redocly &middot; credentials never enter the AI conversation</footer>"}function M(e,t){switch(e.auth.kind){case"bearer":return q(e.name,t);case"apiKey":return H(e.name,t,e.auth.header);case"oauth2ClientCredentials":return N(e.name,t,e.auth.scopes);default:return U(e.name,t)}}function q(e,t){return s(`
38
+ ${c(`Credentials for ${e}`)}
39
39
  <p class="hint">Stored encrypted for this MCP session only (up to 4 hours) and sent to the API as the <code>Authorization</code> header. It never passes through the AI conversation.</p>
40
40
  <form method="post" action="/mcp/credentials" autocomplete="off">
41
- <input type="hidden" name="token" value="${a(t)}">
41
+ <input type="hidden" name="token" value="${i(t)}">
42
42
  <input type="hidden" name="mode" value="token">
43
43
  <label for="secret">API token</label>
44
44
  <input type="password" id="secret" name="secret" placeholder="your API token" required autofocus>
45
45
  <p class="hint">Paste the raw token or one already prefixed with <code>Bearer</code> &mdash; we add the prefix automatically if it is missing.</p>
46
46
  <button type="submit">Save credentials</button>
47
47
  </form>
48
- ${c()}`)}function q(e,t,n){return i(`
49
- ${s(`Credentials for ${e}`)}
50
- <p class="hint">Stored encrypted for this MCP session only (up to 4 hours) and sent to the API as the <code>${a(n)}</code> header. It never passes through the AI conversation.</p>
48
+ ${d()}`)}function H(e,t,o){return s(`
49
+ ${c(`Credentials for ${e}`)}
50
+ <p class="hint">Stored encrypted for this MCP session only (up to 4 hours) and sent to the API as the <code>${i(o)}</code> header. It never passes through the AI conversation.</p>
51
51
  <form method="post" action="/mcp/credentials" autocomplete="off">
52
- <input type="hidden" name="token" value="${a(t)}">
52
+ <input type="hidden" name="token" value="${i(t)}">
53
53
  <input type="hidden" name="mode" value="token">
54
- <label for="secret">${a(n)} value</label>
54
+ <label for="secret">${i(o)} value</label>
55
55
  <input type="password" id="secret" name="secret" placeholder="value" required autofocus>
56
56
  <button type="submit">Save credentials</button>
57
57
  </form>
58
- ${c()}`)}function H(e,t,n){const o=n.length>0?n.map(a).join(", "):"default scopes";return i(`
59
- ${s(`Credentials for ${e}`)}
58
+ ${d()}`)}function N(e,t,o){const n=o.length>0?o.map(i).join(", "):"default scopes";return s(`
59
+ ${c(`Credentials for ${e}`)}
60
60
  <p class="hint">Paste a token you already have, or let us exchange client credentials for one via OAuth2 client_credentials.</p>
61
61
  <div class="section">
62
62
  <h2>I have a token</h2>
63
63
  <form method="post" action="/mcp/credentials" autocomplete="off">
64
- <input type="hidden" name="token" value="${a(t)}">
64
+ <input type="hidden" name="token" value="${i(t)}">
65
65
  <input type="hidden" name="mode" value="token">
66
66
  <label for="secret">API token</label>
67
67
  <input type="password" id="secret" name="secret" placeholder="your API token" required autofocus>
@@ -71,9 +71,9 @@ ${s(`Credentials for ${e}`)}
71
71
  </div>
72
72
  <div class="section">
73
73
  <h2>Exchange client credentials</h2>
74
- <p class="hint">We request a token from the API on your behalf. Scopes: ${o}.</p>
74
+ <p class="hint">We request a token from the API on your behalf. Scopes: ${n}.</p>
75
75
  <form method="post" action="/mcp/credentials" autocomplete="off">
76
- <input type="hidden" name="token" value="${a(t)}">
76
+ <input type="hidden" name="token" value="${i(t)}">
77
77
  <input type="hidden" name="mode" value="client-credentials">
78
78
  <label for="client_id">Client ID</label>
79
79
  <input type="text" id="client_id" name="client_id" required>
@@ -82,20 +82,20 @@ ${s(`Credentials for ${e}`)}
82
82
  <button type="submit">Exchange &amp; save</button>
83
83
  </form>
84
84
  </div>
85
- ${c()}`)}function N(e,t){return i(`
86
- ${s(`Credentials for ${e}`)}
85
+ ${d()}`)}function U(e,t){return s(`
86
+ ${c(`Credentials for ${e}`)}
87
87
  <p class="hint">The value is stored encrypted for this MCP session only (up to 4 hours) and is sent to the API as the <code>Authorization</code> header. It never passes through the AI conversation.</p>
88
88
  <form method="post" action="/mcp/credentials" autocomplete="off">
89
- <input type="hidden" name="token" value="${a(t)}">
89
+ <input type="hidden" name="token" value="${i(t)}">
90
90
  <input type="hidden" name="mode" value="token">
91
91
  <label for="secret">Authorization header value</label>
92
92
  <input type="password" id="secret" name="secret" placeholder="Bearer &lt;your token&gt;" required autofocus>
93
93
  <button type="submit">Save credentials</button>
94
94
  </form>
95
- ${c()}`)}function w(e){return i(`
96
- ${s("Credentials saved")}
97
- <p>Credentials for ${a(e)} are set for this session. Return to your AI client and continue - confirm the elicitation prompt if it is still open.</p>
98
- ${c()}`)}function m(e){return new Response(i(`
99
- ${s("Exchange failed")}
100
- <p>We could not exchange your client credentials for a token for ${a(e)}. This link has been used up - go back to your AI client and request a new link, then try again.</p>
101
- ${c()}`),{status:502,headers:p})}function l(){return new Response(i(`${s("Link expired")}<p>This credentials link is invalid or has expired. Retry the request in your AI client to get a fresh link.</p>${c()}`),{status:404,headers:p})}var D=T;export{D as default,j as setCredentialsFetchImpl};
95
+ ${d()}`)}function x(e){return s(`
96
+ ${c("Credentials saved")}
97
+ <p>Credentials for ${i(e)} are set for this session. Return to your AI client and continue - confirm the elicitation prompt if it is still open.</p>
98
+ ${d()}`)}function h(e){return new Response(s(`
99
+ ${c("Exchange failed")}
100
+ <p>We could not exchange your client credentials for a token for ${i(e)}. This link has been used up - go back to your AI client and request a new link, then try again.</p>
101
+ ${d()}`),{status:502,headers:m})}function u(){return new Response(s(`${c("Link expired")}<p>This credentials link is invalid or has expired. Retry the request in your AI client to get a fresh link.</p>${d()}`),{status:404,headers:m})}var W=z;export{W as default,j as setCredentialsFetchImpl};
@@ -1 +1 @@
1
- import{fileURLToPath as L}from"node:url";import{dirname as q,join as c}from"node:path";import{logger as m}from"../../tools/notifiers/logger.js";import{reporter as w}from"../../tools/notifiers/reporter.js";import{isDefined as N}from"../../../utils/guards/is-defined.js";import{buildGraphqlDescriptions as G,filterIgnoredApiDescriptions as D,getCleanedUpApiDescriptions as H,isMcpInRedirects as I}from"./utils.js";import{getMcpMode as U,isMcpElicitationEnabled as j}from"./utils/mode.js";import{getDefaultToolSchemas as x}from"./docs-mcp/tool-schemas.js";import{EXECUTE_TOOL_SCHEMA as F}from"./codemode/tools/execute-schema.js";import{isGatewayCapabilityEnabled as V,isGatewayEligible as B}from"./gateway-mcp/eligibility.js";import{telemetryTraceStep as J}from"../../../cli/telemetry/helpers/trace-step.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as E,MCP_NOT_ALLOWED_TEMPLATE_ID as W}from"./constants.js";const M="mcp-docs-server-handler",X="/mcp",C="mcp-credentials-handler",k="/mcp/credentials",p=q(L(import.meta.url));async function z(){let o=!1;return{id:"mcp",async processContent(i,r){o||await J("build.plugin.mcp",async a=>{const s=await i.loadOpenApiDefinitions(r),d=await i.loadGraphqlDefinitions(r),n=await r.getConfig(),{mcp:t,redirects:l}=n;if(a?.setAttribute("config",JSON.stringify(t||{})),!(t?.hide||t?.docs?.hide)&&(I(l)&&await w.panicOnBuildContentError('The reserved route "/mcp" cannot be used in redirect configuration. Please remove any redirects involving "/mcp" from your redocly.yaml config.'),i.createTemplate(W,"@redocly/theme/layouts/McpNotAllowed"),!t?.docs?.hide)){m.info("Configuring MCP servers...");const _=c(p,"./handlers/docs-mcp-handler.js"),P=H(D(s,t?.docs?.ignore||[])),v=!!n?.access?.requiresLogin,f=n?.access?.rbac||{},R=V(t),g=new Map;for(const e of P){const b=e.definition.info?.title||"",O=e.definition.info?.version||"";g.set(e.relativePath,{name:b,description:e.definition.info?.description||"",version:O,servers:e.definition.servers||[],relativePath:e.relativePath,...R&&B(e.definition.info,e.relativePath,f)?{fetchEligible:!0}:{}})}const u=G(D(d,t?.docs?.ignore||[])),S=Object.values(n.products||{}).map(e=>e?.name).filter(N),y=n.l10n?.locales?.map(e=>e.code)??r.fs.localeFolders,A=x({products:S,locales:y,requiresLogin:v,rbac:f,hasGraphqlApis:u.length>0});i.addMcpTools(c(p,"./docs-mcp/tools/index.js"),A);const h=U(),T=j();h==="code"&&i.addMcpTools(c(p,"./codemode/tools/execute.js"),[F]),await K({actions:i,handlerPath:_,apiDescriptionsMap:Object.fromEntries(g),graphqlDescriptions:u,mcpConfig:t,mcpMode:h,elicitationEnabled:T}),o=!0}})},async afterRoutesCreated(i,r){const a=await r.getConfig(),{mcp:s}=a;i.setGlobalData({mcpData:{docs:{enabled:!s?.hide&&!s?.docs?.hide,name:s?.docs?.name||E}}})}}}var ce=z;async function K({actions:o,handlerPath:i,apiDescriptionsMap:r,graphqlDescriptions:a,mcpConfig:s,mcpMode:d,elicitationEnabled:n}){if(o.createRequestHandler(M,i),o.addApiRoute({slug:X,requestHandlerId:M,httpMethod:"all",getStaticData:async()=>({props:{config:{apiDescriptionsMap:r,graphqlDescriptions:a,mcpDocsServerName:s?.docs?.name||E,mcpMode:d,elicitationEnabled:n},serverOutDir:o.serverOutDir,tools:o.getMcpTools().map(({importPath:t,...l})=>l)}})}),m.info("Registered Docs MCP Server endpoint at /mcp"),n){const t=c(p,"./handlers/mcp-credentials-handler.js");o.createRequestHandler(C,t),o.addApiRoute({slug:k,requestHandlerId:C,httpMethod:"all",getStaticData:async()=>({props:{}})}),m.info("Registered MCP credentials endpoint at /mcp/credentials")}}export{C as MCP_CREDENTIALS_HANDLER_ID,M as MCP_DOCS_SERVER_HANDLER_ID,ce as default,z as mcpServerPlugin};
1
+ import{fileURLToPath as w}from"node:url";import{dirname as q,join as p}from"node:path";import{logger as f}from"../../tools/notifiers/logger.js";import{reporter as G}from"../../tools/notifiers/reporter.js";import{isDefined as N}from"../../../utils/guards/is-defined.js";import{buildGraphqlDescriptions as H,filterIgnoredApiDescriptions as E,getCleanedUpApiDescriptions as I,isMcpInRedirects as U}from"./utils.js";import{getMcpMode as j,isMcpElicitationEnabled as x}from"./utils/mode.js";import{getDefaultToolSchemas as F}from"./docs-mcp/tool-schemas.js";import{EXECUTE_TOOL_SCHEMA as B}from"./codemode/tools/execute-schema.js";import{isGatewayCapabilityEnabled as V,isGatewayEligible as J}from"./gateway-mcp/eligibility.js";import{resolveGatewayFetchLimits as W}from"./gateway-mcp/rate-limit/limits.js";import{telemetryTraceStep as X}from"../../../cli/telemetry/helpers/trace-step.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as M,MCP_NOT_ALLOWED_TEMPLATE_ID as k}from"./constants.js";const C="mcp-docs-server-handler",z="/mcp",P="mcp-credentials-handler",K="/mcp/credentials",d=q(w(import.meta.url));async function Q(){let o=!1;return{id:"mcp",async processContent(t,r){o||await X("build.plugin.mcp",async a=>{const n=await t.loadOpenApiDefinitions(r),l=await t.loadGraphqlDefinitions(r),s=await r.getConfig(),{mcp:i,redirects:c}=s;if(a?.setAttribute("config",JSON.stringify(i||{})),!(i?.hide||i?.docs?.hide)&&(U(c)&&await G.panicOnBuildContentError('The reserved route "/mcp" cannot be used in redirect configuration. Please remove any redirects involving "/mcp" from your redocly.yaml config.'),t.createTemplate(k,"@redocly/theme/layouts/McpNotAllowed"),!i?.docs?.hide)){f.info("Configuring MCP servers...");const m=p(d,"./handlers/docs-mcp-handler.js"),_=I(E(n,i?.docs?.ignore||[])),v=!!s?.access?.requiresLogin,g=s?.access?.rbac||{},y=V(i),u=new Map;for(const e of _){const O=e.definition.info?.title||"",L=e.definition.info?.version||"";u.set(e.relativePath,{name:O,description:e.definition.info?.description||"",version:L,servers:e.definition.servers||[],relativePath:e.relativePath,...y&&J(e.definition.info,e.relativePath,g)?{fetchEligible:!0}:{}})}const h=H(E(l,i?.docs?.ignore||[])),R=Object.values(s.products||{}).map(e=>e?.name).filter(N),S=s.l10n?.locales?.map(e=>e.code)??r.fs.localeFolders,b=F({products:R,locales:S,requiresLogin:v,rbac:g,hasGraphqlApis:h.length>0});t.addMcpTools(p(d,"./docs-mcp/tools/index.js"),b);const D=j(),A=x(),{budgetPerExec:T}=W();D==="code"&&t.addMcpTools(p(d,"./codemode/tools/execute.js"),[B]),await Y({actions:t,handlerPath:m,apiDescriptionsMap:Object.fromEntries(u),graphqlDescriptions:h,mcpConfig:i,mcpMode:D,elicitationEnabled:A,gatewayFetchBudgetPerExec:T}),o=!0}})},async afterRoutesCreated(t,r){const a=await r.getConfig(),{mcp:n}=a;t.setGlobalData({mcpData:{docs:{enabled:!n?.hide&&!n?.docs?.hide,name:n?.docs?.name||M}}})}}}var le=Q;async function Y({actions:o,handlerPath:t,apiDescriptionsMap:r,graphqlDescriptions:a,mcpConfig:n,mcpMode:l,elicitationEnabled:s,gatewayFetchBudgetPerExec:i}){if(o.createRequestHandler(C,t),o.addApiRoute({slug:z,requestHandlerId:C,httpMethod:"all",getStaticData:async()=>({props:{config:{apiDescriptionsMap:r,graphqlDescriptions:a,mcpDocsServerName:n?.docs?.name||M,mcpMode:l,elicitationEnabled:s,gatewayFetchBudgetPerExec:i},serverOutDir:o.serverOutDir,tools:o.getMcpTools().map(({importPath:c,...m})=>m)}})}),f.info("Registered Docs MCP Server endpoint at /mcp"),s){const c=p(d,"./handlers/mcp-credentials-handler.js");o.createRequestHandler(P,c),o.addApiRoute({slug:K,requestHandlerId:P,httpMethod:"all",getStaticData:async()=>({props:{}})}),f.info("Registered MCP credentials endpoint at /mcp/credentials")}}export{P as MCP_CREDENTIALS_HANDLER_ID,C as MCP_DOCS_SERVER_HANDLER_ID,le as default,Q as mcpServerPlugin};
@@ -1,11 +1,13 @@
1
1
  import type { McpToolSchema } from '../../../types';
2
2
  import type { McpToolContext, SkillMcpResource } from '../types.js';
3
+ import type { KvService } from '@redocly/config';
3
4
  import { type McpServerInstance, type McpSessionHooks } from './mcp-server.js';
4
- export declare function createDocsMcpServer({ name, tools, context, sessionHooks, skills, }: {
5
+ export declare function createDocsMcpServer({ name, tools, context, sessionHooks, skills, getKv, }: {
5
6
  name: string;
6
7
  tools: McpToolSchema[];
7
8
  context: McpToolContext;
8
9
  sessionHooks?: McpSessionHooks;
9
10
  skills?: SkillMcpResource[];
11
+ getKv: () => Promise<KvService>;
10
12
  }): Promise<McpServerInstance>;
11
13
  //# sourceMappingURL=docs-server.d.ts.map
@@ -1 +1 @@
1
- import{logger as O}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as _,MCP_TOOL_IS_AVAILABLE_KEY as u}from"../../../workers/mcp-tool-worker-pool.js";import{EXECUTE_TOOL_SCHEMA as b}from"../codemode/tools/execute-schema.js";import{EXECUTE_TOOL_NAME as p,DESCRIBE_TOOLS_TOOL_NAME as M,CODE_MODE_TOOL_NAMES as S,FETCH_TOOL_NAME as A}from"../codemode/constants.js";import{buildCodeModeInstructions as h,buildExecuteDescription as L,DESCRIBE_TOOLS_DESCRIPTION as g}from"../codemode/prompts.js";import{composeExecuteAnnotations as v,getActiveCapabilities as x}from"../codemode/capabilities/index.js";import{describeToolsSchema as D,describeTools as y}from"../codemode/tools/describe-tools.js";import{connectMcpServer as I,createMcpServer as k,createToolDispatch as w,extractSerializableContext as N}from"./mcp-server.js";async function $({name:E,tools:n,context:o,sessionHooks:d,skills:T=[]}){const a=o.mcpMode==="code",r=a?x(o):[],s=r.some(e=>e.id===A),{server:i,transport:f}=k(E,a?h(s):void 0,d),c=N(o),m=w(c,{getClientCapabilities:()=>i.server.getClientCapabilities()}),l=async e=>{try{return await _.exec(u,[{toolName:e,context:c}],{timeout:1e4})}catch(t){return O.error(`Failed to check MCP tool availability for "${e}": ${t instanceof Error?t.message:String(t)}`),!1}};if(a){const e=n.filter(t=>!S.has(t.name));i.tool(p,L(s),b.schema,v(r),m(p)),i.tool(M,g,D(e,r),y(e,l,r,o))}else await Promise.all(n.map(async e=>{const t=e.annotations||{title:e.name},C=i.tool(e.name,e.description,e.schema,t,m(e.name));await l(e.name)||C.disable()}));for(const e of T)i.registerResource(e.slug,e.uri,{title:e.name,description:e.description,mimeType:"text/markdown"},async()=>({contents:[{uri:e.uri,mimeType:"text/markdown",text:e.content}]}));return I(i,f)}export{$ as createDocsMcpServer};
1
+ import{logger as _}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as u,MCP_TOOL_IS_AVAILABLE_KEY as b}from"../../../workers/mcp-tool-worker-pool.js";import{EXECUTE_TOOL_SCHEMA as M}from"../codemode/tools/execute-schema.js";import{EXECUTE_TOOL_NAME as p,DESCRIBE_TOOLS_TOOL_NAME as S,CODE_MODE_TOOL_NAMES as A,FETCH_TOOL_NAME as h}from"../codemode/constants.js";import{buildCodeModeInstructions as L,buildExecuteDescription as g,DESCRIBE_TOOLS_DESCRIPTION as v}from"../codemode/prompts.js";import{composeExecuteAnnotations as x,getActiveCapabilities as D}from"../codemode/capabilities/index.js";import{describeToolsSchema as y,describeTools as I}from"../codemode/tools/describe-tools.js";import{connectMcpServer as k,createMcpServer as w,createToolDispatch as N,extractSerializableContext as P}from"./mcp-server.js";async function K({name:E,tools:n,context:o,sessionHooks:d,skills:T=[],getKv:f}){const a=o.mcpMode==="code",r=a?D(o):[],s=r.some(e=>e.id===h),{server:i,transport:C}=w(E,a?L(s):void 0,d),c=P(o),m=N(c,{getClientCapabilities:()=>i.server.getClientCapabilities(),getKv:f}),l=async e=>{try{return await u.exec(b,[{toolName:e,context:c}],{timeout:1e4})}catch(t){return _.error(`Failed to check MCP tool availability for "${e}": ${t instanceof Error?t.message:String(t)}`),!1}};if(a){const e=n.filter(t=>!A.has(t.name));i.tool(p,g(s),M.schema,x(r),m(p)),i.tool(S,v,y(e,r),I(e,l,r,o))}else await Promise.all(n.map(async e=>{const t=e.annotations||{title:e.name},O=i.tool(e.name,e.description,e.schema,t,m(e.name));await l(e.name)||O.disable()}));for(const e of T)i.registerResource(e.slug,e.uri,{title:e.name,description:e.description,mimeType:"text/markdown"},async()=>({contents:[{uri:e.uri,mimeType:"text/markdown",text:e.content}]}));return k(i,C)}export{K as createDocsMcpServer};
@@ -2,6 +2,7 @@ import { McpServer } from '@redocly/mcp-typescript-sdk/server/mcp.js';
2
2
  import { StreamableHTTPServerTransport } from '@redocly/mcp-typescript-sdk/server/streamableHttp.js';
3
3
  import type { ToolCallback } from '@redocly/mcp-typescript-sdk/server/mcp.js';
4
4
  import type { McpToolContext } from '../types.js';
5
+ import type { KvService } from '@redocly/config';
5
6
  export type McpServerInstance = {
6
7
  server: McpServer;
7
8
  transport: StreamableHTTPServerTransport;
@@ -20,6 +21,7 @@ export type ToolDispatchDeps = {
20
21
  getClientCapabilities: () => {
21
22
  elicitation?: unknown;
22
23
  } | undefined;
24
+ getKv: () => Promise<KvService>;
23
25
  };
24
26
  /** Build the tool callback that ships a tool call to the worker pool. */
25
27
  export declare function createToolDispatch(serializableContext: McpToolContext, deps?: ToolDispatchDeps): (toolName: string) => ToolCallback<Record<string, unknown>>;
@@ -1 +1 @@
1
- import{randomUUID as I}from"node:crypto";import{McpServer as S}from"@redocly/mcp-typescript-sdk/server/mcp.js";import{StreamableHTTPServerTransport as g}from"@redocly/mcp-typescript-sdk/server/streamableHttp.js";import{logger as M}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as h,MCP_TOOL_WORKER_KEY as q}from"../../../workers/mcp-tool-worker-pool.js";import{secretStore as E}from"../gateway-mcp/secret-store.js";import{readAuthRequiredMeta as T,stripAuthRequiredMeta as b}from"../gateway-mcp/upstream-auth.js";import{runUpstreamCredentialElicitation as w}from"../gateway-mcp/elicitation.js";import{resolveUpstreamAuthScheme as U}from"../gateway-mcp/upstream-auth-scheme.js";const v=6e4,p=new Map;function D(e,t){const i=p.get(e);if(i)return i;const r=t().finally(()=>{p.delete(e)});return p.set(e,r),r}function G(e,t,i){const r=new S({name:e,version:new Date().toISOString().slice(0,10)},{capabilities:{logging:{}},...t?{instructions:t}:{}}),s=new g(i?{sessionIdGenerator:()=>I(),onsessioninitialized:i.onInitialized,onsessionclosed:i.onClosed}:{sessionIdGenerator:void 0});return{server:r,transport:s}}async function K(e,t){return await e.connect(t),{server:e,transport:t,cleanup:async()=>{t.close()}}}function L(e,t){return i=>async(r,s)=>{M.info(`MCP tool called: ${i}`);const l=async()=>{const a=e.elicitationEnabled&&s.sessionId?E.getSecretsForSession(s.sessionId):void 0,c={toolName:i,args:r,context:{...e,upstreamSecrets:a},extra:O(s)};return await h.exec(q,[c],{timeout:v})};let n=await l();const o=T(n);if(o.length>0&&e.elicitationEnabled&&s.sessionId){const a=s.sessionId,c=await U(e,o[0].api),u=await D(`${a}:${o[0].api}`,()=>w({sendRequest:(d,m,f)=>s.sendRequest(d,m,f),clientSupportsElicitation:!!t?.getClientCapabilities()?.elicitation,sessionId:a,baseUrl:e.baseUrl??"",events:o,auth:c}));u.status==="accepted"?n=await l():u.status==="fallback"&&(n={...n,content:[...n.content,{type:"text",text:u.text}]})}return b(n)}}function O(e){return{sessionId:e.sessionId,authInfo:e.authInfo,requestId:e.requestId,requestInfo:e.requestInfo,_meta:e._meta}}function W(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,upstreamSecrets:e.upstreamSecrets}}export{K as connectMcpServer,G as createMcpServer,L as createToolDispatch,W as extractSerializableContext};
1
+ import{randomUUID as w}from"node:crypto";import{McpServer as h}from"@redocly/mcp-typescript-sdk/server/mcp.js";import{StreamableHTTPServerTransport as E}from"@redocly/mcp-typescript-sdk/server/streamableHttp.js";import{logger as d}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as M,MCP_TOOL_WORKER_KEY as q}from"../../../workers/mcp-tool-worker-pool.js";import{secretStore as v}from"../gateway-mcp/secret-store.js";import{readAuthRequiredMeta as y,stripAuthRequiredMeta as T}from"../gateway-mcp/upstream-auth.js";import{runUpstreamCredentialElicitation as b}from"../gateway-mcp/elicitation.js";import{resolveUpstreamAuthScheme as U}from"../gateway-mcp/upstream-auth-scheme.js";const D=6e4,p=new Map;function O(e,t){const r=p.get(e);if(r)return r;const s=t().finally(()=>{p.delete(e)});return p.set(e,s),s}function z(e,t,r){const s=new h({name:e,version:new Date().toISOString().slice(0,10)},{capabilities:{logging:{}},...t?{instructions:t}:{}}),i=new E(r?{sessionIdGenerator:()=>w(),onsessioninitialized:r.onInitialized,onsessionclosed:r.onClosed}:{sessionIdGenerator:void 0});return{server:s,transport:i}}async function G(e,t){return await e.connect(t),{server:e,transport:t,cleanup:async()=>{t.close()}}}function L(e,t){return r=>async(s,i)=>{d.info(`MCP tool called: ${r}`);const m=async()=>{if(!(!e.elicitationEnabled||!i.sessionId||!t?.getKv))try{const n=await t.getKv();return await v.getSecretsForSession(n,i.sessionId)}catch(n){d.warn(`gateway mcp secret store error, continuing unauthenticated: ${String(n)}`);return}},l=async()=>{const n=await m(),c={toolName:r,args:s,context:{...e,upstreamSecrets:n},extra:_(i)};return await M.exec(q,[c],{timeout:D})};let a=await l();const o=y(a);if(o.length>0&&e.elicitationEnabled&&i.sessionId&&t?.getKv){const n=i.sessionId,c=await t.getKv(),f=await U(e,o[0].api),u=await O(`${n}:${o[0].api}`,()=>b({sendRequest:(g,I,S)=>i.sendRequest(g,I,S),clientSupportsElicitation:!!t?.getClientCapabilities()?.elicitation,sessionId:n,baseUrl:e.baseUrl??"",events:o,auth:f,kv:c}));u.status==="accepted"?a=await l():u.status==="fallback"&&(a={...a,content:[...a.content,{type:"text",text:u.text}]})}return T(a)}}function _(e){return{sessionId:e.sessionId,authInfo:e.authInfo,requestId:e.requestId,requestInfo:e.requestInfo,_meta:e._meta}}function W(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,upstreamSecrets:e.upstreamSecrets}}export{G as connectMcpServer,z as createMcpServer,L as createToolDispatch,W as extractSerializableContext};
@@ -1 +1 @@
1
- import{logger as i}from"../../../tools/notifiers/logger.js";import{secretStore as a}from"../gateway-mcp/secret-store.js";const c=1800*1e3,o=60*1e3,r=500;class h{#t=new Map;#e;#s;constructor(t={}){this.#e=t.maxSessions??r,this.#s=t.onEvict??(s=>a.deleteSession(s)),setInterval(()=>{this.#i()},o).unref?.()}get(t){const e=this.#t.get(t);if(e)return e.lastAccess=Date.now(),this.#t.delete(t),this.#t.set(t,e),e.instance}set(t,e){const s=this.#t.get(t);if(this.#t.delete(t),this.#t.set(t,{instance:e,lastAccess:Date.now()}),s&&s.instance!==e&&this.#n(t,s.instance),this.#t.size>this.#e){const n=this.#t.keys().next().value;n!==void 0&&this.close(n)}}async close(t){const e=this.#t.get(t);e&&(this.#t.delete(t),this.#s(t),await this.#n(t,e.instance))}async#n(t,e){try{await e.cleanup()}catch(s){i.warn(`Failed to clean up MCP session ${t.slice(0,8)}\u2026: ${s instanceof Error?s.message:String(s)}`)}}async#i(){const t=Date.now()-c;for(const[e,s]of this.#t)s.lastAccess<=t&&await this.close(e)}}const g=new h;export{h as McpSessionManager,g as mcpSessionManager};
1
+ import{logger as i}from"../../../tools/notifiers/logger.js";const a=1800*1e3,c=60*1e3,o=500;class r{#t=new Map;#e;#s;constructor(t={}){this.#e=t.maxSessions??o,this.#s=t.onEvict??(()=>{}),setInterval(()=>{this.#i()},c).unref?.()}get(t){const e=this.#t.get(t);if(e)return e.lastAccess=Date.now(),this.#t.delete(t),this.#t.set(t,e),e.instance}set(t,e){const s=this.#t.get(t);if(this.#t.delete(t),this.#t.set(t,{instance:e,lastAccess:Date.now()}),s&&s.instance!==e&&this.#n(t,s.instance),this.#t.size>this.#e){const n=this.#t.keys().next().value;n!==void 0&&this.close(n)}}async close(t){const e=this.#t.get(t);e&&(this.#t.delete(t),this.#s(t),await this.#n(t,e.instance))}async#n(t,e){try{await e.cleanup()}catch(s){i.warn(`Failed to clean up MCP session ${t.slice(0,8)}\u2026: ${s instanceof Error?s.message:String(s)}`)}}async#i(){const t=Date.now()-a;for(const[e,s]of this.#t)s.lastAccess<=t&&await this.close(e)}}const f=new r;export{r as McpSessionManager,f as mcpSessionManager};
@@ -29,6 +29,7 @@ export type McpDocsStaticDataPropsConfig = {
29
29
  graphqlDescriptions: GraphqlDescriptionInfo[];
30
30
  mcpMode: McpMode;
31
31
  elicitationEnabled: boolean;
32
+ gatewayFetchBudgetPerExec: number;
32
33
  };
33
34
  export type McpDocsStaticData = PageStaticData & {
34
35
  props: {
@@ -95,6 +96,8 @@ export type McpToolContext = Omit<ApiFunctionsContext, 'telemetry' | 'getKv' | k
95
96
  upstreamAuthorization?: string;
96
97
  mcpMode?: McpMode;
97
98
  elicitationEnabled?: boolean;
99
+ gatewayFetchBudgetPerExec?: number;
100
+ clientIp?: string;
98
101
  upstreamSecrets?: Record<string, StoredSecret>;
99
102
  };
100
103
  export type McpToolHandler<TArgs extends Record<string, unknown> = Record<string, unknown>> = (args: TArgs, context: McpToolContext, extra: McpToolExtra) => Promise<McpToolWorkerResponse>;
@@ -1 +1 @@
1
- import{EntitlementsProvider as e}from"../../../entitlements/entitlements-provider.js";import{envConfig as n}from"../../../config/env-config.js";function t(){return e.instance().entitlements?.mcpMode==="code"}function o(){return!!e.instance().canAccessFeature("mcpGateway")&&t()}function i(){return(n.REDOCLY_EXP_MCP_ELICITATION_ENABLED??!1)&&o()}function a(){return t()?"code":"tools"}export{a as getMcpMode,t as isMcpCodeModeEnabled,i as isMcpElicitationEnabled,o as isMcpGatewayEnabled};
1
+ import{EntitlementsProvider as e}from"../../../entitlements/entitlements-provider.js";import{envConfig as n}from"../../../config/env-config.js";function t(){return e.instance().entitlements?.mcpMode==="code"}function o(){return!!e.instance().canAccessFeature("mcpGateway")&&t()}function i(){return(n.REDOCLY_EXP_MCP_ELICITATION_ENABLED??!0)&&o()}function a(){return t()?"code":"tools"}export{a as getMcpMode,t as isMcpCodeModeEnabled,i as isMcpElicitationEnabled,o as isMcpGatewayEnabled};
@@ -0,0 +1,8 @@
1
+ import type { McpServerType } from '../types.js';
2
+ /** CloudEvent resource attributes shared by every `mcpServer` telemetry event. */
3
+ export declare function serverAttributes(serverType: McpServerType): {
4
+ id: "docs";
5
+ object: 'mcpServer';
6
+ uri: string;
7
+ };
8
+ //# sourceMappingURL=telemetry-attributes.d.ts.map
@@ -0,0 +1 @@
1
+ function e(r){return{id:r,object:"mcpServer",uri:`urn:redocly:realm:mcp:server:${r}`}}export{e as serverAttributes};
@@ -1 +1 @@
1
- import{telemetry as c}from"../../../telemetry/index.js";function l(r){return{id:r,object:"mcpServer",uri:`urn:redocly:realm:mcp:server:${r}`,server_type:r}}function s({tool:r,mode:e,serverType:o,durationMs:t,outputLength:n}){c.sendMcpToolCalledMessage([{...l(o),tool:r,mode:e,output_length:n,duration_ms:t}])}function i({tool:r,mode:e,serverType:o,durationMs:t,message:n,stack:p=""}){c.sendMcpErrorMessage([{...l(o),tool:r,message:n,stack:p,mode:e,duration_ms:t}])}export{s as reportMcpToolCalled,i as reportMcpToolError};
1
+ import{telemetry as s}from"../../../telemetry/index.js";import{serverAttributes as l}from"../utils/telemetry-attributes.js";function u({tool:r,mode:o,serverType:e,durationMs:t,outputLength:p}){s.sendMcpToolCalledMessage([{...l(e),tool:r,mode:o,output_length:p,duration_ms:t}])}function a({tool:r,mode:o,serverType:e,durationMs:t,message:p,stack:n=""}){s.sendMcpErrorMessage([{...l(e),tool:r,message:p,stack:n,mode:o,duration_ms:t}])}export{u as reportMcpToolCalled,a as reportMcpToolError};
@@ -1 +1 @@
1
- import{REDOCLY_TEAMS_RBAC as i}from"@redocly/config";import{combineUrls as p}from"@redocly/theme/core/utils";import{FEEDBACK_TYPES as c}from"../../../constants/common.js";import{normalizeRouteSlug as g}from"../../../utils/path/normalize-route-slug.js";function u(t){const{contentItems:r,sidebarItems:a,routeSlug:o,navItem:s}=t;let b=!0;for(const e of r){let n;switch(e.type){case"group":a.push({type:"separator",label:e.name,[i]:e[i]}),u({...t,contentItems:e.items,sidebarItems:a});break;case"tag":n=p(o,e.href);const m={type:"group",label:e.name,items:[],link:n,routeSlug:n,[i]:e[i]};a.push(m),u({routeSlug:o,contentItems:e.items,sidebarItems:m.items||[],navItem:s});break;case"operation":case"schema":case"rsrc":case"prompt":case"tool":e.isWebhook&&b&&(a.push({type:"separator",label:"Webhooks",variant:"secondary"}),b=!1),n=p(o,e.href),a.push({type:"link",label:e.name,httpVerb:e.type==="operation"?e.httpVerb:e.type,routeSlug:g(n),badges:e.badges,link:n,deprecated:e.deprecated,isAdditionalOperation:e.isAdditionalOperation,[i]:e[i]});break;case"section":if(e.depth===-1)continue;n=p(o,e.href);const l=e.href==="/",f={type:e.items.length?"group":"link",label:l&&s?.label?s.label:e.name,labelTranslationKey:s?.labelTranslationKey,routeSlug:n.split("#")[0]===o?o:g(n),link:n,items:[],...l&&s?.icon&&{icon:typeof s.icon=="object"?s.icon.srcSet:s.icon},...l&&s?.badges&&{badges:s.badges}};a.push(f),e.items.length&&u({routeSlug:o,contentItems:e.items,sidebarItems:f.items});break}}}function S({item:t}){return t.id===""?!0:t.type!=="group"||t.description!==""}const I=t=>(typeof t=="string"?t:t?.raw)?.replace(/\[(.*?)\][\[\(].*?[\]\)]/g,"$1")||"";function O(t){if(!t)return;let r="";const{type:a,settings:o}=t;switch(a){case c.RATING:r="Rate this section";break;case c.SENTIMENT:r="Was this section helpful?";break;case c.MOOD:r="Was this section helpful?";break;case c.COMMENT:r="Share your feedback about this section";break;case c.SCALE:r="How would you rate this section?";break}return{...t,settings:{...o,label:o?.label||r}}}function A(t){return(typeof t=="string"?t:t?.raw||"").replace(/<[^>]*>/g,"").replace(/```([\s\S]*?)```/g,"$1").replace(/`([^`]+)`/g,"$1").replace(/\*\*(.*?)\*\*/g,"$1").replace(/\*(.*?)\*/g,"$1").trim()}function E(t){if(t?.oneOf)return t;if(t?.items?.oneOf)return t.items}export{u as convertOpenAPIDocs2Sidebar,E as getOneOfSchema,O as normalizeFeedbackOptions,I as removeMarkdownLinks,S as shouldAddRoute,A as stripFormatting};
1
+ import{REDOCLY_TEAMS_RBAC as i}from"@redocly/config";import{combineUrls as l}from"@redocly/theme/core/utils";import{FEEDBACK_TYPES as c}from"../../../constants/common.js";import{normalizeRouteSlug as f}from"../../../utils/path/normalize-route-slug.js";function u(t){const{contentItems:r,sidebarItems:a,routeSlug:o,navItem:s}=t;let m=!0;for(const e of r){let n;switch(e.type){case"group":a.push({type:"separator",label:e.name,hideWhenEmpty:!0,[i]:e[i]}),u({...t,contentItems:e.items,sidebarItems:a});break;case"tag":n=l(o,e.href);const b={type:"group",label:e.name,items:[],link:n,routeSlug:n,[i]:e[i]};a.push(b),u({routeSlug:o,contentItems:e.items,sidebarItems:b.items||[],navItem:s});break;case"operation":case"schema":case"rsrc":case"prompt":case"tool":e.isWebhook&&m&&(a.push({type:"separator",label:"Webhooks",variant:"secondary",hideWhenEmpty:!0}),m=!1),n=l(o,e.href),a.push({type:"link",label:e.name,httpVerb:e.type==="operation"?e.httpVerb:e.type,routeSlug:f(n),badges:e.badges,link:n,deprecated:e.deprecated,isAdditionalOperation:e.isAdditionalOperation,[i]:e[i]});break;case"section":if(e.depth===-1)continue;n=l(o,e.href);const p=e.href==="/",d={type:e.items.length?"group":"link",label:p&&s?.label?s.label:e.name,labelTranslationKey:s?.labelTranslationKey,routeSlug:n.split("#")[0]===o?o:f(n),link:n,items:[],...p&&s?.icon&&{icon:typeof s.icon=="object"?s.icon.srcSet:s.icon},...p&&s?.badges&&{badges:s.badges}};a.push(d),e.items.length&&u({routeSlug:o,contentItems:e.items,sidebarItems:d.items});break}}}function S({item:t}){return t.id===""?!0:t.type!=="group"||t.description!==""}const I=t=>(typeof t=="string"?t:t?.raw)?.replace(/\[(.*?)\][\[\(].*?[\]\)]/g,"$1")||"";function E(t){if(!t)return;let r="";const{type:a,settings:o}=t;switch(a){case c.RATING:r="Rate this section";break;case c.SENTIMENT:r="Was this section helpful?";break;case c.MOOD:r="Was this section helpful?";break;case c.COMMENT:r="Share your feedback about this section";break;case c.SCALE:r="How would you rate this section?";break}return{...t,settings:{...o,label:o?.label||r}}}function O(t){return(typeof t=="string"?t:t?.raw||"").replace(/<[^>]*>/g,"").replace(/```([\s\S]*?)```/g,"$1").replace(/`([^`]+)`/g,"$1").replace(/\*\*(.*?)\*\*/g,"$1").replace(/\*(.*?)\*/g,"$1").trim()}function A(t){if(t?.oneOf)return t;if(t?.items?.oneOf)return t.items}export{u as convertOpenAPIDocs2Sidebar,A as getOneOfSchema,E as normalizeFeedbackOptions,I as removeMarkdownLinks,S as shouldAddRoute,O as stripFormatting};
@@ -11,6 +11,7 @@ export declare function getScopeItemsForResource(rbacConfig: RbacConfig | undefi
11
11
  fsPath?: string;
12
12
  slug?: string;
13
13
  }): RbacScopeItems;
14
+ export declare function hasScopeAccess(scopeItems: RbacScopeItems, teams: string[], email?: string): boolean;
14
15
  export declare function canAccessFeature(feature: RbacFeatures, auth: AuthDetails, rbacConfig?: RbacConfig, requiresLogin?: boolean): boolean;
15
16
  export declare function isResourcePubliclyAccessible(resource: {
16
17
  slug: string;
@@ -140,6 +141,9 @@ export declare const normalizeRbacConfig: (rbacConfig: RbacConfig) => {
140
141
  aiSearch?: {
141
142
  [x: string]: string;
142
143
  } | undefined;
144
+ mcp?: {
145
+ [x: string]: string;
146
+ } | undefined;
143
147
  } | undefined;
144
148
  reunite?: {
145
149
  [x: string]: string;
@@ -1 +1 @@
1
- import y from"path";import T from"picomatch";import"../node-crypto-polyfill.js";import{REDOCLY_TEAMS_RBAC as L,REDOCLY_ROUTE_RBAC as R}from"@redocly/config";import{ServerRoutes as w}from"../../constants/api.js";import{DEFAULT_ANONYMOUS_VISITOR_TEAM as x,PUBLIC_RBAC_SCOPE_ITEM as A,RBAC_ALL_OTHER_TEAMS as u,DEFAULT_RBAC_SCOPE as _}from"../../constants/common.js";import{DEPRECATED_PUBLIC_API_DEFINITIONS_FOLDER as I,PUBLIC_API_DEFINITIONS_FOLDER as j,PUBLIC_ASSETS_FOLDER as N}from"../constants/common.js";import{removeTrailingSlash as k}from"../../utils/url/remove-trailing-slash.js";import{removeLeadingSlash as B}from"../../utils/url/remove-leading-slash.js";import{parsePathVersions as b}from"../../utils/path/parse-path-versions.js";import{reporter as M}from"../tools/notifiers/reporter.js";import{bold as U}from"../tools/notifiers/helpers/colors.js";import{shaDirPathShort as W}from"../utils/crypto/sha-dir-path-short.js";import{isTruthy as $}from"../../utils/guards/is-truthy.js";import{canExpandConfig as v,expandRbacConfig as Y,getTeamFolderDefaults as z,parseTeamFoldersTemplate as K,parseTeamNameTemplate as H}from"./rbac-expand.js";import{getUserParamsFromCookies as G}from"../web-server/auth.js";import{getDeeperGlobPattern as V}from"./globs.js";import{EntitlementsProvider as J}from"../entitlements/entitlements-provider.js";const C=["NONE","READ","TRIAGE","WRITE","MAINTAIN","ADMIN"],Q=new Set(["x-parsed-md-description","x-parsed-md-summary"]);function xt(t,e){const r=C.indexOf(t.toUpperCase()),n=C.indexOf(e.toUpperCase());return r>n?t:e}const E={};function O(t,e){if(!t?.content)return A;const r=t.content,{slug:n,fsPath:o}=e;if(!n&&!o)return A;const s=f=>{const d=`slug:${f}`,p=E[d]??T(f);E[d]=p;const g=`fsPath:${f}`,S=E[g]??T(B(f));return E[g]=S,!!(n&&p(n))||!!(o&&S(o))};if(et(n||o||"")&&Object.keys(r).filter(p=>s(p)).length===0)return r[_]||A;const i=Object.keys(r).filter(f=>s(f));if(i.length==0)return A;const l=i.map(f=>T.scan(f,{tokens:!0,parts:!0}));let h=l[0];for(let f=1;f<l.length;f++)h=V(h,l[f]);return r[h.input]}function Dt(t,e,r={},n=!1){if(n&&Object.keys(r).length===0)return e.isAuthenticated;const o=r.features?.[t];return o?e.teams.some(s=>o[s]&&o[s].toLowerCase()!=="none"):!0}function gt(t,e){return P(t,{isAuthenticated:!1,teams:[x]},e.access?.rbac||{},e.access?.requiresLogin||!1)}function P(t,e={},r={},n=!1){if(t.slug&&typeof t.slug=="string"&&Object.values(w).some(i=>{const l=i.split(":")[0].replace(/\/$/,"");return t.slug===l||t.slug?.startsWith(i)})||t.slug==="/mcp"&&J.instance().canAccessFeature("mcp"))return!0;if(n&&Object.keys(r).length===0)return!!e.isAuthenticated;const o=Y(r,e.teams||[]),s=t[L]||O(o,t[R]||{});if(Object.keys(s||{}).length===0)return!1;if(Object.keys(s).length===1&&s[u]&&s[u].toLowerCase()!=="none")return!0;const c=(e?.email?[...e?.teams||[],e?.email]:e?.teams)||[],a=[];for(const i of c??[])s[i]?a.push(s[i]):s[u]&&i!==e?.email&&a.push(s[u]);return a.length?a.some(i=>i.toLowerCase()!=="none"):!1}function St(t,e,r,n){if(!t.startsWith(j)&&!t.startsWith(I))return!0;const o=t.replace(new RegExp(`^${j}/`),"").replace(new RegExp(`^${I}/`),""),c=o==="."?"":o,a={[R]:{slug:t,fsPath:c},slug:t};return P(a,n,e,r)}function Lt(t,e,r,n,o){if(!t.startsWith(N))return!0;const s=t.match(/.*\..{64}\.([A-Fa-f0-9]{8})\.[^\.]+$/)?.[1];if(!s)return!0;const c=n[s];if(!c)return!0;const{base:a,ext:i}=y.parse(t),l=a.split(".")[0],h=i.split(".").join(""),d=c==="."?"":c,p={[R]:{slug:t,fsPath:y.posix.join(d,`${l}.${h}`)},slug:t};return P(p,o,e,r)}async function _t(t,e){const{isAuthenticated:r=!1,idpAccessToken:n,federatedAccessToken:o,federatedIdToken:s,...c}=await G(t,e),{teams:a=[]}=c;let i;return r?i=a.filter(l=>l!==x):i=[x],{isAuthenticated:r,idpAccessToken:n,teams:i,claims:c}}function F(t,e,r={},n=!1){if(!t)return t;if(Array.isArray(t)){const o=[];for(const s of t){const c=F(s,e,r,n);c!==void 0&&o.push(c)}return tt(o)}if(typeof t=="object"){if(!P(t,e,r,n))return;let o=!1;const s={};for(const c in t){if(c===L||c===R)continue;if(Q.has(c)){s[c]=t[c];continue}const a=F(t[c],e,r,n);if(c==="items"&&Array.isArray(a)&&a.length===0&&t[c].length!==0){o=!0;continue}a!==void 0&&(s[c]=a),c==="paths"&&X(a)&&Z(a)}return o?void 0:s}return t}function It(t){return typeof t=="string"?t.split(" ").filter(Boolean):Array.isArray(t)?t.map(e=>e.toString()):[]}function jt(t,e){if(!e)return;const r=e.content;if(!r)return e;const n=Object.entries(r).flatMap(([s,c])=>s===_?[[s,c]]:[[s,c],...t.localeFolders.map(a=>[s.startsWith("/")?`/${a.toLocaleLowerCase()}${s}`:y.posix.join(t.localizationFolder,a,s),c])]),o=Object.fromEntries(n);return{...e,content:o}}async function Ct(t,e){if(!e)return{};const r={},n=new Set((await t.scan()).flatMap(({relativePath:o})=>{const{versionFolderPath:s}=b(o)||{},c=y.dirname(o);return s?[s,c]:c}));for(const o of n)r[W(o)]=o;return r}const m=t=>typeof t=="object"&&t!==null&&!Array.isArray(t);function X(t){return m(t)&&Object.keys(t).length>0}function Z(t){for(const e of Object.keys(t)){const r=t[e];m(r)&&Object.keys(r).length===0&&delete t[e]}}function q(t){return m(t)&&t.type==="separator"}function tt(t){const e=[];let r;for(const n of t){if(q(n)){r=n;continue}r&&(e.push(r),r=void 0),e.push(n)}return e}function et(t){return t?t.split("/").filter(Boolean).some(r=>r.startsWith(".")):!1}const rt=t=>{if(t&&m(t)&&("content"in t&&m(t.content)||"reunite"in t&&m(t.reunite)||"features"in t&&m(t.features)||t.teamFolders&&t.teamNamePatterns)){const e=Object.values(t.content||{});if(e.length===0)return!0;if(e.every(m))return e.every(r=>Object.values(r).every(n=>typeof n=="string"))}return!1},Ft=async t=>{if(t){if(Object.keys(t).length===0)return{};if(rt(t))return nt(t);await M.panicOnContentError(`You are using an incorrect format of ${U("rbac:")} configuration. See: https://redocly.com/docs/realm/access`)}},nt=t=>{const e={...t};if(e.content){const r={};for(const n in e.content)if(e.content[n]!==void 0){const o=k(n);r[o]=e.content[n]}e.content=r}return e};function wt(t,e){const r=t.fsPath,n=t.slug,o=[];if(v(e)&&(r||n)){const s=[r,n].filter($),c=K(e,s);if(c){const a=e?.teamNamePatterns?.map(l=>l.replace("{teamPathSegment}",c.teamPathSegment).replace("{projectRole}","read"))??[];o.push(...a);const i=O({content:{...z(e),...e.content}},t);o.push(...D(i))}else{const a=O(e,t);o.push(...D(a))}}else{const s=O(e,t);o.push(...D(s))}return ot(e,o)}function D(t){if(!t)return[];const e=[],r=u in t?{authenticated:t[u],anonymous:t[u]}:{};for(const[n,o]of Object.entries({...r,...t}))o.toLowerCase()!=="none"&&n!==u&&e.push(n);return e}function ot(t,e){return e.map(n=>H(t,n)??{teamName:n}).map(n=>n.projectRole&&n.projectRole!=="READ"?n.teamName?.toLowerCase().replace(n.projectRole?.toLowerCase?.()??"","read")??"":n.teamName?.toLowerCase()??"")}export{C as PROJECT_ROLES_ORDERED_BY_ACCESS_LEVEL,jt as applyL10nToRbacConfig,Lt as canAccessAsset,Dt as canAccessFeature,P as canAccessResource,St as canDownloadApiDefinition,ot as expandTeamsForRead,D as extractTeamsFromScopeItems,F as filterDataByAccessDeep,_t as getAuthDetailsFromCookies,xt as getHigherRole,wt as getRbacTeamsListForResource,O as getScopeItemsForResource,rt as isRbacConfigValid,gt as isResourcePubliclyAccessible,nt as normalizeRbacConfig,Ft as parseRbacConfig,It as parseTeamClaimToArray,Ct as resolveDirectoryHashes};
1
+ import y from"path";import P from"picomatch";import"../node-crypto-polyfill.js";import{REDOCLY_TEAMS_RBAC as _,REDOCLY_ROUTE_RBAC as A}from"@redocly/config";import{ServerRoutes as k}from"../../constants/api.js";import{DEFAULT_ANONYMOUS_VISITOR_TEAM as x,PUBLIC_RBAC_SCOPE_ITEM as R,RBAC_ALL_OTHER_TEAMS as l,DEFAULT_RBAC_SCOPE as L}from"../../constants/common.js";import{DEPRECATED_PUBLIC_API_DEFINITIONS_FOLDER as j,PUBLIC_API_DEFINITIONS_FOLDER as I,PUBLIC_ASSETS_FOLDER as w}from"../constants/common.js";import{removeTrailingSlash as B}from"../../utils/url/remove-trailing-slash.js";import{removeLeadingSlash as b}from"../../utils/url/remove-leading-slash.js";import{parsePathVersions as M}from"../../utils/path/parse-path-versions.js";import{reporter as U}from"../tools/notifiers/reporter.js";import{bold as W}from"../tools/notifiers/helpers/colors.js";import{shaDirPathShort as $}from"../utils/crypto/sha-dir-path-short.js";import{isTruthy as v}from"../../utils/guards/is-truthy.js";import{canExpandConfig as Y,expandRbacConfig as z,getTeamFolderDefaults as K,parseTeamFoldersTemplate as H,parseTeamNameTemplate as G}from"./rbac-expand.js";import{getUserParamsFromCookies as V}from"../web-server/auth.js";import{getDeeperGlobPattern as J}from"./globs.js";import{EntitlementsProvider as Q}from"../entitlements/entitlements-provider.js";const C=["NONE","READ","TRIAGE","WRITE","MAINTAIN","ADMIN"],X=new Set(["x-parsed-md-description","x-parsed-md-summary"]);function Pt(t,e){const r=C.indexOf(t.toUpperCase()),n=C.indexOf(e.toUpperCase());return r>n?t:e}const E={};function T(t,e){if(!t?.content)return R;const r=t.content,{slug:n,fsPath:s}=e;if(!n&&!s)return R;const o=a=>{const d=`slug:${a}`,p=E[d]??P(a);E[d]=p;const g=`fsPath:${a}`,S=E[g]??P(b(a));return E[g]=S,!!(n&&p(n))||!!(s&&S(s))};if(tt(n||s||"")&&Object.keys(r).filter(p=>o(p)).length===0)return r[L]||R;const f=Object.keys(r).filter(a=>o(a));if(f.length==0)return R;const u=f.map(a=>P.scan(a,{tokens:!0,parts:!0}));let h=u[0];for(let a=1;a<u.length;a++)h=J(h,u[a]);return r[h.input]}function F(t,e,r){const n=r?[...e,r]:e,s=[];for(const o of n)t[o]?s.push(t[o]):t[l]&&o!==r&&s.push(t[l]);return s.some(o=>o.toLowerCase()!=="none")}function xt(t,e,r={},n=!1){if(n&&Object.keys(r).length===0)return e.isAuthenticated;const s=r.features?.[t];return s?F(s,e.teams):!0}function Dt(t,e){return O(t,{isAuthenticated:!1,teams:[x]},e.access?.rbac||{},e.access?.requiresLogin||!1)}function O(t,e={},r={},n=!1){if(t.slug&&typeof t.slug=="string"&&Object.values(k).some(c=>{const i=c.split(":")[0].replace(/\/$/,"");return t.slug===i||t.slug?.startsWith(c)})||t.slug==="/mcp"&&Q.instance().canAccessFeature("mcp"))return!0;if(n&&Object.keys(r).length===0)return!!e.isAuthenticated;const s=z(r,e.teams||[]),o=t[_]||T(s,t[A]||{});return Object.keys(o||{}).length===0?!1:Object.keys(o).length===1&&o[l]&&o[l].toLowerCase()!=="none"?!0:F(o,e?.teams||[],e?.email)}function gt(t,e,r,n){if(!t.startsWith(I)&&!t.startsWith(j))return!0;const s=t.replace(new RegExp(`^${I}/`),"").replace(new RegExp(`^${j}/`),""),c=s==="."?"":s,i={[A]:{slug:t,fsPath:c},slug:t};return O(i,n,e,r)}function St(t,e,r,n,s){if(!t.startsWith(w))return!0;const o=t.match(/.*\..{64}\.([A-Fa-f0-9]{8})\.[^\.]+$/)?.[1];if(!o)return!0;const c=n[o];if(!c)return!0;const{base:i,ext:f}=y.parse(t),u=i.split(".")[0],h=f.split(".").join(""),d=c==="."?"":c,p={[A]:{slug:t,fsPath:y.posix.join(d,`${u}.${h}`)},slug:t};return O(p,s,e,r)}async function _t(t,e){const{isAuthenticated:r=!1,idpAccessToken:n,federatedAccessToken:s,federatedIdToken:o,...c}=await V(t,e),{teams:i=[]}=c;let f;return r?f=i.filter(u=>u!==x):f=[x],{isAuthenticated:r,idpAccessToken:n,teams:f,claims:c}}function N(t,e,r={},n=!1){if(!t)return t;if(Array.isArray(t)){const s=[];for(const o of t){const c=N(o,e,r,n);c!==void 0&&s.push(c)}return s}if(typeof t=="object"){if(!O(t,e,r,n))return;let s=!1;const o={};for(const c in t){if(c===_||c===A)continue;if(X.has(c)){o[c]=t[c];continue}const i=N(t[c],e,r,n);if(c==="items"&&Array.isArray(i)&&i.length===0&&t[c].length!==0){s=!0;continue}i!==void 0&&(o[c]=i),c==="paths"&&Z(i)&&q(i)}return s?void 0:o}return t}function Lt(t){return typeof t=="string"?t.split(" ").filter(Boolean):Array.isArray(t)?t.map(e=>e.toString()):[]}function jt(t,e){if(!e)return;const r=e.content;if(!r)return e;const n=Object.entries(r).flatMap(([o,c])=>o===L?[[o,c]]:[[o,c],...t.localeFolders.map(i=>[o.startsWith("/")?`/${i.toLocaleLowerCase()}${o}`:y.posix.join(t.localizationFolder,i,o),c])]),s=Object.fromEntries(n);return{...e,content:s}}async function It(t,e){if(!e)return{};const r={},n=new Set((await t.scan()).flatMap(({relativePath:s})=>{const{versionFolderPath:o}=M(s)||{},c=y.dirname(s);return o?[o,c]:c}));for(const s of n)r[$(s)]=s;return r}const m=t=>typeof t=="object"&&t!==null&&!Array.isArray(t);function Z(t){return m(t)&&Object.keys(t).length>0}function q(t){for(const e of Object.keys(t)){const r=t[e];m(r)&&Object.keys(r).length===0&&delete t[e]}}function tt(t){return t?t.split("/").filter(Boolean).some(r=>r.startsWith(".")):!1}const et=t=>{if(t&&m(t)&&("content"in t&&m(t.content)||"reunite"in t&&m(t.reunite)||"features"in t&&m(t.features)||t.teamFolders&&t.teamNamePatterns)){const e=Object.values(t.content||{});if(e.length===0)return!0;if(e.every(m))return e.every(r=>Object.values(r).every(n=>typeof n=="string"))}return!1},Ct=async t=>{if(t){if(Object.keys(t).length===0)return{};if(et(t))return rt(t);await U.panicOnContentError(`You are using an incorrect format of ${W("rbac:")} configuration. See: https://redocly.com/docs/realm/access`)}},rt=t=>{const e={...t};if(e.content){const r={};for(const n in e.content)if(e.content[n]!==void 0){const s=B(n);r[s]=e.content[n]}e.content=r}return e};function Ft(t,e){const r=t.fsPath,n=t.slug,s=[];if(Y(e)&&(r||n)){const o=[r,n].filter(v),c=H(e,o);if(c){const i=e?.teamNamePatterns?.map(u=>u.replace("{teamPathSegment}",c.teamPathSegment).replace("{projectRole}","read"))??[];s.push(...i);const f=T({content:{...K(e),...e.content}},t);s.push(...D(f))}else{const i=T(e,t);s.push(...D(i))}}else{const o=T(e,t);s.push(...D(o))}return nt(e,s)}function D(t){if(!t)return[];const e=[],r=l in t?{authenticated:t[l],anonymous:t[l]}:{};for(const[n,s]of Object.entries({...r,...t}))s.toLowerCase()!=="none"&&n!==l&&e.push(n);return e}function nt(t,e){return e.map(n=>G(t,n)??{teamName:n}).map(n=>n.projectRole&&n.projectRole!=="READ"?n.teamName?.toLowerCase().replace(n.projectRole?.toLowerCase?.()??"","read")??"":n.teamName?.toLowerCase()??"")}export{C as PROJECT_ROLES_ORDERED_BY_ACCESS_LEVEL,jt as applyL10nToRbacConfig,St as canAccessAsset,xt as canAccessFeature,O as canAccessResource,gt as canDownloadApiDefinition,nt as expandTeamsForRead,D as extractTeamsFromScopeItems,N as filterDataByAccessDeep,_t as getAuthDetailsFromCookies,Pt as getHigherRole,Ft as getRbacTeamsListForResource,T as getScopeItemsForResource,F as hasScopeAccess,et as isRbacConfigValid,Dt as isResourcePubliclyAccessible,rt as normalizeRbacConfig,Ct as parseRbacConfig,Lt as parseTeamClaimToArray,It as resolveDirectoryHashes};
@@ -1 +1 @@
1
- import l from"workerpool";import{mkdirSync as m}from"node:fs";import{basename as u,dirname as c,resolve as f}from"node:path";import{fileURLToPath as p}from"node:url";import{envConfig as n}from"../config/env-config.js";import{SandboxContext as v}from"../utils/sandbox-context.js";const h=(()=>{const o=c(p(import.meta.url));return u(o)==="chunks"?c(o):o})();class D{#t=null;#r;constructor(r){this.#r=r,this.#r.lazy||(this.#t=this.#e())}#e(){const{workerScript:r,lazy:t,sandbox:i,resolveForkEnv:s,...e}=this.#r,a=this.#s(r);return s&&e.forkOpts&&(e.forkOpts={...e.forkOpts,env:s()}),this.#o(i)?l.pool(a,{...e,forkOpts:this.#i(e.forkOpts)}):l.pool(a,{...e})}#o(r){return!!r&&n.isReunite&&n.REDOCLY_DISABLE_API_FUNCTION_SANDBOX!==!0}#i(r){const t=v.resolve(h);try{m(t.tmpDir,{recursive:!0})}catch{}return{...r,execArgv:[...t.execArgv,...r?.execArgv??[]],env:{...r?.env,...t.env}}}#s(r){let t=r;return!t.endsWith(".js")&&!t.endsWith(".mjs")&&(t=`${t}.${n.isDevelopMode?"js":"mjs"}`),t.startsWith(".")?f(h,t):t}#n(){return this.#t||(this.#t=this.#e()),this.#t}async exec(r,t,i){let e=this.#n().exec(r,t);return i?.timeout&&(e=e.timeout(i.timeout)),await e}async terminate(){this.#t&&(await this.#t.terminate(),this.#t=null)}}export{D as WorkerPool};
1
+ import l from"workerpool";import{mkdirSync as m}from"node:fs";import{basename as u,dirname as c,resolve as f}from"node:path";import{fileURLToPath as p}from"node:url";import{envConfig as n}from"../config/env-config.js";import{SandboxContext as v}from"../utils/sandbox-context.js";const h=(()=>{const o=c(p(import.meta.url));return u(o)==="chunks"?c(o):o})();class P{#t=null;#r;constructor(r){this.#r=r,this.#r.lazy||(this.#t=this.#e())}#e(){const{workerScript:r,lazy:t,sandbox:i,resolveForkEnv:s,...e}=this.#r,a=this.#s(r);return s&&e.forkOpts&&(e.forkOpts={...e.forkOpts,env:s()}),this.#o(i)?l.pool(a,{...e,forkOpts:this.#i(e.forkOpts)}):l.pool(a,{...e})}#o(r){return!!r&&n.isReunite&&n.REDOCLY_DISABLE_API_FUNCTION_SANDBOX!==!0}#i(r){const t=v.resolve(h);try{m(t.tmpDir,{recursive:!0})}catch{}return{...r,cwd:t.serverOutDir,execArgv:[...t.execArgv,...r?.execArgv??[]],env:{...r?.env,...t.env}}}#s(r){let t=r;return!t.endsWith(".js")&&!t.endsWith(".mjs")&&(t=`${t}.${n.isDevelopMode?"js":"mjs"}`),t.startsWith(".")?f(h,t):t}#n(){return this.#t||(this.#t=this.#e()),this.#t}async exec(r,t,i){let e=this.#n().exec(r,t);return i?.timeout&&(e=e.timeout(i.timeout)),await e}async terminate(){this.#t&&(await this.#t.terminate(),this.#t=null)}}export{P as WorkerPool};
@@ -37,6 +37,9 @@ export type PlanGates = {
37
37
  [FEATURE.mcp]: boolean;
38
38
  [FEATURE.mcpGateway]: boolean;
39
39
  [FEATURE.mcpMode]: 'tools' | 'code';
40
+ [FEATURE.gatewayFetchRatePerMin]: number;
41
+ [FEATURE.gatewayFetchQuotaPerDay]: number;
42
+ [FEATURE.gatewayFetchBudgetPerExec]: number;
40
43
  };
41
44
  export type Entitlements = {
42
45
  [key in Feature]: boolean | number | string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/redoc-reef",
3
- "version": "0.136.0-next.5",
3
+ "version": "0.136.0-next.6",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "bin": {
@@ -94,14 +94,14 @@
94
94
  "xpath": "0.0.34",
95
95
  "yaml-ast-parser": "0.0.43",
96
96
  "zod": "^3.25.76",
97
- "@redocly/asyncapi-docs": "1.13.0-next.5",
98
- "@redocly/config": "0.52.0",
99
- "@redocly/graphql-docs": "1.13.0-next.5",
100
- "@redocly/openapi-docs": "3.24.0-next.5",
97
+ "@redocly/asyncapi-docs": "1.13.0-next.6",
98
+ "@redocly/config": "0.53.0",
99
+ "@redocly/graphql-docs": "1.13.0-next.6",
100
+ "@redocly/openapi-docs": "3.24.0-next.6",
101
101
  "@redocly/portal-legacy-ui": "0.18.0",
102
- "@redocly/portal-plugin-mock-server": "0.21.0-next.5",
102
+ "@redocly/portal-plugin-mock-server": "0.21.0-next.6",
103
103
  "@redocly/realm-asyncapi-sdk": "0.14.0-next.0",
104
- "@redocly/theme": "0.68.0-next.3"
104
+ "@redocly/theme": "0.68.0-next.4"
105
105
  },
106
106
  "peerDependencies": {
107
107
  "react": "^19.2.4",