@redocly/realm 0.136.0-next.8 → 0.136.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/CHANGELOG.md +221 -0
  2. package/dist/client/App.js +1 -1
  3. package/dist/client/ScriptLoader.js +1 -1
  4. package/dist/client/TestProvider.js +1 -1
  5. package/dist/client/app/Link.js +1 -1
  6. package/dist/client/app/Sidebar/useBreadcrumbs.js +1 -1
  7. package/dist/client/app/Sidebar/useSidebarItems.js +1 -1
  8. package/dist/client/app/Sidebar/utils.d.ts +1 -1
  9. package/dist/client/app/hooks/catalog/useCatalogClassic.js +1 -1
  10. package/dist/client/app/hooks/catalog/useCatalogFilter.js +1 -1
  11. package/dist/client/app/hooks/catalog/useCatalogSearch.js +1 -1
  12. package/dist/client/app/hooks/catalog/useCatalogSort.js +1 -1
  13. package/dist/client/app/hooks/products/useCurrentProduct.js +1 -1
  14. package/dist/client/app/hooks/useAutoScroll.js +1 -1
  15. package/dist/client/app/hooks/useBanner.js +1 -1
  16. package/dist/client/app/hooks/useL10nConfig.js +1 -1
  17. package/dist/client/app/hooks/useLoginUrl.js +1 -1
  18. package/dist/client/app/hooks/usePageTimeTracker.js +1 -1
  19. package/dist/client/app/hooks/useRouteChangeTracker.js +1 -1
  20. package/dist/client/app/markdoc/custom-components/openapi/replay-openapi.js +1 -1
  21. package/dist/client/app/pages/DevLogin/DevLogin.js +1 -1
  22. package/dist/client/app/search/useSearch.d.ts +1 -0
  23. package/dist/client/app/search/useSearch.js +1 -1
  24. package/dist/client/app/seo/hooks/useHreflangLinks.js +1 -1
  25. package/dist/client/app/useMobileMenu.js +1 -1
  26. package/dist/client/app/usePreloadHistory.d.ts +1 -1
  27. package/dist/client/app/usePreloadHistory.js +1 -1
  28. package/dist/client/app/utils/loadAndNavigate.d.ts +1 -1
  29. package/dist/client/browser-entry.js +2 -2
  30. package/dist/client/providers/hooks.d.ts +1 -1
  31. package/dist/client/providers/hooks.js +1 -1
  32. package/dist/client/providers/page-data/hooks.js +1 -1
  33. package/dist/client/providers/post-message/PostMessageProvider.js +1 -1
  34. package/dist/constants/api.d.ts +2 -0
  35. package/dist/constants/api.js +1 -1
  36. package/dist/server/api-routes/helpers/enhance-context.js +1 -1
  37. package/dist/server/api-routes/run-api-routes-worker.js +1 -1
  38. package/dist/server/config/env-config.d.ts +0 -2
  39. package/dist/server/config/env-schema.d.ts +0 -5
  40. package/dist/server/config/env-schemas/feature-flags.d.ts +0 -4
  41. package/dist/server/config/env-schemas/feature-flags.js +1 -1
  42. package/dist/server/constants/common.d.ts +2 -0
  43. package/dist/server/constants/common.js +1 -1
  44. package/dist/server/constants/plugins/search.d.ts +1 -1
  45. package/dist/server/constants/plugins/search.js +1 -1
  46. package/dist/server/esbuild/esbuild.js +1 -1
  47. package/dist/server/esbuild/plugins/themes-resolver.js +4 -2
  48. package/dist/server/plugins/catalog-entities/database/repositories/catalog-entities-repository.js +1 -1
  49. package/dist/server/plugins/dev-onboarding/template/App.js +1 -1
  50. package/dist/server/plugins/dev-onboarding/template/AppsList.js +1 -1
  51. package/dist/server/plugins/dev-onboarding/template/components/ApiKeyItem.js +1 -1
  52. package/dist/server/plugins/dev-onboarding/template/components/ApiLogDetails.js +1 -1
  53. package/dist/server/plugins/dev-onboarding/template/components/ApiLogs.js +1 -1
  54. package/dist/server/plugins/dev-onboarding/template/components/CreateApiKey.js +1 -1
  55. package/dist/server/plugins/dev-onboarding/template/components/DeleteAppDialog.js +1 -1
  56. package/dist/server/plugins/dev-onboarding/template/components/EditApis.js +1 -1
  57. package/dist/server/plugins/dev-onboarding/template/components/EditAppDescriptionDialog.js +1 -1
  58. package/dist/server/plugins/dev-onboarding/template/components/EditAppDialog.js +1 -1
  59. package/dist/server/plugins/dev-onboarding/template/components/EditCallbackUrlDialog.js +1 -1
  60. package/dist/server/plugins/dev-onboarding/template/components/RevokeAPIKey.js +1 -1
  61. package/dist/server/plugins/dev-onboarding/template/components/RollAPIKey.js +1 -1
  62. package/dist/server/plugins/dev-onboarding/template/components/UpdateApiKey.js +1 -1
  63. package/dist/server/plugins/dev-onboarding/template/index.js +1 -1
  64. package/dist/server/plugins/mcp/audit/audit-log.d.ts +36 -0
  65. package/dist/server/plugins/mcp/audit/audit-log.js +1 -0
  66. package/dist/server/plugins/mcp/audit/constants.d.ts +6 -0
  67. package/dist/server/plugins/mcp/audit/constants.js +1 -0
  68. package/dist/server/plugins/mcp/audit/secret-patterns.d.ts +2 -0
  69. package/dist/server/plugins/mcp/audit/secret-patterns.js +1 -0
  70. package/dist/server/plugins/mcp/codemode/build-tool-list.d.ts +12 -0
  71. package/dist/server/plugins/mcp/codemode/build-tool-list.js +2 -0
  72. package/dist/server/plugins/mcp/codemode/capabilities/fetch/index.js +4 -4
  73. package/dist/server/plugins/mcp/codemode/capabilities/types.d.ts +2 -1
  74. package/dist/server/plugins/mcp/codemode/capabilities/utils.d.ts +1 -1
  75. package/dist/server/plugins/mcp/codemode/capabilities/utils.js +3 -3
  76. package/dist/server/plugins/mcp/codemode/code-audit.d.ts +12 -0
  77. package/dist/server/plugins/mcp/codemode/code-audit.js +1 -0
  78. package/dist/server/plugins/mcp/codemode/code-execution-audit.d.ts +17 -0
  79. package/dist/server/plugins/mcp/codemode/code-execution-audit.js +1 -0
  80. package/dist/server/plugins/mcp/codemode/code-execution-telemetry.d.ts +11 -0
  81. package/dist/server/plugins/mcp/codemode/code-execution-telemetry.js +1 -0
  82. package/dist/server/plugins/mcp/codemode/constants.js +1 -1
  83. package/dist/server/plugins/mcp/codemode/prompts.d.ts +6 -3
  84. package/dist/server/plugins/mcp/codemode/prompts.js +7 -4
  85. package/dist/server/plugins/mcp/codemode/sandbox/create-sandbox-setup.js +1 -1
  86. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/fetch-shim.d.ts +6 -0
  87. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/fetch-shim.js +54 -0
  88. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/form-data.d.ts +3 -0
  89. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/form-data.js +17 -0
  90. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/index.d.ts +3 -0
  91. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/index.js +2 -0
  92. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/url-search-params.d.ts +3 -0
  93. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/url-search-params.js +64 -0
  94. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/url.d.ts +3 -0
  95. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/url.js +239 -0
  96. package/dist/server/plugins/mcp/codemode/tools/describe-tools.d.ts +2 -3
  97. package/dist/server/plugins/mcp/codemode/tools/describe-tools.js +1 -1
  98. package/dist/server/plugins/mcp/codemode/tools/execute-schema.d.ts +1 -1
  99. package/dist/server/plugins/mcp/codemode/tools/execute.js +1 -1
  100. package/dist/server/plugins/mcp/codemode/tools/manage-credentials.d.ts +19 -0
  101. package/dist/server/plugins/mcp/codemode/tools/manage-credentials.js +2 -0
  102. package/dist/server/plugins/mcp/codemode/tools/tool-errors.d.ts +4 -0
  103. package/dist/server/plugins/mcp/codemode/tools/tool-errors.js +1 -0
  104. package/dist/server/plugins/mcp/constants.d.ts +2 -1
  105. package/dist/server/plugins/mcp/constants.js +1 -1
  106. package/dist/server/plugins/mcp/docs-mcp/tool-schemas.js +1 -1
  107. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/get-endpoint-info.js +1 -1
  108. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/get-endpoints.js +1 -1
  109. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/get-full-api-description.js +1 -1
  110. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/get-security-schemes.js +1 -1
  111. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/list-apis.js +1 -1
  112. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/load-api-description.d.ts +5 -1
  113. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/load-api-description.js +1 -1
  114. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/types.d.ts +2 -0
  115. package/dist/server/plugins/mcp/docs-mcp/utils.js +1 -1
  116. package/dist/server/plugins/mcp/gateway-mcp/elicitation.d.ts +38 -17
  117. package/dist/server/plugins/mcp/gateway-mcp/elicitation.js +3 -2
  118. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-audit.d.ts +24 -0
  119. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-audit.js +1 -0
  120. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-bridge.js +1 -1
  121. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-telemetry.d.ts +2 -1
  122. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-telemetry.js +1 -1
  123. package/dist/server/plugins/mcp/gateway-mcp/fetch/normalize-path.d.ts +6 -0
  124. package/dist/server/plugins/mcp/gateway-mcp/fetch/normalize-path.js +1 -0
  125. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/apply-edge-rate-limit.js +1 -1
  126. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/edge-limiter.d.ts +2 -2
  127. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/edge-limiter.js +1 -1
  128. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/rate-limit-telemetry.d.ts +8 -0
  129. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/rate-limit-telemetry.js +1 -0
  130. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/window-counter.d.ts +2 -2
  131. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/window-counter.js +1 -1
  132. package/dist/server/plugins/mcp/gateway-mcp/secret-store.d.ts +3 -0
  133. package/dist/server/plugins/mcp/gateway-mcp/secret-store.js +1 -1
  134. package/dist/server/plugins/mcp/gateway-mcp/upstream-auth.d.ts +2 -1
  135. package/dist/server/plugins/mcp/gateway-mcp/upstream-auth.js +1 -1
  136. package/dist/server/plugins/mcp/handlers/docs-mcp-handler.js +1 -1
  137. package/dist/server/plugins/mcp/handlers/errors.js +1 -1
  138. package/dist/server/plugins/mcp/handlers/mcp-credentials-handler.js +47 -24
  139. package/dist/server/plugins/mcp/servers/docs-server.d.ts +2 -3
  140. package/dist/server/plugins/mcp/servers/docs-server.js +1 -1
  141. package/dist/server/plugins/mcp/servers/mcp-server.d.ts +11 -14
  142. package/dist/server/plugins/mcp/servers/mcp-server.js +3 -1
  143. package/dist/server/plugins/mcp/types.d.ts +6 -6
  144. package/dist/server/plugins/mcp/utils/json-schema-validator.d.ts +9 -0
  145. package/dist/server/plugins/mcp/utils/json-schema-validator.js +1 -0
  146. package/dist/server/plugins/mcp/utils/mode.js +1 -1
  147. package/dist/server/plugins/mcp/workers/execute-mcp-tool.js +1 -1
  148. package/dist/server/plugins/mcp/workers/mcp-tool-telemetry.d.ts +20 -1
  149. package/dist/server/plugins/mcp/workers/mcp-tool-telemetry.js +1 -1
  150. package/dist/server/plugins/scorecard-classic/template/ApiDetailsPage.js +1 -1
  151. package/dist/server/plugins/scorecard-classic/template/RuleDetailsPage.js +1 -1
  152. package/dist/server/plugins/scorecard-classic/template/index.js +1 -1
  153. package/dist/server/plugins/scorecard-classic/template/utils.d.ts +1 -1
  154. package/dist/server/plugins/scorecard-classic/template/views.js +1 -1
  155. package/dist/server/plugins/search/engines/flexsearch/index.js +1 -1
  156. package/dist/server/plugins/search/engines/flexsearch/search-index.js +1 -1
  157. package/dist/server/plugins/search/engines/typesense/index.js +1 -1
  158. package/dist/server/ssr/render.js +1 -1
  159. package/dist/server/tools/notifiers/formatter.d.ts +1 -0
  160. package/dist/server/tools/notifiers/formatter.js +3 -3
  161. package/dist/server/tools/notifiers/logger.d.ts +1 -0
  162. package/dist/server/tools/notifiers/logger.js +2 -2
  163. package/dist/server/tools/notifiers/reporter.js +6 -6
  164. package/dist/server/types/plugin-hooks.d.ts +1 -1
  165. package/dist/server/types/plugins/common.d.ts +1 -1
  166. package/dist/server/utils/llmstxt/agent-feedback-llms-footer.js +1 -1
  167. package/dist/server/web-server/auth.js +3 -3
  168. package/dist/server/web-server/middleware/telemetry-middleware.js +1 -1
  169. package/dist/server/web-server/routes/ask-ai.d.ts +11 -0
  170. package/dist/server/web-server/routes/ask-ai.js +1 -1
  171. package/dist/server/web-server/routes/catalog/bff-catalog-related-entities.js +1 -1
  172. package/dist/server/web-server/routes/catalog/bff-catalog-revisions.js +1 -1
  173. package/dist/server/web-server/routes/catalog/bff-catalog.js +1 -1
  174. package/dist/server/web-server/routes/catalog/catalog-relations.js +1 -1
  175. package/dist/server/web-server/routes/catalog/catalog.js +1 -1
  176. package/dist/server/web-server/routes/dynamic-route.js +1 -1
  177. package/dist/server/web-server/routes/index.js +1 -1
  178. package/dist/server/web-server/routes/mcp-routes/a2a.js +1 -1
  179. package/dist/server/web-server/routes/mcp-routes/agent-card.js +1 -1
  180. package/dist/server/web-server/routes/mcp-routes/mcp-server-card.js +1 -1
  181. package/dist/server/web-server/routes/page-data.js +1 -1
  182. package/dist/server/web-server/routes/search.js +1 -1
  183. package/dist/server/web-server/routes/skills/skill-markdown.js +1 -1
  184. package/dist/server/web-server/routes/skills/skills-index.js +1 -1
  185. package/dist/types/api.d.ts +3 -0
  186. package/dist/utils/url/parse-preview-url.d.ts +22 -0
  187. package/dist/utils/url/parse-preview-url.js +1 -0
  188. package/package.json +21 -22
  189. package/dist/server/plugins/mcp/codemode/sandbox/polyfills.d.ts +0 -2
  190. package/dist/server/plugins/mcp/codemode/sandbox/polyfills.js +0 -99
  191. package/dist/server/plugins/mcp/handlers/sse-keepalive.d.ts +0 -15
  192. package/dist/server/plugins/mcp/handlers/sse-keepalive.js +0 -3
  193. package/dist/server/plugins/mcp/servers/session-manager.d.ts +0 -15
  194. package/dist/server/plugins/mcp/servers/session-manager.js +0 -1
@@ -0,0 +1,24 @@
1
+ import type { GatewayFetchOutcome } from './fetch-telemetry.js';
2
+ type GatewayFetchAuditRecord = {
3
+ rawUrl: string;
4
+ method: string;
5
+ host: string;
6
+ outcome: GatewayFetchOutcome;
7
+ durationMs: number;
8
+ injectedSecretValues: readonly string[];
9
+ path?: string;
10
+ api?: string;
11
+ status?: number;
12
+ requestHeaders?: Record<string, string>;
13
+ requestBody?: string;
14
+ responseHeaders?: Record<string, string>;
15
+ responseBody?: string;
16
+ };
17
+ /**
18
+ * One outbound gateway fetch. `host`, `method` and `path` repeat the `mcpServer.gatewayFetch`
19
+ * telemetry event verbatim so a Grafana row joins onto this line; `url_sha256` identifies the
20
+ * request itself, since the telemetry event's `path` is deliberately normalized.
21
+ */
22
+ export declare function logGatewayFetchAudit({ rawUrl, method, host, outcome, durationMs, injectedSecretValues, path, api, status, requestHeaders, requestBody, responseHeaders, responseBody, }: GatewayFetchAuditRecord): void;
23
+ export {};
24
+ //# sourceMappingURL=fetch-audit.d.ts.map
@@ -0,0 +1 @@
1
+ import{auditHeaderNames as u,auditInput as g,auditOutputDigest as c,auditSha256 as b,auditUrl as l,logAuditRecord as y}from"../../audit/audit-log.js";function D({rawUrl:e,method:n,host:i,outcome:r,durationMs:o,injectedSecretValues:t,path:_,api:h,status:f,requestHeaders:p,requestBody:d,responseHeaders:m,responseBody:a}){const s=a===void 0?void 0:c(a);y("Record gateway fetch",{url_sha256:b(e),host:i,method:n,path:_,api:h,outcome:r,status:f,duration_ms:o,url:l(e,t),request_header_names:u(p),request_body:d===void 0?void 0:g(d,t),response_header_names:u(m),response_sha256:s?.sha256,response_bytes:s?.bytes})}export{D as logGatewayFetchAudit};
@@ -1 +1 @@
1
- import{Agent as 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};
1
+ import{Agent as W}from"undici";import{envConfig as I}from"../../../../config/env-config.js";import{logger as R}from"../../../../tools/notifiers/logger.js";import{assertUrlAllowed as B}from"./ssrf-guard.js";import{createSsrfSafeLookup as J}from"./ssrf-safe-lookup.js";import{SsrfBlockedError as C}from"./ssrf-blocklist.js";import{GatewayFetchBlockedError as $,GatewayFetchSystemError as K,GatewayFetchUserError as U}from"./fetch-errors.js";import{redactHeaders as x,redactSecretsFromString as V}from"./redact.js";import{normalizeFetchPath as X}from"./normalize-path.js";import{reportGatewayFetch as G}from"./fetch-telemetry.js";import{logGatewayFetchAudit as O}from"./fetch-audit.js";const N="x-forwarded-for",Y=3e4,z=1024*1024,Q=new Set([401,403,407]);let M;function Z(){if(!M){const t=I.isDevelopMode;t&&R.warn("Gateway MCP SSRF guard: loopback upstreams are ALLOWED (develop mode only, never in runtime)."),M=new W({connect:{lookup:J({allowLoopback:t})}})}return M}function be({allowedHosts:t,apis:e=[],authorization:o,onAuthRequired:s,fetchImpl:i=fetch,dispatcher:u=Z(),timeoutMs:d=Y,maxResponseBytes:h=z,maxFetchesPerExec:f,clientIp:m}){const k=[...t,...e.flatMap(r=>r.matchers)],L=[o,...e.map(r=>r.secret?.value)].filter(r=>!!r),v=e.map(r=>r.secret?.header).filter(r=>!!r);let H=0;return async r=>{const w=r.method??"GET",A=Date.now();let _,g,b,S={},T,D;try{if(f!==void 0){const l=Math.max(0,f);if(H+=1,H>l)throw new U(`Outbound fetch budget exceeded (max ${l} fetches per execute); split the work across calls`)}const n=B(r.url,k);_=n.hostname,g=X(n.pathname);const E=e.filter(l=>l.matchers.some(P=>P.test(n.hostname))),c=E.length===1?E[0]:void 0;b=c?.slug,S=se(r.headers,c?.secret,o,m);const p=new AbortController;D=setTimeout(()=>p.abort(),d);const a=await i(n,{method:w,headers:S,body:r.body,redirect:"manual",signal:p.signal,dispatcher:u});T=a.status,Q.has(a.status)&&c&&s&&s({api:c.slug,name:c.name,status:a.status});const F=await ae(a,h);let y=ce(a.headers);if(re(a.status)){y=x(y,v);const{location:l}=y;l&&!oe(l,n,k)&&delete y.location}return G({host:n.hostname,method:w,outcome:"success",durationMs:Date.now()-A,path:g,api:b,status:a.status,bytes:Buffer.byteLength(F,"utf8")}),O({rawUrl:n.href,method:w,host:n.hostname,outcome:"success",durationMs:Date.now()-A,injectedSecretValues:L,path:g,api:b,status:a.status,requestHeaders:S,requestBody:r.body,responseHeaders:y,responseBody:F}),{status:a.status,statusText:a.statusText,ok:a.ok,headers:y,bodyText:F}}catch(n){const{outcome:E,userSafe:c}=ee(n),p=_??(c instanceof $?c.host:void 0)??te(r.url)??"invalid-url";throw G({host:p,method:w,outcome:E,durationMs:Date.now()-A,path:g,api:b,status:T}),O({rawUrl:r.url,method:w,host:p,outcome:E,durationMs:Date.now()-A,injectedSecretValues:L,path:g,api:b,status:T,requestHeaders:S,requestBody:r.body}),c||(R.error(`Gateway MCP fetch failed [${w} ${p}]: ${V(ne(n),L)} headers=${JSON.stringify(x(S,v))}`),new K("Upstream fetch failed due to an internal error"))}finally{D!==void 0&&clearTimeout(D)}}}function q(t){let e=t;for(let o=0;o<5&&e instanceof Error;o++){if(e instanceof U||e instanceof C)return e;e=e.cause}}function ee(t){const e=q(t);return{outcome:e instanceof $||e instanceof C?"blocked":e?"user_error":"system_error",userSafe:e}}function te(t){try{return new URL(t).hostname||void 0}catch{return}}function re(t){return t>=300&&t<400}function oe(t,e,o){try{return!!B(new URL(t,e).href,o)}catch{return!1}}function ne(t){if(!(t instanceof Error))return String(t);const e=t.cause;return e instanceof Error?`${t.message}: ${e.message}`:t.message}async function ae(t,e){const o=Number(t.headers.get("content-length"));if(Number.isFinite(o)&&o>e)throw j(e,o);if(!t.body)return"";const s=t.body.getReader(),i=new TextDecoder;let u=0,d="";try{for(;;){const{done:h,value:f}=await s.read();if(h)break;if(u+=f.byteLength,u>e)throw j(e,u);d+=i.decode(f,{stream:!0})}return d+i.decode()}finally{await s.cancel().catch(()=>{})}}function j(t,e){return new U(`Response is too large (over ${t} bytes, got ${e}); request less data`)}function se(t,e,o,s){const i={},u=new Set;for(const[h,f]of Object.entries(t??{})){const m=h.toLowerCase();m==="host"||m==="content-length"||m!==N&&(u.add(m),i[h]=f)}s&&(i[N]=s);const d=e??(o?{header:"Authorization",value:o}:void 0);return d&&!u.has(d.header.toLowerCase())&&(i[d.header]=d.value),i}function ce(t){const e={};return t.forEach((o,s)=>{e[s.toLowerCase()]=o}),e}export{be as buildFetchBridge,Z as getDefaultAgent,ae as readCappedBody};
@@ -4,10 +4,11 @@ type GatewayFetchEvent = {
4
4
  method: string;
5
5
  outcome: GatewayFetchOutcome;
6
6
  durationMs: number;
7
+ path?: string;
7
8
  api?: string;
8
9
  status?: number;
9
10
  bytes?: number;
10
11
  };
11
- export declare function reportGatewayFetch({ host, method, outcome, durationMs, api, status, bytes, }: GatewayFetchEvent): void;
12
+ export declare function reportGatewayFetch({ host, method, outcome, durationMs, path, api, status, bytes, }: GatewayFetchEvent): void;
12
13
  export {};
13
14
  //# sourceMappingURL=fetch-telemetry.d.ts.map
@@ -1 +1 @@
1
- import{telemetry as i}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 M({host:d,method:f,outcome:o,durationMs:t,api:e,status:r,bytes:n}){try{i.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:t}])}catch{}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=${t}`)}export{M as reportGatewayFetch};
1
+ import{telemetry as m}from"../../../../telemetry/index.js";import{logger as c}from"../../../../tools/notifiers/logger.js";import{McpServerType as u}from"../../constants.js";import{serverAttributes as $}from"../../utils/telemetry-attributes.js";function g({host:f,method:i,outcome:o,durationMs:t,path:e,api:r,status:n,bytes:d}){try{m.sendMcpGatewayFetchPerformedMessage([{...$(u.Docs),host:f,method:i,outcome:o,...e!==void 0&&{path:e},...r!==void 0&&{api:r},...n!==void 0&&{status:n},...d!==void 0&&{bytes:d},duration_ms:t}])}catch{}c.info(`Gateway MCP fetch [${i} ${f}] outcome=${o}`+(e!==void 0?` path=${e}`:"")+(r!==void 0?` api=${r}`:"")+(n!==void 0?` status=${n}`:"")+(d!==void 0?` bytes=${d}`:"")+` durationMs=${t}`)}export{g as reportGatewayFetch};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Reduce a request path to something safe to report: no query string (it carries tokens), no
3
+ * identifiers (they explode cardinality and can be customer data), bounded length.
4
+ */
5
+ export declare function normalizeFetchPath(pathname: string): string;
6
+ //# sourceMappingURL=normalize-path.d.ts.map
@@ -0,0 +1 @@
1
+ const A=200,a=12,c=/^\d+$/,i=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,E=/^[0-9a-f]{16,}$/i,_=/^[a-z]{2,6}_[A-Za-z0-9]{8,}$/,f=/^(?=.*\d)[A-Za-z0-9._~-]{20,}$/;function r(t){return t&&(c.test(t)||i.test(t)||E.test(t)||_.test(t)||f.test(t)?"{id}":t)}function M(t){if(!t||t==="/")return"/";const o=t.split("/"),s=o.slice(0,13).map(r);o.length>13&&s.push("...");const n=s.join("/")||"/";return n.length>200?`${n.slice(0,200)}...`:n}export{M as normalizeFetchPath};
@@ -1 +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};
1
+ import{envConfig as o}from"../../../../config/env-config.js";import{KvRepository as s}from"../../../../persistence/kv/repositories/kv-repository.js";import{logger as m}from"../../../../tools/notifiers/logger.js";import{ipRateLimitKey as p}from"./ip-key.js";import{checkEdgeRateLimit as c}from"./edge-limiter.js";import{resolveGatewayFetchLimits as d}from"./limits.js";import{reportMcpRateLimited as f}from"./rate-limit-telemetry.js";async function S(n,a){const i=p(n);if(!i)return;let e,t;try{const r=await s.create({baseDbDir:a.serverOutDir,sqldRemoteDatabaseUrl:o.SQLD_REMOTE_DATABASE_URL,sqldRemoteDatabaseAuthToken:o.SQLD_REMOTE_DATABASE_AUTH_TOKEN});t=d(),e=await c({kv:r,ip:i,limits:t,now:Date.now()})}catch(r){m.warn(`gateway rate-limit init error, failing open: ${String(r)}`);return}if(!e.allowed)return f({scope:e.scope,retryAfterSeconds:e.retryAfterSeconds,limit:e.scope==="rate"?t.ratePerMin:t.quotaPerDay}),new Response(JSON.stringify({jsonrpc:"2.0",error:{code:-32029,message:`Rate limit exceeded (${e.scope}); retry after ${e.retryAfterSeconds}s`},id:null}),{status:429,headers:{"Content-Type":"application/json","Retry-After":String(e.retryAfterSeconds)}})}export{S as applyEdgeRateLimit};
@@ -1,4 +1,4 @@
1
- import type { CacheService } from '../../../../persistence/cache/services/cache-service.js';
1
+ import type { KvRepository } from '../../../../persistence/kv/repositories/kv-repository.js';
2
2
  import type { GatewayFetchLimits } from './limits.js';
3
3
  export type EdgeDecision = {
4
4
  allowed: true;
@@ -8,7 +8,7 @@ export type EdgeDecision = {
8
8
  scope: 'rate' | 'quota';
9
9
  };
10
10
  export declare function checkEdgeRateLimit(params: {
11
- cache: CacheService;
11
+ kv: KvRepository;
12
12
  ip: string;
13
13
  limits: GatewayFetchLimits;
14
14
  now: number;
@@ -1 +1 @@
1
- import{checkAndIncrement as a}from"./window-counter.js";const s=60,d=86400;async function w(i){const{cache:e,ip:t,limits:o,now:r}=i,n=await a({cache:e,ip:t,windowSeconds:s,limit:o.ratePerMin,now:r});if(!n.allowed)return{allowed:!1,retryAfterSeconds:n.retryAfterSeconds,scope:"rate"};const c=await a({cache:e,ip:t,windowSeconds:d,limit:o.quotaPerDay,now:r});return c.allowed?{allowed:!0}:{allowed:!1,retryAfterSeconds:c.retryAfterSeconds,scope:"quota"}}export{w as checkEdgeRateLimit};
1
+ import{checkAndIncrement as c}from"./window-counter.js";const s=60,d=86400;async function w(i){const{kv:e,ip:t,limits:o,now:r}=i;await e.sync();const n=await c({kv:e,ip:t,windowSeconds:s,limit:o.ratePerMin,now:r});if(!n.allowed)return{allowed:!1,retryAfterSeconds:n.retryAfterSeconds,scope:"rate"};const a=await c({kv:e,ip:t,windowSeconds:d,limit:o.quotaPerDay,now:r});return a.allowed?{allowed:!0}:{allowed:!1,retryAfterSeconds:a.retryAfterSeconds,scope:"quota"}}export{w as checkEdgeRateLimit};
@@ -0,0 +1,8 @@
1
+ type RateLimitedEvent = {
2
+ scope: 'rate' | 'quota';
3
+ retryAfterSeconds: number;
4
+ limit: number;
5
+ };
6
+ export declare function reportMcpRateLimited({ scope, retryAfterSeconds, limit }: RateLimitedEvent): void;
7
+ export {};
8
+ //# sourceMappingURL=rate-limit-telemetry.d.ts.map
@@ -0,0 +1 @@
1
+ import{telemetry as o}from"../../../../telemetry/index.js";import{logger as i}from"../../../../tools/notifiers/logger.js";import{McpServerType as m}from"../../constants.js";import{serverAttributes as p}from"../../utils/telemetry-attributes.js";function d({scope:e,retryAfterSeconds:r,limit:t}){try{o.sendMcpRateLimitedMessage([{...p(m.Docs),scope:e,retry_after_seconds:r,limit:t}])}catch{}i.warn(`Gateway MCP rate limited [scope=${e}] retryAfterSeconds=${r} limit=${t}`)}export{d as reportMcpRateLimited};
@@ -1,4 +1,4 @@
1
- import type { CacheService } from '../../../../persistence/cache/services/cache-service.js';
1
+ import type { KvRepository } from '../../../../persistence/kv/repositories/kv-repository.js';
2
2
  export type WindowCheck = {
3
3
  allowed: false;
4
4
  retryAfterSeconds: number;
@@ -6,7 +6,7 @@ export type WindowCheck = {
6
6
  allowed: true;
7
7
  };
8
8
  export declare function checkAndIncrement(params: {
9
- cache: CacheService;
9
+ kv: KvRepository;
10
10
  ip: string;
11
11
  windowSeconds: number;
12
12
  limit: number;
@@ -1 +1 @@
1
- import{logger as f}from"../../../../tools/notifiers/logger.js";const s="gateway-fetch-rl";async function m(i){const{cache:e,ip:l,windowSeconds:t,limit:w,now:d}=i,n=Math.floor(d/1e3),o=Math.floor(n/t),r=`${l}:${t}:${o}`;try{const a=(await e.get({key:r,namespace:s}))?.count??0;if(a>=w){const u=(o+1)*t-n;return{allowed:!1,retryAfterSeconds:Math.max(1,u)}}return await e.set({key:r,namespace:s,value:{count:a+1},ttlInSeconds:t}),{allowed:!0}}catch(c){return f.warn(`gateway rate-limit store error, failing open: ${String(c)}`),{allowed:!0}}}export{m as checkAndIncrement};
1
+ import{logger as g}from"../../../../tools/notifiers/logger.js";function u(e,t,o){return["mcp","gateway-fetch-rl",e,String(t),String(o)]}async function S(e){const{kv:t,ip:o,windowSeconds:n,limit:w,now:l}=e,r=Math.floor(l/1e3),c=Math.floor(r/n),i=u(o,n,c);try{const s=(await t.get(i))?.count??0;if(s>=w){const d=(c+1)*n-r;return{allowed:!1,retryAfterSeconds:Math.max(1,d)}}return await t.set(i,{count:s+1},{ttlInSeconds:n}),{allowed:!0}}catch(a){return g.warn(`gateway rate-limit store error, failing open: ${String(a)}`),{allowed:!0}}}export{S as checkAndIncrement};
@@ -10,11 +10,14 @@ export type ElicitationTarget = {
10
10
  api: string;
11
11
  name: string;
12
12
  auth: UpstreamAuthScheme;
13
+ hasExistingSecret?: boolean;
13
14
  };
14
15
  export declare class SecretStore {
15
16
  #private;
16
17
  setSecret(kv: KvService, subject: string, api: string, secret: StoredSecret): Promise<void>;
17
18
  getSecretsForSubject(kv: KvService, subject: string): Promise<Record<string, StoredSecret>>;
19
+ hasSecret(kv: KvService, subject: string, api: string): Promise<boolean>;
20
+ deleteSecret(kv: KvService, subject: string, api: string): Promise<void>;
18
21
  deleteSubject(kv: KvService, subject: string): Promise<void>;
19
22
  createElicitation(kv: KvService, target: ElicitationTarget): Promise<string>;
20
23
  peekElicitation(kv: KvService, token: string): Promise<ElicitationTarget | undefined>;
@@ -1 +1 @@
1
- import{createCipheriv as o,createDecipheriv as f,createHash as u,hkdfSync as a,randomBytes as h,randomUUID as m}from"node:crypto";import{JWT_SECRET_KEY as c}from"../../../constants/common.js";import{secretTtlSeconds as l}from"./subject.js";const p=900*1e3,d="mcp-upstream-secrets",y="mcp-elicitation",S="mcp-elicitation";class g{async setSecret(t,e,r,n){const i={api:r,...this.#c(this.#a(e),n,r)};await t.set(this.#n(e,r),i,{ttlInSeconds:l(e)})}async getSecretsForSubject(t,e){const r=this.#a(e),{items:n}=await t.list({prefix:this.#i(e)}),i={};for(const s of n)if(s.value)try{i[s.value.api]=this.#r(r,s.value,s.value.api)}catch{continue}return i}async deleteSubject(t,e){const{items:r}=await t.list({prefix:this.#i(e)}),n=r.flatMap(i=>i.value?[this.#n(e,i.value.api)]:[]);n.length!==0&&await t.transaction(async i=>{for(const s of n)await i.delete(s)})}async createElicitation(t,e){const r=m();return await t.set(this.#t(r),this.#c(this.#e(),e),{ttlInSeconds:p/1e3}),r}async peekElicitation(t,e){const r=await t.get(this.#t(e));if(r)try{return this.#r(this.#e(),r)}catch{return}}async consumeElicitation(t,e){const r=this.#t(e),n=await t.transaction(async i=>{const s=await i.get(r);return s&&await i.delete(r),s});if(n)try{return this.#r(this.#e(),n)}catch{return}}#i(t){return["mcp","secret",this.#s(t)]}#n(t,e){return["mcp","secret",this.#s(t),e]}#t(t){return["mcp","elicitation",t]}#s(t){return u("sha256").update(t).digest("hex")}#a(t){return Buffer.from(a("sha256",c,t,d,32))}#e(){return Buffer.from(a("sha256",c,S,y,32))}#c(t,e,r){const n=h(12),i=o("aes-256-gcm",t,n);r&&i.setAAD(Buffer.from(r,"utf8"));const s=Buffer.concat([i.update(JSON.stringify(e),"utf8"),i.final()]);return{iv:n.toString("base64"),tag:i.getAuthTag().toString("base64"),data:s.toString("base64")}}#r(t,e,r){const n=f("aes-256-gcm",t,Buffer.from(e.iv,"base64"));r&&n.setAAD(Buffer.from(r,"utf8")),n.setAuthTag(Buffer.from(e.tag,"base64"));const i=Buffer.concat([n.update(Buffer.from(e.data,"base64")),n.final()]).toString("utf8");return JSON.parse(i)}}const E=new g;export{p as ELICITATION_TTL_MS,g as SecretStore,E as secretStore};
1
+ import{createCipheriv as o,createDecipheriv as f,createHash as u,hkdfSync as a,randomBytes as h,randomUUID as l}from"node:crypto";import{JWT_SECRET_KEY as c}from"../../../constants/common.js";import{secretTtlSeconds as m}from"./subject.js";const p=900*1e3,d="mcp-upstream-secrets",y="mcp-elicitation",S="mcp-elicitation";class g{async setSecret(t,e,r,n){const i={api:r,...this.#c(this.#a(e),n,r)};await t.set(this.#t(e,r),i,{ttlInSeconds:m(e)})}async getSecretsForSubject(t,e){const r=this.#a(e),{items:n}=await t.list({prefix:this.#n(e)}),i={};for(const s of n)if(s.value)try{i[s.value.api]=this.#i(r,s.value,s.value.api)}catch{continue}return i}async hasSecret(t,e,r){return!!await t.get(this.#t(e,r))}async deleteSecret(t,e,r){await t.delete(this.#t(e,r))}async deleteSubject(t,e){const{items:r}=await t.list({prefix:this.#n(e)}),n=r.flatMap(i=>i.value?[this.#t(e,i.value.api)]:[]);n.length!==0&&await t.transaction(async i=>{for(const s of n)await i.delete(s)})}async createElicitation(t,e){const r=l();return await t.set(this.#e(r),this.#c(this.#r(),e),{ttlInSeconds:p/1e3}),r}async peekElicitation(t,e){const r=await t.get(this.#e(e));if(r)try{return this.#i(this.#r(),r)}catch{return}}async consumeElicitation(t,e){const r=this.#e(e),n=await t.transaction(async i=>{const s=await i.get(r);return s&&await i.delete(r),s});if(n)try{return this.#i(this.#r(),n)}catch{return}}#n(t){return["mcp","secret",this.#s(t)]}#t(t,e){return["mcp","secret",this.#s(t),e]}#e(t){return["mcp","elicitation",t]}#s(t){return u("sha256").update(t).digest("hex")}#a(t){return Buffer.from(a("sha256",c,t,d,32))}#r(){return Buffer.from(a("sha256",c,S,y,32))}#c(t,e,r){const n=h(12),i=o("aes-256-gcm",t,n);r&&i.setAAD(Buffer.from(r,"utf8"));const s=Buffer.concat([i.update(JSON.stringify(e),"utf8"),i.final()]);return{iv:n.toString("base64"),tag:i.getAuthTag().toString("base64"),data:s.toString("base64")}}#i(t,e,r){const n=f("aes-256-gcm",t,Buffer.from(e.iv,"base64"));r&&n.setAAD(Buffer.from(r,"utf8")),n.setAuthTag(Buffer.from(e.tag,"base64"));const i=Buffer.concat([n.update(Buffer.from(e.data,"base64")),n.final()]).toString("utf8");return JSON.parse(i)}}const B=new g;export{p as ELICITATION_TTL_MS,g as SecretStore,B as secretStore};
@@ -1,8 +1,9 @@
1
- import type { CallToolResult } from '@redocly/mcp-typescript-sdk/types.js';
1
+ import type { CallToolResult } from '@modelcontextprotocol/server';
2
2
  export declare const UPSTREAM_AUTH_REQUIRED_META = "com.redocly/upstream-auth-required";
3
3
  export type UpstreamAuthRequiredEvent = {
4
4
  api: string;
5
5
  name: string;
6
+ status: number;
6
7
  };
7
8
  export declare function withAuthRequiredMeta(result: CallToolResult, events: UpstreamAuthRequiredEvent[]): CallToolResult;
8
9
  export declare function readAuthRequiredMeta(result: CallToolResult): UpstreamAuthRequiredEvent[];
@@ -1 +1 @@
1
- const n="com.redocly/upstream-auth-required";function i(e,a){const t=[...new Map(a.map(r=>[r.api,r])).values()];return t.length===0?e:{...e,_meta:{...e._meta,[n]:t}}}function o(e){const a=e._meta?.[n];return Array.isArray(a)?a.filter(t=>typeof t?.api=="string"&&typeof t?.name=="string"):[]}function p(e){if(!e._meta||!(n in e._meta))return e;const{[n]:a,...t}=e._meta,r={...e};return Object.keys(t).length>0?r._meta=t:delete r._meta,r}export{n as UPSTREAM_AUTH_REQUIRED_META,o as readAuthRequiredMeta,p as stripAuthRequiredMeta,i as withAuthRequiredMeta};
1
+ const i="com.redocly/upstream-auth-required";function u(t,n){const e=new Map;for(const a of n){const o=e.get(a.api);(!o||o.status!==401&&a.status===401)&&e.set(a.api,a)}const r=[...e.values()];return r.length===0?t:{...t,_meta:{...t._meta,[i]:r}}}function p(t){const n=t._meta?.[i];return Array.isArray(n)?n.filter(e=>typeof e?.api=="string"&&typeof e?.name=="string"&&typeof e?.status=="number"):[]}function s(t){if(!t._meta||!(i in t._meta))return t;const{[i]:n,...e}=t._meta,r={...t};return Object.keys(e).length>0?r._meta=e:delete r._meta,r}export{i as UPSTREAM_AUTH_REQUIRED_META,p as readAuthRequiredMeta,s as stripAuthRequiredMeta,u as withAuthRequiredMeta};
@@ -1 +1 @@
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
+ import{logger as g}from"../../../tools/notifiers/logger.js";import{getFeedbackClientIp as O}from"../../../web-server/utils/feedback-ip.js";import{GATEWAY_FETCH_LIMIT_DEFAULTS as v}from"../../../../constants/common.js";import{isObject as F}from"../../../../utils/guards/is-object.js";import{getAccessibleSkills as N}from"../../../utils/skills/get-accessible-skills.js";import{getSkillUrl as _}from"../../../utils/skills/get-skill-url.js";import{createDocsMcpServer as L}from"../servers/docs-server.js";import{filterApiDescriptionsByRbac as k,filterGraphqlDescriptionsByRbac as B}from"../utils.js";import{createMethodNotAllowedError as P,withErrorHandling as H}from"./errors.js";import{constructForbiddenResponse as j,constructInvalidTokenResponse as V,constructUnauthorizedResponse as G,handleMcpAuth as J,shouldHandleMcpAuth as $}from"../auth/auth-handlers.js";import{extractTokenFromAuthHeader as z}from"../utils/jwt.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as K}from"../constants.js";import{canAccessFeature as Y}from"../../../utils/rbac.js";import{DEFAULT_ANONYMOUS_VISITOR_TEAM as W,RbacFeatures as X}from"../../../../constants/common.js";const u="X-Redocly-AI-Metadata";function Q(r){if(r)try{const e=JSON.parse(r);if(F(e))return e;g.warn(`Ignoring ${u} header: not a JSON object.`)}catch{g.warn(`Ignoring ${u} header: invalid JSON.`)}}const Z=async(r,e,s)=>{const i=!!e?.config?.access?.requiresLogin,t=e?.config?.access?.rbac||{},f=s?.props?.config?.mcpMode??"tools",h=s?.props?.config?.gatewayFetchBudgetPerExec??v.budgetPerExec;let p;const m=r.headers.get("Authorization");if($(i,t)){const{isAuthenticated:d,isTokenValid:o,currentUser:c,accessToken:l}=await J(r,e);if(!d)return G(new URL(r.url).origin);if(!o)return V();c&&(e.user=c),p=l}else m&&(p=z(m||"")??void 0);const A={isAuthenticated:!!e.user?.isAuthenticated,teams:e.user?.teams||[W],claims:e.user?.claims??{}};if(!Y(X.MCP,A,t,i))return j();let a;return await H(async()=>{const d=s?.props?.config?.elicitationEnabled??!1;if(r.method==="GET")return new Response(JSON.stringify({error:"Method Not Allowed",message:`In order to use this MCP server, you need to register it in your MCP Client (VS Code, Cursor, Claude Code, etc.) using this URL: ${r.url}`}),{status:405,headers:{"Content-Type":"application/json"}});if(r.method!=="POST")return P();const o=s,c=o?.props?.config?.apiDescriptionsMap||{},l=o?.props?.config?.graphqlDescriptions||[],M=o?.props?.config?.mcpDocsServerName||K,{config:{mcp:E={}}}=e,T=k(c,e.user,t,i),D=B(l,e.user,t,i),R=e.config.products?Object.values(e.config.products).map(n=>n?.name):[],w=E.docs?.name||M,S=Q(r.headers.get(u)),b=o?.props?.tools||[],y=o?.props?.skillsBaseUrl||new URL(r.url).origin,U=N(o?.props?.skills||[],e.user,t,i).map(n=>({slug:n.slug,name:n.name,description:n.description,content:n.content,uri:_(y,n.slug)})),C=await r.json(),I={name:w,tools:b,skills:U,context:{...e,accessToken:p,outdir:e.outdir||"",baseUrl:e.baseUrl||new URL(r.url).origin,apiDescriptionsMap:T,graphqlDescriptions:D,products:R,metadata:S,mcpMode:f,elicitationEnabled:d,gatewayFetchBudgetPerExec:h,clientIp:O(r)}};return a=await L({...I,getKv:e.getKv}),await q(r,a,C)},async()=>{a&&(await a.cleanup(),a=void 0)},f)};async function q(r,e,s){return e.transport.handleRequest(r,s!==void 0?{parsedBody:s}:void 0)}var me=Z;export{me as default};
@@ -1 +1 @@
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
+ 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||"",error_class:"server_error",mode:t}]),new n(s.InternalError,"Internal server error mcp",o,e).createErrorResponse()}function v(r="Invalid request",e){return new n(s.InvalidRequest,r,void 0,e).createErrorResponse()}function I(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||"",error_class:"server_error",mode:t}]),e&&e(),p(o,void 0,t)}}export{n as McpError,p as createInternalServerError,I as createInvalidParamsError,v as createInvalidRequestError,h as createMethodNotAllowedError,g as withErrorHandling};
@@ -1,4 +1,4 @@
1
- import{secretStore as l}from"../gateway-mcp/secret-store.js";import{buildAllowedHostMatchers as C}from"../gateway-mcp/fetch/allowed-hosts.js";import{assertUrlAllowed as P}from"../gateway-mcp/fetch/ssrf-guard.js";import{readCappedBody as I,getDefaultAgent as _}from"../gateway-mcp/fetch/fetch-bridge.js";const m={"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store","X-Frame-Options":"DENY","Referrer-Policy":"no-referrer","Content-Security-Policy":"default-src 'none'; style-src 'unsafe-inline'; form-action 'self'"},E=3e4,T=64*1024;let v=fetch;function L(e){v=e}const z=async(e,t)=>{const o=await t.getKv();if(e.method==="GET"){const n=new URL(e.url).searchParams.get("token")??"",r=await l.peekElicitation(o,n);return r?new Response(q(r,n),{status:200,headers:m}):u()}if(e.method==="POST"){const n=await e.formData(),r=String(n.get("token")??"");return String(n.get("mode")??"token")==="client-credentials"?F(o,n,r):R(o,n,r)}return new Response("Method Not Allowed",{status:405,headers:{Allow:"GET, POST"}})};async function R(e,t,o){const n=String(t.get("secret")??"");if(!n)return u();const r=await l.consumeElicitation(e,o);return r?(await l.setSecret(e,r.subject,r.api,O(r.auth,n)),new Response(x(r.name),{status:200,headers:m})):u()}async function F(e,t,o){const n=String(t.get("client_id")??"").trim(),r=String(t.get("client_secret")??"").trim();if(!n||!r)return u();const b=await l.peekElicitation(e,o);if(!b||b.auth.kind!=="oauth2ClientCredentials")return u();const a=await l.consumeElicitation(e,o);if(!a||a.auth.kind!=="oauth2ClientCredentials")return u();const f=a.auth;try{const A=C([{url:f.tokenUrl}]),S=P(f.tokenUrl,A),w=new AbortController,$=setTimeout(()=>w.abort(),E);let y;try{y=await v(S,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"client_credentials",client_id:n,client_secret:r,...f.scopes.length>0?{scope:f.scopes.join(" ")}:{}}),redirect:"manual",signal:w.signal,dispatcher:_()})}finally{clearTimeout($)}if(!y.ok)return h(a.name);let k;try{k=await I(y,T)}catch{return h(a.name)}let p;try{p=JSON.parse(k)}catch{return h(a.name)}const g=typeof p=="object"&&p!==null&&"access_token"in p?p.access_token:void 0;return typeof g!="string"||!g?h(a.name):(await l.setSecret(e,a.subject,a.api,{header:"Authorization",value:`Bearer ${g}`}),new Response(x(a.name),{status:200,headers:m}))}catch{return h(a.name)}}function O(e,t){return e.kind==="apiKey"?{header:e.header,value:t}:e.kind==="bearer"||e.kind==="oauth2ClientCredentials"?{header:"Authorization",value:B(t)}:{header:"Authorization",value:t}}function B(e){const t=e.trim();return t.toLowerCase().startsWith("bearer")?t:`Bearer ${t}`}function i(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;")}function s(e){return`<!doctype html>
1
+ import{secretStore as s,ELICITATION_TTL_MS as x}from"../gateway-mcp/secret-store.js";import{buildAllowedHostMatchers as P}from"../gateway-mcp/fetch/allowed-hosts.js";import{assertUrlAllowed as _}from"../gateway-mcp/fetch/ssrf-guard.js";import{readCappedBody as E,getDefaultAgent as T}from"../gateway-mcp/fetch/fetch-bridge.js";const p={"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store","X-Frame-Options":"DENY","Referrer-Policy":"no-referrer","Content-Security-Policy":"default-src 'none'; style-src 'unsafe-inline'; form-action 'self'"},R=3e4,z=64*1024;let $=fetch;function Q(e){$=e}const F=async(e,t)=>{const n=await t.getKv();if(e.method==="GET"){const o=new URL(e.url).searchParams.get("token")??"",r=await s.peekElicitation(n,o);return r?new Response(j(r,o),{status:200,headers:p}):u()}if(e.method==="POST"){const o=await e.formData(),r=String(o.get("token")??""),m=String(o.get("mode")??"token");return m==="client-credentials"?M(n,o,r):m==="clear"?B(n,r):O(n,o,r)}return new Response("Method Not Allowed",{status:405,headers:{Allow:"GET, POST"}})};async function O(e,t,n){const o=String(t.get("secret")??"");if(!o)return u();const r=await s.consumeElicitation(e,n);return r?(await s.setSecret(e,r.subject,r.api,H(r.auth,o)),new Response(A(r.name),{status:200,headers:p})):u()}async function B(e,t){const n=await s.consumeElicitation(e,t);return n?(await s.deleteSecret(e,n.subject,n.api),new Response(D(n.name),{status:200,headers:p})):u()}async function M(e,t,n){const o=String(t.get("client_id")??"").trim(),r=String(t.get("client_secret")??"").trim();if(!o||!r)return u();const m=await s.peekElicitation(e,n);if(!m||m.auth.kind!=="oauth2ClientCredentials")return u();const i=await s.consumeElicitation(e,n);if(!i||i.auth.kind!=="oauth2ClientCredentials")return u();const b=i.auth;try{const S=P([{url:b.tokenUrl}]),C=_(b.tokenUrl,S),k=new AbortController,I=setTimeout(()=>k.abort(),R);let g;try{g=await $(C,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"client_credentials",client_id:o,client_secret:r,...b.scopes.length>0?{scope:b.scopes.join(" ")}:{}}),redirect:"manual",signal:k.signal,dispatcher:T()})}finally{clearTimeout(I)}if(!g.ok)return f(i.name);let v;try{v=await E(g,z)}catch{return f(i.name)}let h;try{h=JSON.parse(v)}catch{return f(i.name)}const w=typeof h=="object"&&h!==null&&"access_token"in h?h.access_token:void 0;return typeof w!="string"||!w?f(i.name):(await s.setSecret(e,i.subject,i.api,{header:"Authorization",value:`Bearer ${w}`}),new Response(A(i.name),{status:200,headers:p}))}catch{return f(i.name)}}function H(e,t){return e.kind==="apiKey"?{header:e.header,value:t}:e.kind==="bearer"||e.kind==="oauth2ClientCredentials"?{header:"Authorization",value:N(t)}:{header:"Authorization",value:t}}function N(e){const t=e.trim();return t.toLowerCase().startsWith("bearer")?t:`Bearer ${t}`}function a(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;")}function c(e){return`<!doctype html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="utf-8">
@@ -20,6 +20,9 @@ input:focus{outline:none;border-color:#4f46e5;box-shadow:0 0 0 3px rgba(79,70,22
20
20
  button{margin-top:1.25rem;padding:.625rem 1.5rem;font-size:1rem;font-weight:600;color:#fff;background:#4f46e5;border:none;border-radius:.5rem;cursor:pointer}
21
21
  button:hover{background:#4338ca}
22
22
  button:focus-visible{outline:2px solid #4f46e5;outline-offset:2px}
23
+ button.danger{background:#b91c1c}
24
+ button.danger:hover{background:#991b1b}
25
+ button.danger:focus-visible{outline-color:#b91c1c}
23
26
  code{background:#f3f4f6;padding:.125rem .375rem;border-radius:.25rem;font-size:.85em}
24
27
  footer{margin-top:1.5rem;padding-top:1rem;border-top:1px solid #e5e7eb;font-size:.75rem;color:#9ca3af;text-align:center}
25
28
  @media (prefers-color-scheme:dark){
@@ -34,34 +37,47 @@ footer{border-top-color:#1f2937;color:#6b7280}
34
37
  </style>
35
38
  </head>
36
39
  <body><div class="card">${e}</div></body>
37
- </html>`}function c(e){return`<h1>\u{1F512} ${i(e)}</h1>`}function d(){return"<footer>Secured by Redocly &middot; credentials never enter the AI conversation</footer>"}function q(e,t){switch(e.auth.kind){case"bearer":return H(e.name,t);case"apiKey":return N(e.name,t,e.auth.header);case"oauth2ClientCredentials":return U(e.name,t,e.auth.scopes);default:return j(e.name,t)}}function H(e,t){return s(`
38
- ${c(`Credentials for ${e}`)}
40
+ </html>`}function d(e){return`<h1>\u{1F512} ${a(e)}</h1>`}function l(){return"<footer>Secured by Redocly &middot; credentials never enter the AI conversation</footer>"}function y(){return`<p class="hint">This link expires ${Math.round(x/6e4)} minutes after it was issued. If it stops working, ask your AI client for a new one.</p>`}function j(e,t){const n=e.hasExistingSecret?q(t):"";switch(e.auth.kind){case"bearer":return L(e.name,t,n);case"apiKey":return U(e.name,t,e.auth.header,n);case"oauth2ClientCredentials":return G(e.name,t,e.auth.scopes,n);default:return K(e.name,t,n)}}function q(e){return`
41
+ <div class="section">
42
+ <h2>Remove stored credentials</h2>
43
+ <p class="hint">This API already has credentials stored. Save above to replace them, or remove them to make calls anonymous again.</p>
44
+ <form method="post" action="/mcp/credentials" autocomplete="off">
45
+ <input type="hidden" name="token" value="${a(e)}">
46
+ <input type="hidden" name="mode" value="clear">
47
+ <button class="danger" type="submit">Remove credentials</button>
48
+ </form>
49
+ </div>`}function L(e,t,n){return c(`
50
+ ${d(`Credentials for ${e}`)}
39
51
  <p class="hint">Stored encrypted for you only (24 hours for anonymous callers, 30 days when signed in) and sent to the API as the <code>Authorization</code> header. It never passes through the AI conversation.</p>
40
52
  <form method="post" action="/mcp/credentials" autocomplete="off">
41
- <input type="hidden" name="token" value="${i(t)}">
53
+ <input type="hidden" name="token" value="${a(t)}">
42
54
  <input type="hidden" name="mode" value="token">
43
55
  <label for="secret">API token</label>
44
56
  <input type="password" id="secret" name="secret" placeholder="your API token" required autofocus>
45
57
  <p class="hint">Paste the raw token or one already prefixed with <code>Bearer</code> &mdash; we add the prefix automatically if it is missing.</p>
46
58
  <button type="submit">Save credentials</button>
47
59
  </form>
48
- ${d()}`)}function N(e,t,o){return s(`
49
- ${c(`Credentials for ${e}`)}
50
- <p class="hint">Stored encrypted for you only (24 hours for anonymous callers, 30 days when signed in) and sent to the API as the <code>${i(o)}</code> header. It never passes through the AI conversation.</p>
60
+ ${n}
61
+ ${y()}
62
+ ${l()}`)}function U(e,t,n,o){return c(`
63
+ ${d(`Credentials for ${e}`)}
64
+ <p class="hint">Stored encrypted for you only (24 hours for anonymous callers, 30 days when signed in) and sent to the API as the <code>${a(n)}</code> header. It never passes through the AI conversation.</p>
51
65
  <form method="post" action="/mcp/credentials" autocomplete="off">
52
- <input type="hidden" name="token" value="${i(t)}">
66
+ <input type="hidden" name="token" value="${a(t)}">
53
67
  <input type="hidden" name="mode" value="token">
54
- <label for="secret">${i(o)} value</label>
68
+ <label for="secret">${a(n)} value</label>
55
69
  <input type="password" id="secret" name="secret" placeholder="value" required autofocus>
56
70
  <button type="submit">Save credentials</button>
57
71
  </form>
58
- ${d()}`)}function U(e,t,o){const n=o.length>0?o.map(i).join(", "):"default scopes";return s(`
59
- ${c(`Credentials for ${e}`)}
72
+ ${o}
73
+ ${y()}
74
+ ${l()}`)}function G(e,t,n,o){const r=n.length>0?n.map(a).join(", "):"default scopes";return c(`
75
+ ${d(`Credentials for ${e}`)}
60
76
  <p class="hint">Paste a token you already have, or let us exchange client credentials for one via OAuth2 client_credentials.</p>
61
77
  <div class="section">
62
78
  <h2>I have a token</h2>
63
79
  <form method="post" action="/mcp/credentials" autocomplete="off">
64
- <input type="hidden" name="token" value="${i(t)}">
80
+ <input type="hidden" name="token" value="${a(t)}">
65
81
  <input type="hidden" name="mode" value="token">
66
82
  <label for="secret">API token</label>
67
83
  <input type="password" id="secret" name="secret" placeholder="your API token" required autofocus>
@@ -71,9 +87,9 @@ ${c(`Credentials for ${e}`)}
71
87
  </div>
72
88
  <div class="section">
73
89
  <h2>Exchange client credentials</h2>
74
- <p class="hint">We request a token from the API on your behalf. Scopes: ${n}.</p>
90
+ <p class="hint">We request a token from the API on your behalf. Scopes: ${r}.</p>
75
91
  <form method="post" action="/mcp/credentials" autocomplete="off">
76
- <input type="hidden" name="token" value="${i(t)}">
92
+ <input type="hidden" name="token" value="${a(t)}">
77
93
  <input type="hidden" name="mode" value="client-credentials">
78
94
  <label for="client_id">Client ID</label>
79
95
  <input type="text" id="client_id" name="client_id" required>
@@ -82,20 +98,27 @@ ${c(`Credentials for ${e}`)}
82
98
  <button type="submit">Exchange &amp; save</button>
83
99
  </form>
84
100
  </div>
85
- ${d()}`)}function j(e,t){return s(`
86
- ${c(`Credentials for ${e}`)}
101
+ ${o}
102
+ ${y()}
103
+ ${l()}`)}function K(e,t,n){return c(`
104
+ ${d(`Credentials for ${e}`)}
87
105
  <p class="hint">The value is stored encrypted for you only (24 hours for anonymous callers, 30 days when signed in) and is sent to the API as the <code>Authorization</code> header. It never passes through the AI conversation.</p>
88
106
  <form method="post" action="/mcp/credentials" autocomplete="off">
89
- <input type="hidden" name="token" value="${i(t)}">
107
+ <input type="hidden" name="token" value="${a(t)}">
90
108
  <input type="hidden" name="mode" value="token">
91
109
  <label for="secret">Authorization header value</label>
92
110
  <input type="password" id="secret" name="secret" placeholder="Bearer &lt;your token&gt;" required autofocus>
93
111
  <button type="submit">Save credentials</button>
94
112
  </form>
95
- ${d()}`)}function x(e){return s(`
96
- ${c("Credentials saved")}
97
- <p>Credentials for ${i(e)} are set for this session. Return to your AI client and continue - confirm the elicitation prompt if it is still open.</p>
98
- ${d()}`)}function h(e){return new Response(s(`
99
- ${c("Exchange failed")}
100
- <p>We could not exchange your client credentials for a token for ${i(e)}. This link has been used up - go back to your AI client and request a new link, then try again.</p>
101
- ${d()}`),{status:502,headers:m})}function u(){return new Response(s(`${c("Link expired")}<p>This credentials link is invalid or has expired. Retry the request in your AI client to get a fresh link.</p>${d()}`),{status:404,headers:m})}var W=z;export{W as default,L as setCredentialsFetchImpl};
113
+ ${n}
114
+ ${y()}
115
+ ${l()}`)}function D(e){return c(`
116
+ ${d("Credentials removed")}
117
+ <p>The stored credentials for ${a(e)} are gone. Calls to that API are anonymous again until you add new ones.</p>
118
+ ${l()}`)}function A(e){return c(`
119
+ ${d("Credentials saved")}
120
+ <p>Credentials for ${a(e)} are saved. Go back to your AI client and ask it to retry - or confirm the prompt it left open, if there is one. You can close this tab.</p>
121
+ ${l()}`)}function f(e){return new Response(c(`
122
+ ${d("Exchange failed")}
123
+ <p>We could not exchange your client credentials for a token for ${a(e)}. This link has been used up - go back to your AI client and request a new link, then try again.</p>
124
+ ${l()}`),{status:502,headers:p})}function u(){return new Response(c(`${d("Link expired")}<p>This credentials link is invalid, already used, or older than ${Math.round(x/6e4)} minutes. Retry the request in your AI client to get a fresh link.</p>${l()}`),{status:404,headers:p})}var V=F;export{V as default,Q as setCredentialsFetchImpl};
@@ -1,12 +1,11 @@
1
1
  import type { McpToolSchema } from '../../../types';
2
2
  import type { McpToolContext, SkillMcpResource } from '../types.js';
3
3
  import type { KvService } from '@redocly/config';
4
- import { type McpServerInstance, type McpSessionHooks } from './mcp-server.js';
5
- export declare function createDocsMcpServer({ name, tools, context, sessionHooks, skills, getKv, }: {
4
+ import { type McpServerInstance } from './mcp-server.js';
5
+ export declare function createDocsMcpServer({ name, tools, context, skills, getKv, }: {
6
6
  name: string;
7
7
  tools: McpToolSchema[];
8
8
  context: McpToolContext;
9
- sessionHooks?: McpSessionHooks;
10
9
  skills?: SkillMcpResource[];
11
10
  getKv: () => Promise<KvService>;
12
11
  }): Promise<McpServerInstance>;
@@ -1 +1 @@
1
- import{logger as C}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as S,MCP_TOOL_IS_AVAILABLE_KEY as b}from"../../../workers/mcp-tool-worker-pool.js";import{CREATE_ANONYMOUS_SESSION_TOOL_NAME as A}from"../constants.js";import{isAnonymousSessionToolAvailable as f}from"../docs-mcp/tools/core/create-anonymous-session.js";import{EXECUTE_TOOL_SCHEMA as M}from"../codemode/tools/execute-schema.js";import{EXECUTE_TOOL_NAME as O,DESCRIBE_TOOLS_TOOL_NAME as h,CODE_MODE_TOOL_NAMES as L,FETCH_TOOL_NAME as v}from"../codemode/constants.js";import{buildCodeModeInstructions as g,buildExecuteDescription as y,DESCRIBE_TOOLS_DESCRIPTION as D}from"../codemode/prompts.js";import{composeExecuteAnnotations as x,getActiveCapabilities as I}from"../codemode/capabilities/index.js";import{describeToolsSchema as N,describeTools as k}from"../codemode/tools/describe-tools.js";import{connectMcpServer as w,createMcpServer as R,createToolDispatch as F,extractSerializableContext as H}from"./mcp-server.js";function P(a){const t=M.schema;if(f(a))return t;const{sessionHandle:i,...c}=t.properties;return{...t,properties:c}}async function q({name:a,tools:t,context:i,sessionHooks:c,skills:T=[],getKv:_}){const m=i.mcpMode==="code",s=m?I(i):[],p=s.some(e=>e.id===v),{server:r,transport:u}=R(a,m?g(p):void 0,c),E=H(i),l=F(E,{getClientCapabilities:()=>r.server.getClientCapabilities(),getKv:_}),d=async e=>{try{return await S.exec(b,[{toolName:e,context:E}],{timeout:1e4})}catch(o){return C.error(`Failed to check MCP tool availability for "${e}": ${o instanceof Error?o.message:String(o)}`),!1}};if(m){const e=t.filter(n=>!L.has(n.name));r.tool(O,y(p),P(i),x(s),l(O)),r.tool(h,D,N(e,s),k(e,d,s,i));const o=t.find(n=>n.name===A);o&&f(i)&&r.tool(o.name,o.description,o.schema,o.annotations??{title:o.name},l(o.name))}else await Promise.all(t.map(async e=>{const o=e.annotations||{title:e.name},n=r.tool(e.name,e.description,e.schema,o,l(e.name));await d(e.name)||n.disable()}));for(const e of T)r.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 w(r,u)}export{q as createDocsMcpServer};
1
+ import{fromJsonSchema as A}from"@modelcontextprotocol/server";import{logger as b}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as g,MCP_TOOL_IS_AVAILABLE_KEY as M}from"../../../workers/mcp-tool-worker-pool.js";import{CREATE_ANONYMOUS_SESSION_TOOL_NAME as L}from"../constants.js";import{isAnonymousSessionToolAvailable as T}from"../docs-mcp/tools/core/create-anonymous-session.js";import{isManageApiCredentialsToolAvailable as v,manageApiCredentials as D,manageApiCredentialsSchema as y}from"../codemode/tools/manage-credentials.js";import{EXECUTE_TOOL_SCHEMA as I}from"../codemode/tools/execute-schema.js";import{EXECUTE_TOOL_NAME as O,DESCRIBE_TOOLS_TOOL_NAME as N,CODE_MODE_TOOL_NAMES as H,FETCH_TOOL_NAME as k}from"../codemode/constants.js";import{buildCodeModeInstructions as w,buildExecuteDescription as x,DESCRIBE_TOOLS_DESCRIPTION as F}from"../codemode/prompts.js";import{toToolListEntries as R}from"../codemode/build-tool-list.js";import{composeExecuteAnnotations as P,getActiveCapabilities as B}from"../codemode/capabilities/index.js";import{describeToolsSchema as U,describeTools as z}from"../codemode/tools/describe-tools.js";import{mcpJsonSchemaValidator as J}from"../utils/json-schema-validator.js";import{connectMcpServer as V,createMcpServer as X,createToolDispatch as Y,extractSerializableContext as $}from"./mcp-server.js";function K(r){const t=I.schema;if(T(r))return t;const{sessionHandle:i,...m}=t.properties;return{...t,properties:m}}function W(r,t){const i=t.schema;if(T(r))return i;const{sessionHandle:m,...p}=i.properties;return{...i,properties:p}}async function me({name:r,tools:t,context:i,skills:m=[],getKv:p}){const s=i.mcpMode==="code",a=s?B(i):[],E=a.some(e=>e.id===k),l=s?t.filter(e=>!H.has(e.name)):[],u=R(l,a),_=s&&v(i),{server:n,transport:C}=X(r,s?w(E,u):void 0),c=e=>A(e,J),d=$(i),S={getClientCapabilities:()=>n.server.getClientCapabilities(),getKv:p},f=Y(d,S),h=async e=>{try{return await g.exec(M,[{toolName:e,context:d}],{timeout:1e4})}catch(o){return b.error(`Failed to check MCP tool availability for "${e}": ${o instanceof Error?o.message:String(o)}`),!1}};if(s){n.registerTool(O,{description:x(E,u),inputSchema:c(K(i)),annotations:P(a)},f(O)),n.registerTool(N,{description:F,inputSchema:c(U(l,a))},z(l,h,a,i));const e=t.find(o=>o.name===L);if(e&&T(i)&&n.registerTool(e.name,{description:e.description,inputSchema:c(e.schema),annotations:e.annotations??{title:e.name}},f(e.name)),_){const o=y(i);n.registerTool(o.name,{description:o.description,inputSchema:c(W(i,o)),annotations:o.annotations??{title:o.name}},D(d,S))}}else await Promise.all(t.map(async e=>{const o=n.registerTool(e.name,{description:e.description,inputSchema:c(e.schema),annotations:e.annotations||{title:e.name}},f(e.name));await h(e.name)||o.disable()}));for(const e of m)n.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 V(n,C)}export{me as createDocsMcpServer};
@@ -1,29 +1,26 @@
1
- import { McpServer } from '@redocly/mcp-typescript-sdk/server/mcp.js';
2
- import { StreamableHTTPServerTransport } from '@redocly/mcp-typescript-sdk/server/streamableHttp.js';
3
- import type { ToolCallback } from '@redocly/mcp-typescript-sdk/server/mcp.js';
4
- import type { McpToolContext } from '../types.js';
1
+ import { McpServer, WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/server';
2
+ import type { McpToolCallback, McpToolContext } from '../types.js';
5
3
  import type { KvService } from '@redocly/config';
6
4
  export type McpServerInstance = {
7
5
  server: McpServer;
8
- transport: StreamableHTTPServerTransport;
6
+ transport: WebStandardStreamableHTTPServerTransport;
9
7
  cleanup: () => Promise<void>;
10
8
  };
11
- export type McpSessionHooks = {
12
- onInitialized: (sessionId: string) => void;
13
- onClosed: (sessionId: string) => void;
14
- };
15
- export declare function createMcpServer(name: string, instructions?: string, sessionHooks?: McpSessionHooks): {
9
+ export declare function createMcpServer(name: string, instructions?: string): {
16
10
  server: McpServer;
17
- transport: StreamableHTTPServerTransport;
11
+ transport: WebStandardStreamableHTTPServerTransport;
18
12
  };
19
- export declare function connectMcpServer(server: McpServer, transport: StreamableHTTPServerTransport): Promise<McpServerInstance>;
13
+ export declare function connectMcpServer(server: McpServer, transport: WebStandardStreamableHTTPServerTransport): Promise<McpServerInstance>;
20
14
  export type ToolDispatchDeps = {
21
15
  getClientCapabilities: () => {
22
- elicitation?: unknown;
16
+ elicitation?: {
17
+ form?: unknown;
18
+ url?: unknown;
19
+ };
23
20
  } | undefined;
24
21
  getKv: () => Promise<KvService>;
25
22
  };
26
23
  /** Build the tool callback that ships a tool call to the worker pool. */
27
- export declare function createToolDispatch(serializableContext: McpToolContext, deps?: ToolDispatchDeps): (toolName: string) => ToolCallback<Record<string, unknown>>;
24
+ export declare function createToolDispatch(serializableContext: McpToolContext, deps?: ToolDispatchDeps): (toolName: string) => McpToolCallback;
28
25
  export declare function extractSerializableContext(context: McpToolContext): McpToolContext;
29
26
  //# sourceMappingURL=mcp-server.d.ts.map
@@ -1 +1,3 @@
1
- import{randomUUID as w}from"node:crypto";import{McpServer as v}from"@redocly/mcp-typescript-sdk/server/mcp.js";import{StreamableHTTPServerTransport as M}from"@redocly/mcp-typescript-sdk/server/streamableHttp.js";import{logger as f}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as y,MCP_TOOL_WORKER_KEY as b}from"../../../workers/mcp-tool-worker-pool.js";import{secretStore as q}from"../gateway-mcp/secret-store.js";import{readAuthRequiredMeta as T,stripAuthRequiredMeta as S}from"../gateway-mcp/upstream-auth.js";import{runUpstreamCredentialElicitation as U}from"../gateway-mcp/elicitation.js";import{resolveUpstreamAuthScheme as _}from"../gateway-mcp/upstream-auth-scheme.js";import{MISSING_SESSION_HANDLE_MESSAGE as D,resolveSecretSubject as O}from"../gateway-mcp/subject.js";const k=6e4,d=new Map;function A(e,t){const r=d.get(e);if(r)return r;const i=t().finally(()=>{d.delete(e)});return d.set(e,i),i}function H(e,t,r){const i=new v({name:e,version:new Date().toISOString().slice(0,10)},{capabilities:{logging:{}},...t?{instructions:t}:{}}),c=new M(r?{sessionIdGenerator:()=>w(),onsessioninitialized:r.onInitialized,onsessionclosed:r.onClosed}:{sessionIdGenerator:void 0});return{server:i,transport:c}}async function Y(e,t){return await e.connect(t),{server:e,transport:t,cleanup:async()=>{t.close()}}}function C(e,t){return r=>async(i,c)=>{f.info(`MCP tool called: ${r}`);const u=e.elicitationEnabled?O(e.user,i.sessionHandle):{kind:"anonymous"},o=u.kind==="resolved"?u.subject:void 0;if(u.kind==="invalid")return{content:[{type:"text",text:u.message}],isError:!0};const g=async()=>{if(!(!e.elicitationEnabled||!o||!t?.getKv))try{const a=await t.getKv();return await q.getSecretsForSubject(a,o)}catch(a){f.warn(`gateway mcp secret store error, continuing unauthenticated: ${String(a)}`);return}},m=async()=>{const a=await g(),p={toolName:r,args:i,context:{...e,upstreamSecrets:a},extra:K(c)};return await y.exec(b,[p],{timeout:k})};let n=await m();const s=T(n);if(s.length>0&&e.elicitationEnabled&&!o)return S({...n,content:[{type:"text",text:D}],isError:!0});if(s.length>0&&e.elicitationEnabled&&o&&t?.getKv){const a=await t.getKv(),p=await _(e,s[0].api),l=await A(`${o}:${s[0].api}`,()=>U({sendRequest:(E,I,h)=>c.sendRequest(E,I,h),clientSupportsElicitation:!!t?.getClientCapabilities()?.elicitation,subject:o,baseUrl:e.baseUrl??"",events:s,auth:p,kv:a}));l.status==="accepted"?n=await m():l.status==="fallback"&&(n={...n,content:[...n.content,{type:"text",text:l.text}]})}return S(n)}}function K(e){return{sessionId:e.sessionId,authInfo:e.authInfo,requestId:e.requestId,requestInfo:e.requestInfo,_meta:e._meta}}function J(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{Y as connectMcpServer,H as createMcpServer,C as createToolDispatch,J as extractSerializableContext};
1
+ import{McpServer as T,WebStandardStreamableHTTPServerTransport as q}from"@modelcontextprotocol/server";import{logger as g}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as v,MCP_TOOL_WORKER_KEY as I}from"../../../workers/mcp-tool-worker-pool.js";import{SSE_KEEPALIVE_INTERVAL_MS as R}from"../constants.js";import{secretStore as _}from"../gateway-mcp/secret-store.js";import{readAuthRequiredMeta as A,stripAuthRequiredMeta as h}from"../gateway-mcp/upstream-auth.js";import{buildCredentialsToolHint as O,buildUpstreamCredentialFallback as w,buildUpstreamCredentialInputRequired as U,isCredentialElicitationDeclined as C,targetOf as D}from"../gateway-mcp/elicitation.js";import{resolveUpstreamAuthScheme as j}from"../gateway-mcp/upstream-auth-scheme.js";import{MISSING_SESSION_HANDLE_MESSAGE as K,resolveSecretSubject as P}from"../gateway-mcp/subject.js";const L=6e4,S=new Map;function $(e,t){const r=S.get(e);if(r)return r;const n=t().finally(()=>{S.delete(e)});return S.set(e,n),n}function ee(e,t){const r=new T({name:e,version:new Date().toISOString().slice(0,10)},{capabilities:{logging:{}},...t?{instructions:t}:{}}),n=new q({keepAliveMs:R,sessionIdGenerator:void 0});return{server:r,transport:n}}async function te(e,t){return await e.connect(t),{server:e,transport:t,cleanup:async()=>{t.close()}}}function re(e,t){return r=>async(n,a)=>{g.info(`MCP tool called: ${r}`);const i=e.elicitationEnabled??!1,s=a.mcpReq.envelope!==void 0,c=i?P(e.user,n.sessionHandle):{kind:"anonymous"},o=c.kind==="resolved"?c.subject:void 0;if(c.kind==="invalid")return{content:[{type:"text",text:c.message}],isError:!0};const d=async()=>{if(!(!i||!o||!t?.getKv))try{const u=await t.getKv();return await _.getSecretsForSubject(u,o)}catch(u){g.warn(`gateway mcp secret store error, continuing unauthenticated: ${String(u)}`);return}};let m;const E=async()=>{m=await d();const u={toolName:r,args:n,context:{...e,upstreamSecrets:m},extra:N(a)};return await v.exec(I,[u],{timeout:L})};let p=await E();const l=A(p);if(l.length>0&&i&&!o)return h({...p,content:[{type:"text",text:K}],isError:!0});if(l.length>0&&i&&o&&t?.getKv){g.info(`gateway mcp credentials prompt: era=${s?"2026-07-28":"2025"} clientElicitation=${F(t)}`);const u=await t.getKv(),b=new Set(Object.keys(m??{})),f=s?await H(e,a,l,o,u,b,t):await B(e,l,o,u,b);switch(f.kind){case"inputRequired":return f.result;case"retry":p=await E();break;case"prependText":p={...p,content:[{type:"text",text:f.text},...p.content],isError:!0};break;case"none":break}}return h(p)}}function x(e){return!!e?.getClientCapabilities()?.elicitation?.url}function F(e){const t=e?.getClientCapabilities()?.elicitation;if(!t)return"none";const r=[t.form?"form":void 0,t.url?"url":void 0].filter(Boolean);return r.length>0?r.join("+"):"none"}function k(e,t){return e.status===401&&!t.has(e.api)}function y(e){return e.map(O).join(`
2
+
3
+ `)}async function M(e,t,r,n){const[a,...i]=t;return{subject:r,baseUrl:e.baseUrl??"",target:D(a),auth:await j(e,a.api),kv:n,hasExistingSecret:!1,reason:{kind:"authFailure",status:a.status,otherApiNames:i.map(s=>s.name)}}}async function H(e,t,r,n,a,i,s){if(C(t.mcpReq.inputResponses))return{kind:"none"};const c=r.filter(d=>k(d,i));if(c.length===0)return{kind:"prependText",text:y(r)};const o=await M(e,c,n,a);return x(s)?{kind:"inputRequired",result:await U(o)}:{kind:"prependText",text:await w(o)}}async function B(e,t,r,n,a){const i=t.filter(o=>k(o,a));if(i.length===0)return{kind:"prependText",text:y(t)};const s=await M(e,i,r,n);return{kind:"prependText",text:await $(`${r}:${i[0].api}`,()=>w(s))}}function N(e){return{sessionId:e.sessionId,authInfo:e.http?.authInfo,requestId:e.mcpReq.id,_meta:e.mcpReq._meta}}function ne(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{te as connectMcpServer,ee as createMcpServer,re as createToolDispatch,ne as extractSerializableContext};
@@ -1,6 +1,5 @@
1
1
  import type { ApiFunctionsContext, ApiFunctionsContextMethods, PageStaticData } from '@redocly/config';
2
- import type { CallToolResult, RequestId, RequestInfo, RequestMeta } from '@redocly/mcp-typescript-sdk/types.js';
3
- import type { AuthInfo } from '@redocly/mcp-typescript-sdk/server/types.js';
2
+ import type { AuthInfo, CallToolResult, RequestId, RequestMeta, StandardSchemaWithJSON, ToolCallback } from '@modelcontextprotocol/server';
4
3
  import type { OpenAPIPath, OpenAPIServer, OpenAPIOperation } from '@redocly/api-docs';
5
4
  import type { McpErrorCodes, McpServerType } from './constants';
6
5
  import type { McpToolSchema } from '../../types';
@@ -79,11 +78,12 @@ export type SemanticSearchResult = {
79
78
  content: string;
80
79
  };
81
80
  export type McpToolWorkerResponse = CallToolResult;
81
+ export type ToolArgs = Record<string, unknown>;
82
+ export type McpToolCallback = ToolCallback<StandardSchemaWithJSON<ToolArgs, ToolArgs>>;
82
83
  export type McpToolExtra = {
83
84
  sessionId?: string;
84
85
  authInfo?: AuthInfo;
85
86
  requestId: RequestId;
86
- requestInfo?: RequestInfo;
87
87
  _meta?: RequestMeta;
88
88
  };
89
89
  export type McpToolContext = Omit<ApiFunctionsContext, 'telemetry' | 'getKv' | keyof ApiFunctionsContextMethods> & {
@@ -99,15 +99,15 @@ export type McpToolContext = Omit<ApiFunctionsContext, 'telemetry' | 'getKv' | k
99
99
  clientIp?: string;
100
100
  upstreamSecrets?: Record<string, StoredSecret>;
101
101
  };
102
- export type McpToolHandler<TArgs extends Record<string, unknown> = Record<string, unknown>> = (args: TArgs, context: McpToolContext, extra: McpToolExtra) => Promise<McpToolWorkerResponse>;
102
+ export type McpToolHandler<TArgs extends ToolArgs = ToolArgs> = (args: TArgs, context: McpToolContext, extra: McpToolExtra) => Promise<McpToolWorkerResponse>;
103
103
  export type McpToolIsAvailable = (context: McpToolContext) => boolean | Promise<boolean>;
104
- export type McpToolHandlerExtended<TArgs extends Record<string, unknown> = Record<string, unknown>> = {
104
+ export type McpToolHandlerExtended<TArgs extends ToolArgs = ToolArgs> = {
105
105
  execute: McpToolHandler<TArgs>;
106
106
  isAvailable?: McpToolIsAvailable;
107
107
  };
108
108
  export type McpToolExecutionParams = {
109
109
  toolName: string;
110
- args: Record<string, unknown>;
110
+ args: ToolArgs;
111
111
  context: McpToolContext;
112
112
  extra: McpToolExtra;
113
113
  };
@@ -0,0 +1,9 @@
1
+ import { AjvJsonSchemaValidator } from '@modelcontextprotocol/server/validators/ajv';
2
+ /**
3
+ * Validates tool arguments with the same engine and options the forked SDK
4
+ * (@redocly/mcp-typescript-sdk) used internally, so migrating to the upstream
5
+ * v2 SDK does not change which arguments are accepted. The SDK default would
6
+ * be a lenient draft-2020-12 engine instead.
7
+ */
8
+ export declare const mcpJsonSchemaValidator: AjvJsonSchemaValidator;
9
+ //# sourceMappingURL=json-schema-validator.d.ts.map
@@ -0,0 +1 @@
1
+ import{AjvJsonSchemaValidator as o}from"@modelcontextprotocol/server/validators/ajv";import r from"@redocly/ajv";const t=new o(new r({allErrors:!0}));export{t as mcpJsonSchemaValidator};
@@ -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??!0)&&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";function t(){return e.instance().entitlements?.mcpMode==="code"}function n(){return!!e.instance().canAccessFeature("mcpGateway")&&t()}function c(){return n()}function r(){return t()?"code":"tools"}export{r as getMcpMode,t as isMcpCodeModeEnabled,c as isMcpElicitationEnabled,n as isMcpGatewayEnabled};