@redocly/reef 0.137.0-next.11 → 0.137.0-next.12

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 +19 -0
  2. package/dist/cli/build/index.js +1 -1
  3. package/dist/client/app/Sidebar/useActions.js +1 -1
  4. package/dist/client/app/Sidebar/useSidebarItems.js +1 -1
  5. package/dist/client/app/hooks/catalog/useCatalogFilter.js +1 -1
  6. package/dist/client/app/seo/SeoTags.js +1 -1
  7. package/dist/server/esbuild/esbuild-logger.d.ts +2 -2
  8. package/dist/server/esbuild/esbuild-logger.js +3 -3
  9. package/dist/server/plugins/api-docs/search/get-ai-search-documents.d.ts +1 -1
  10. package/dist/server/plugins/api-docs/search/get-ai-search-documents.js +33 -33
  11. package/dist/server/plugins/mcp/codemode/capabilities/fetch/index.js +5 -4
  12. package/dist/server/plugins/mcp/codemode/capabilities/types.d.ts +5 -2
  13. package/dist/server/plugins/mcp/codemode/capabilities/utils.js +4 -4
  14. package/dist/server/plugins/mcp/codemode/prompts.d.ts +1 -1
  15. package/dist/server/plugins/mcp/codemode/prompts.js +7 -6
  16. package/dist/server/plugins/mcp/codemode/sandbox/create-sandbox-setup.d.ts +2 -2
  17. package/dist/server/plugins/mcp/codemode/sandbox/create-sandbox-setup.js +1 -11
  18. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/console.d.ts +2 -0
  19. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/console.js +5 -0
  20. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/index.d.ts +1 -0
  21. package/dist/server/plugins/mcp/codemode/sandbox/polyfills/index.js +2 -2
  22. package/dist/server/plugins/mcp/codemode/sandbox/tool-input-validator.d.ts +5 -0
  23. package/dist/server/plugins/mcp/codemode/sandbox/tool-input-validator.js +1 -0
  24. package/dist/server/plugins/mcp/codemode/sandbox/unknown-tool-hint.d.ts +8 -0
  25. package/dist/server/plugins/mcp/codemode/sandbox/unknown-tool-hint.js +2 -0
  26. package/dist/server/plugins/mcp/codemode/tools/describe-tools.js +3 -1
  27. package/dist/server/plugins/mcp/codemode/tools/execute.js +1 -1
  28. package/dist/server/plugins/mcp/codemode/tools/manage-credentials.js +2 -2
  29. package/dist/server/plugins/mcp/docs-mcp/tool-schemas.js +1 -1
  30. package/dist/server/plugins/mcp/docs-mcp/tools/core/search.js +2 -2
  31. package/dist/server/plugins/mcp/docs-mcp/tools/core/types.d.ts +2 -2
  32. package/dist/server/plugins/mcp/docs-mcp/tools/graphql/get-graphql-type.js +5 -5
  33. package/dist/server/plugins/mcp/docs-mcp/tools/graphql/types.d.ts +2 -1
  34. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/list-apis.js +1 -1
  35. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/load-api-description.d.ts +4 -2
  36. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/load-api-description.js +1 -1
  37. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/types.d.ts +6 -2
  38. package/dist/server/plugins/mcp/docs-mcp/utils/api-titles.d.ts +6 -0
  39. package/dist/server/plugins/mcp/docs-mcp/utils/api-titles.js +1 -0
  40. package/dist/server/plugins/mcp/gateway-mcp/fetch/ssrf-guard.js +1 -1
  41. package/dist/server/plugins/mcp/gateway-mcp/fetch/url-placeholders.d.ts +5 -0
  42. package/dist/server/plugins/mcp/gateway-mcp/fetch/url-placeholders.js +1 -0
  43. package/dist/server/plugins/mcp/servers/docs-server.js +1 -1
  44. package/dist/server/plugins/mcp/servers/mcp-server.js +2 -2
  45. package/dist/server/plugins/mcp/types.d.ts +1 -0
  46. package/dist/server/plugins/mcp/utils/available-sandbox-tools.js +1 -1
  47. package/dist/server/plugins/search/ai-indexer/prepare-semantic-documents.js +1 -1
  48. package/dist/server/plugins/search/llmstxt/create-llms-txt-generator.js +1 -1
  49. package/dist/server/plugins/search/llmstxt/index.d.ts +3 -1
  50. package/dist/server/plugins/search/llmstxt/index.js +9 -9
  51. package/dist/server/sandbox/sandbox.js +2 -2
  52. package/dist/server/sandbox/types.d.ts +8 -0
  53. package/dist/server/sandbox/utils/execution-budget.js +1 -1
  54. package/dist/server/sandbox/utils/return-last-expression.d.ts +6 -0
  55. package/dist/server/sandbox/utils/return-last-expression.js +1 -0
  56. package/dist/server/sandbox/utils/setup-host-functions.d.ts +1 -0
  57. package/dist/server/sandbox/utils/setup-host-functions.js +2 -2
  58. package/dist/server/store.js +1 -1
  59. package/dist/server/web-server/routes/dynamic-route.js +1 -1
  60. package/dist/server/web-server/routes/mcp-routes/mcp-oauth.js +1 -1
  61. package/dist/server/web-server/routes/mcp-routes/mcp-server-card.js +1 -1
  62. package/package.json +6 -6
@@ -1 +1 @@
1
- import{getCookie as A}from"hono/cookie";import{createHash as K,timingSafeEqual as J}from"node:crypto";import{ulid as V}from"ulid";import{isMcpContentProtected as P}from"../../../plugins/mcp/auth/auth-handlers.js";import{SUPPORTED_MCP_SCOPES as E}from"../../../plugins/mcp/constants.js";import{isRedoclySso as Y,isOidcProviderConfig as v}from"../../auth.js";import{AUTH_URL as Z,JWT_SECRET_KEY as m,MCP_ACCESS_TOKEN_TTL_SEC as b,MCP_REFRESH_SESSION_LIFETIME_SEC as W}from"../../../constants/common.js";import{ServerRoutes as T}from"../../../../constants/api.js";import{AuthCookieNames as R}from"../../../../constants/common.js";import{withPathPrefix as I}from"@redocly/theme/core/utils";import{telemetry as M}from"../../../telemetry/index.js";import{envConfig as U}from"../../../config/env-config.js";import{createMcpAuthorizationCode as x,verifyMcpAuthorizationCode as Q,createMcpRefreshToken as j,verifyMcpRefreshToken as X,createMcpSessionResource as y,oidcJwksCache as z,oidcRefreshTokenGrant as ee,getOidcMetadata as re,discoverJwks as te}from"../../auth.js";import*as S from"../../jwt/jwt.js";import{AlgorithmTypes as D}from"../../jwt/types.js";import{getRequestOrigin as q}from"../../utils/get-request-origin.js";import{encryptClaimValue as L}from"../../../utils/crypto/encrypt-claim-value.js";import{decryptAesGcm as ne}from"../../../utils/crypto/decrypt-aes-gcm.js";import{encryptAesGcm as F}from"../../../utils/crypto/encrypt-aes-gcm.js";import{fetchClientMetadataDocument as oe,isClientMetadataDocumentUrl as H,isRedirectUriRegistered as ie}from"./mcp-client-metadata.js";const s=(r,e,t=200,i)=>r.json(e,t,{"Content-Type":"application/json",...i??{}}),N=new Set(["S256"]),$=["authorization_code","refresh_token"],se="https://claude.ai/oauth/claude-code-client-metadata";function ce(r,e,t){if(!N.has(e||""))return!1;const i=K("sha256").update(t).digest("base64url"),c=Buffer.from(i),o=Buffer.from(r);return c.length!==o.length?!1:J(c,o)}async function de(r){const e=Math.floor(Date.now()/1e3);return S.sign({type:"mcp_context",...r,iat:e,exp:e+600},m,D.HS256)}async function ae(r){await S.verify(r,m,D.HS256);const{payload:e}=S.decode(r);if(e.type!=="mcp_context")throw new Error("Invalid context token type");return e}function Oe(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);if(e.req.method!=="GET")return s(e,{error:"Method not allowed"},405,{Allow:"GET"});const t=q(e);return s(e,{resource:`${t}${I("/mcp")}`,authorization_servers:[t],bearer_methods_supported:["header"],resource_documentation:`${t}${T.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:[...E],bearer_token_types_supported:["Bearer"]},200,{"Access-Control-Allow-Origin":"*"})}}function Ue(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const t=q(e);return s(e,{issuer:t,authorization_endpoint:`${t}${I(T.MCP_AUTHORIZATION)}`,token_endpoint:`${t}${I(T.MCP_TOKEN_PORTAL)}`,scopes_supported:[...E],registration_endpoint:`${t}${I(T.MCP_DYNAMIC_CLIENT_REGISTRATION)}`,response_types_supported:["code"],grant_types_supported:[...$],subject_types_supported:["public"],id_token_signing_alg_values_supported:["RS256"],code_challenge_methods_supported:["S256"],token_endpoint_auth_methods_supported:["none"],client_id_metadata_document_supported:!0},200,{"Access-Control-Allow-Origin":"*"})}}function De(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{return s(e,{client_id:U.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:[...$],response_types:["code"],scope:E.join(" "),subject_type:"public",token_endpoint_auth_method:"none",created_at:new Date().toISOString(),updated_at:new Date().toISOString()},201,{"Access-Control-Allow-Origin":"*"})}catch(t){return s(e,{error:"invalid_request",error_description:t?.message||"Unable to register client"},500,{"Access-Control-Allow-Origin":"*"})}}}function G(r){const e=r||{},t=Object.keys(e),i=t.find(o=>v(e[o])&&Y(e[o])),c=t.find(o=>v(e[o]));return i||c||t[0]}function qe(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const t=new URL(e.req.url),{searchParams:i}=t,c=i.get("redirect_uri"),o=i.get("client_id"),a=i.get("code_challenge"),n=i.get("code_challenge_method"),l=V();if(a&&!N.has(n||""))return s(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...N].join(", ")}`},400);if(H(o)&&!a)return s(e,{error:"invalid_request",error_description:"code_challenge is required for client_id_metadata_document clients"},400);if(H(o)&&c)try{const d=await oe(o),p=o===se;if(!ie(c,d.redirect_uris,{ignorePort:p}))return s(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(d){const p=d instanceof Error?d.message:String(d);return s(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${p}`},400)}M.sendMcpAuthorizationStartedMessage([{...y(l),redirect_uri:c||null}]);const f=q(e),_={isMcpFlow:!0,originalRedirectUri:c,mcpClientId:o,mcpState:i.get("state"),mcpSessionId:l,mcpCodeChallenge:a,mcpCodeChallengeMethod:a?n:null,timestamp:Date.now()};try{const d=await de(_),p=new URL(I(T.IDP_LOGIN),f);p.searchParams.set("redirectTo",`${T.MCP_CALLBACK}/${d}`);const k=G(r.getConfig().ssoDirect);return k?(p.searchParams.set("idpId",k),e.redirect(p.toString())):s(e,{error:"not_found"},404)}catch(d){const p=d instanceof Error?d.message:String(d),k=d instanceof Error?d.stack:String(d);M.sendMcpAuthorizationFailedMessage([{...y(l),error:p,error_details:k}]);const C=new URL(I(`${Z}/oauth2/auth`));return C.search=i.toString(),e.redirect(C.toString())}}}async function B(r){const e=Math.floor(Date.now()/1e3),t=await S.sign({...r.claims,...r.idpId?{idpId:r.idpId}:{},...r.idpAccessTokenEnc?{idp_access_token:r.idpAccessTokenEnc}:{},...r.ttlSec?{iat:e,exp:e+r.ttlSec}:{}},m,D.HS256,r.kid),i=r.ttlSec??(typeof r.claims.exp=="number"?Math.max(r.claims.exp-e,0):b);return{accessToken:t,expiresIn:i}}async function le(r,e,t){const i=g=>s(r,{error:"invalid_grant",error_description:g},400),c=r.get("logger"),o=t.get("refresh_token"),a=t.get("client_id")||void 0;if(!o)return s(r,{error:"invalid_request",error_description:"refresh_token is required"},400);let n;try{n=await X(o)}catch(g){return c?.warn("MCP refresh token rejected: %s",g instanceof Error?g.message:String(g)),i("Invalid refresh token")}if(a&&a!==n.client_id)return i("Client mismatch");const l=n.idp_id?e.getConfig().ssoDirect?.[n.idp_id]:void 0;let f,_,d=n.idp_access_token_enc,p=n.idp_refresh_token_enc;if(n.idp_refresh_token_enc){let g;try{g=ne(n.idp_refresh_token_enc,m)}catch{return i("Invalid refresh token")}if(!n.idp_id||!v(l))return i("Unknown identity provider");let u;try{const h=await re(n.idp_id,l);if(!h?.token_endpoint)return i("Identity provider has no token endpoint");u=await ee(h.token_endpoint,g,l)}catch(h){return c?.warn("MCP refresh failed to reach the IdP token endpoint: %s",h instanceof Error?h.message:String(h)),s(r,{error:"server_error",error_description:"Failed to refresh session"},500)}if(u.error||!(u.id_token||u.access_token))return c?.warn("MCP refresh rejected by IdP: %s",u.error||"empty token response"),i("Refresh rejected by identity provider");if(u.refresh_token&&(p=F(u.refresh_token,m)),u.access_token&&(d=L(u.access_token,m)),u.id_token)try{const h=S.decode(u.id_token);f=h.payload,_=h.header.kid}catch{return i("Invalid token response from identity provider")}}const k=v(l)&&l.tokenExpirationTime||b,C=f??n.claims,w=await B({claims:C,kid:_,idpId:n.idp_id,idpAccessTokenEnc:d,ttlSec:f?void 0:k}),O=await j({clientId:n.client_id,idpId:n.idp_id,claims:C,idpAccessTokenEnc:d,idpRefreshTokenEnc:p,sessionExp:n.session_exp});return s(r,{access_token:w.accessToken,token_type:"Bearer",expires_in:w.expiresIn,refresh_token:O,scope:E.join(" ")},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}function Le(r){return async e=>{if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{const t=await e.req.formData(),i=t.get("grant_type"),c=t.get("code"),o=t.get("redirect_uri")||void 0,a=t.get("code_verifier")||void 0;if(!$.includes(i))return s(e,{error:"invalid_request",error_description:"Invalid grant type"},400);if(i==="refresh_token")return await le(e,r,t);if(!c)return s(e,{error:"invalid_request",error_description:"Missing authorization code"},400);try{const n=await Q(c);if(o&&o!==n.redirect_uri)return s(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(n.code_challenge){if(!a)return s(e,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!ce(n.code_challenge,n.code_challenge_method,a))return s(e,{error:"invalid_grant",error_description:"code_verifier mismatch"},400)}if(U.OAUTH_CLIENT_ID&&n.client_id&&!H(n.client_id)&&n.client_id!==U.OAUTH_CLIENT_ID)return s(e,{error:"invalid_client",error_description:"Client mismatch"},400);const l=n.id_token;if(typeof l!="string"||l.length===0)return s(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400);const f=n.idp_access_token_enc??(n.idp_access_token?L(n.idp_access_token,m):void 0),_=S.decode(l),{accessToken:d,expiresIn:p}=await B({claims:_.payload,kid:_.header.kid,idpId:n.idp_id,idpAccessTokenEnc:f}),C=Math.floor(Date.now()/1e3)+W,w=await j({clientId:n.client_id,idpId:n.idp_id,claims:_.payload,idpAccessTokenEnc:f,idpRefreshTokenEnc:n.idp_refresh_token_enc,sessionExp:C}),O={access_token:d,token_type:"Bearer",expires_in:p,refresh_token:w,scope:E.join(" "),id_token:l};return s(e,O,200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch(n){return e.get("logger")?.warn("MCP token exchange failed: %s",n instanceof Error?n.message:String(n)),s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(t){const i=t instanceof Error?t.message:String(t);return s(e,{error:"server_error",error_description:"Failed to process token request",error_details:i},500)}}}async function pe(r,e){try{const{payload:t,header:i}=S.decode(r);if(typeof t.idpId=="string"&&t.idpId)return t.idpId;if(i.kid){z.jwks[i.kid]==null&&await te(e||{});const c=z.jwks[i.kid];if(c?.idpId)return c.idpId}}catch{}return G(e)}function He(r){return async e=>{const t=new URL(e.req.url);let i=t.searchParams.get("context");if(!i&&t.pathname.startsWith(I(`${T.MCP_CALLBACK}/`))){const o=t.pathname.split("/");i=o[o.length-1]}if(!i)return M.sendMcpAuthorizationFailedMessage([{...y(null),error:"Missing context parameter",error_details:null}]),e.text("Missing context parameter",400);let c=null;try{const o=await ae(i);if(c=o.mcpSessionId||null,!o.isMcpFlow||!o.originalRedirectUri)throw new Error("Invalid MCP context");const a=A(e,R.IDP_ID_TOKEN)||A(e,R.AUTHORIZATION),n=A(e,R.IDP_ACCESS_TOKEN),l=A(e,R.IDP_REFRESH_TOKEN);if(!a)return M.sendMcpAuthorizationFailedMessage([{...y(c),error:"Authentication required",error_details:null}]),e.text("Authentication required",401);const f=await x({idToken:a,idpId:await pe(a,r.getConfig().ssoDirect),idpAccessTokenEnc:n?L(n,m):void 0,idpRefreshTokenEnc:l?F(l,m):void 0,clientId:o.mcpClientId||"",redirectUri:o.originalRedirectUri,codeChallenge:o.mcpCodeChallenge||void 0,codeChallengeMethod:o.mcpCodeChallengeMethod||void 0,ttlSec:600}),_=new URL(o.originalRedirectUri);return _.searchParams.set("code",f),o.mcpState&&_.searchParams.set("state",o.mcpState),M.sendMcpAuthorizationCompletedMessage([{...y(c),redirect_uri:o.originalRedirectUri||null}]),e.redirect(_.toString())}catch(o){const a=o instanceof Error?o.message:String(o),n=o instanceof Error?o.stack:String(o);return M.sendMcpAuthorizationFailedMessage([{...y(c),error:a,error_details:n}]),e.text(`Invalid MCP callback: ${a}`,400)}}}export{$ as SUPPORTED_MCP_GRANT_TYPES,de as createMcpContextToken,qe as mcpAuthorizationHandler,He as mcpCallbackHandler,De as mcpDynamicClientRegistrationHandler,Ue as mcpOAuthAuthorizationServerHandler,Oe as mcpOAuthProtectedResourceHandler,Le as mcpTokenPortalHandler,G as resolveMcpIdpId,ae as verifyAndParseMcpContextToken,ce as verifyPkce};
1
+ import{getCookie as A}from"hono/cookie";import{createHash as J,timingSafeEqual as K}from"node:crypto";import{ulid as V}from"ulid";import{isMcpContentProtected as P}from"../../../plugins/mcp/auth/auth-handlers.js";import{SUPPORTED_MCP_SCOPES as y}from"../../../plugins/mcp/constants.js";import{isRedoclySso as Y,isOidcProviderConfig as v}from"../../auth.js";import{AUTH_URL as Z,JWT_SECRET_KEY as m,MCP_ACCESS_TOKEN_TTL_SEC as $,MCP_REFRESH_SESSION_LIFETIME_SEC as W}from"../../../constants/common.js";import{ServerRoutes as T}from"../../../../constants/api.js";import{AuthCookieNames as R}from"../../../../constants/common.js";import{withPathPrefix as I}from"@redocly/theme/core/utils";import{telemetry as M}from"../../../telemetry/index.js";import{envConfig as U}from"../../../config/env-config.js";import{createMcpAuthorizationCode as x,verifyMcpAuthorizationCode as Q,createMcpRefreshToken as j,verifyMcpRefreshToken as X,createMcpSessionResource as w,oidcJwksCache as z,oidcRefreshTokenGrant as ee,getOidcMetadata as re,discoverJwks as te}from"../../auth.js";import*as S from"../../jwt/jwt.js";import{AlgorithmTypes as D,JwtAlgorithmNotImplemented as ne,JwtTokenExpired as oe,JwtTokenInvalid as ie,JwtTokenNotBefore as se,JwtTokenSignatureMismatched as ce}from"../../jwt/types.js";import{getRequestOrigin as q}from"../../utils/get-request-origin.js";import{encryptClaimValue as L}from"../../../utils/crypto/encrypt-claim-value.js";import{decryptAesGcm as de}from"../../../utils/crypto/decrypt-aes-gcm.js";import{encryptAesGcm as F}from"../../../utils/crypto/encrypt-aes-gcm.js";import{fetchClientMetadataDocument as ae,isClientMetadataDocumentUrl as N,isRedirectUriRegistered as le}from"./mcp-client-metadata.js";const s=(r,e,t=200,o)=>r.json(e,t,{"Content-Type":"application/json",...o??{}}),H=new Set(["S256"]),b=["authorization_code","refresh_token"],pe="https://claude.ai/oauth/claude-code-client-metadata";function _e(r,e,t){if(!H.has(e||""))return!1;const o=J("sha256").update(t).digest("base64url"),c=Buffer.from(o),i=Buffer.from(r);return c.length!==i.length?!1:K(c,i)}async function ue(r){const e=Math.floor(Date.now()/1e3);return S.sign({type:"mcp_context",...r,iat:e,exp:e+600},m,D.HS256)}async function fe(r){await S.verify(r,m,D.HS256);const{payload:e}=S.decode(r);if(e.type!=="mcp_context")throw new Error("Invalid context token type");return e}function He(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);if(e.req.method!=="GET")return s(e,{error:"Method not allowed"},405,{Allow:"GET"});const t=q(e);return s(e,{resource:`${t}${I("/mcp")}`,authorization_servers:[t],bearer_methods_supported:["header"],resource_documentation:`${t}${T.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:[...y],bearer_token_types_supported:["Bearer"]},200,{"Access-Control-Allow-Origin":"*"})}}function be(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const t=q(e);return s(e,{issuer:t,authorization_endpoint:`${t}${I(T.MCP_AUTHORIZATION)}`,token_endpoint:`${t}${I(T.MCP_TOKEN_PORTAL)}`,scopes_supported:[...y],registration_endpoint:`${t}${I(T.MCP_DYNAMIC_CLIENT_REGISTRATION)}`,response_types_supported:["code"],grant_types_supported:[...b],subject_types_supported:["public"],id_token_signing_alg_values_supported:["RS256"],code_challenge_methods_supported:["S256"],token_endpoint_auth_methods_supported:["none"],client_id_metadata_document_supported:!0},200,{"Access-Control-Allow-Origin":"*"})}}function $e(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{return s(e,{client_id:U.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:[...b],response_types:["code"],scope:y.join(" "),subject_type:"public",token_endpoint_auth_method:"none",created_at:new Date().toISOString(),updated_at:new Date().toISOString()},201,{"Access-Control-Allow-Origin":"*"})}catch(t){return s(e,{error:"invalid_request",error_description:t?.message||"Unable to register client"},500,{"Access-Control-Allow-Origin":"*"})}}}function G(r){const e=r||{},t=Object.keys(e),o=t.find(i=>v(e[i])&&Y(e[i])),c=t.find(i=>v(e[i]));return o||c||t[0]}function je(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const t=new URL(e.req.url),{searchParams:o}=t,c=o.get("redirect_uri"),i=o.get("client_id"),a=o.get("code_challenge"),n=o.get("code_challenge_method"),l=V();if(a&&!H.has(n||""))return s(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...H].join(", ")}`},400);if(N(i)&&!a)return s(e,{error:"invalid_request",error_description:"code_challenge is required for client_id_metadata_document clients"},400);if(N(i)&&c)try{const d=await ae(i),p=i===pe;if(!le(c,d.redirect_uris,{ignorePort:p}))return s(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(d){const p=d instanceof Error?d.message:String(d);return s(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${p}`},400)}M.sendMcpAuthorizationStartedMessage([{...w(l),redirect_uri:c||null}]);const f=q(e),_={isMcpFlow:!0,originalRedirectUri:c,mcpClientId:i,mcpState:o.get("state"),mcpSessionId:l,mcpCodeChallenge:a,mcpCodeChallengeMethod:a?n:null,timestamp:Date.now()};try{const d=await ue(_),p=new URL(I(T.IDP_LOGIN),f);p.searchParams.set("redirectTo",`${T.MCP_CALLBACK}/${d}`);const k=G(r.getConfig().ssoDirect);return k?(p.searchParams.set("idpId",k),e.redirect(p.toString())):s(e,{error:"not_found"},404)}catch(d){const p=d instanceof Error?d.message:String(d),k=d instanceof Error?d.stack:String(d);M.sendMcpAuthorizationFailedMessage([{...w(l),error:p,error_details:k}]);const C=new URL(I(`${Z}/oauth2/auth`));return C.search=o.toString(),e.redirect(C.toString())}}}async function B(r){const e=Math.floor(Date.now()/1e3),t=await S.sign({...r.claims,...r.idpId?{idpId:r.idpId}:{},...r.idpAccessTokenEnc?{idp_access_token:r.idpAccessTokenEnc}:{},...r.ttlSec?{iat:e,exp:e+r.ttlSec}:{}},m,D.HS256,r.kid),o=r.ttlSec??(typeof r.claims.exp=="number"?Math.max(r.claims.exp-e,0):$);return{accessToken:t,expiresIn:o}}async function he(r,e,t){const o=g=>s(r,{error:"invalid_grant",error_description:g},400),c=r.get("logger"),i=t.get("refresh_token"),a=t.get("client_id")||void 0;if(!i)return s(r,{error:"invalid_request",error_description:"refresh_token is required"},400);let n;try{n=await X(i)}catch(g){return c?.warn("MCP refresh token rejected: %s",g instanceof Error?g.message:String(g)),o("Invalid refresh token")}if(a&&a!==n.client_id)return o("Client mismatch");const l=n.idp_id?e.getConfig().ssoDirect?.[n.idp_id]:void 0;let f,_,d=n.idp_access_token_enc,p=n.idp_refresh_token_enc;if(n.idp_refresh_token_enc){let g;try{g=de(n.idp_refresh_token_enc,m)}catch{return o("Invalid refresh token")}if(!n.idp_id||!v(l))return o("Unknown identity provider");let u;try{const h=await re(n.idp_id,l);if(!h?.token_endpoint)return o("Identity provider has no token endpoint");u=await ee(h.token_endpoint,g,l)}catch(h){return c?.warn("MCP refresh failed to reach the IdP token endpoint: %s",h instanceof Error?h.message:String(h)),s(r,{error:"server_error",error_description:"Failed to refresh session"},500)}if(u.error||!(u.id_token||u.access_token))return c?.warn("MCP refresh rejected by IdP: %s",u.error||"empty token response"),o("Refresh rejected by identity provider");if(u.refresh_token&&(p=F(u.refresh_token,m)),u.access_token&&(d=L(u.access_token,m)),u.id_token)try{const h=S.decode(u.id_token);f=h.payload,_=h.header.kid}catch{return o("Invalid token response from identity provider")}}const k=v(l)&&l.tokenExpirationTime||$,C=f??n.claims,E=await B({claims:C,kid:_,idpId:n.idp_id,idpAccessTokenEnc:d,ttlSec:f?void 0:k}),O=await j({clientId:n.client_id,idpId:n.idp_id,claims:C,idpAccessTokenEnc:d,idpRefreshTokenEnc:p,sessionExp:n.session_exp});return s(r,{access_token:E.accessToken,token_type:"Bearer",expires_in:E.expiresIn,refresh_token:O,scope:y.join(" ")},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}function ze(r){return async e=>{if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{const t=await e.req.formData(),o=t.get("grant_type"),c=t.get("code"),i=t.get("redirect_uri")||void 0,a=t.get("code_verifier")||void 0;if(!b.includes(o))return s(e,{error:"invalid_request",error_description:"Invalid grant type"},400);if(o==="refresh_token")return await he(e,r,t);if(!c)return s(e,{error:"invalid_request",error_description:"Missing authorization code"},400);try{const n=await Q(c);if(i&&i!==n.redirect_uri)return s(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(n.code_challenge){if(!a)return s(e,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!_e(n.code_challenge,n.code_challenge_method,a))return s(e,{error:"invalid_grant",error_description:"code_verifier mismatch"},400)}if(U.OAUTH_CLIENT_ID&&n.client_id&&!N(n.client_id)&&n.client_id!==U.OAUTH_CLIENT_ID)return s(e,{error:"invalid_client",error_description:"Client mismatch"},400);const l=n.id_token;if(typeof l!="string"||l.length===0)return s(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400);const f=n.idp_access_token_enc??(n.idp_access_token?L(n.idp_access_token,m):void 0),_=S.decode(l),{accessToken:d,expiresIn:p}=await B({claims:_.payload,kid:_.header.kid,idpId:n.idp_id,idpAccessTokenEnc:f}),C=Math.floor(Date.now()/1e3)+W,E=await j({clientId:n.client_id,idpId:n.idp_id,claims:_.payload,idpAccessTokenEnc:f,idpRefreshTokenEnc:n.idp_refresh_token_enc,sessionExp:C}),O={access_token:d,token_type:"Bearer",expires_in:p,refresh_token:E,scope:y.join(" "),id_token:l};return s(e,O,200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch(n){return e.get("logger")?.warn("MCP token exchange failed: %s",n instanceof Error?n.message:String(n)),s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(t){const o=t instanceof Error?t.message:String(t);return s(e,{error:"server_error",error_description:"Failed to process token request",error_details:o},500)}}}async function me(r,e){try{const{payload:t,header:o}=S.decode(r);if(typeof t.idpId=="string"&&t.idpId)return t.idpId;if(o.kid){z.jwks[o.kid]==null&&await te(e||{});const c=z.jwks[o.kid];if(c?.idpId)return c.idpId}}catch{}return G(e)}function Fe(r){return async e=>{const t=new URL(e.req.url);let o=t.searchParams.get("context");if(!o&&t.pathname.startsWith(I(`${T.MCP_CALLBACK}/`))){const i=t.pathname.split("/");o=i[i.length-1]}if(!o)return e.text("Missing context parameter",400);let c=null;try{const i=await fe(o);if(c=i.mcpSessionId||null,!i.isMcpFlow||!i.originalRedirectUri)throw new Error("Invalid MCP context");const a=A(e,R.IDP_ID_TOKEN)||A(e,R.AUTHORIZATION),n=A(e,R.IDP_ACCESS_TOKEN),l=A(e,R.IDP_REFRESH_TOKEN);if(!a)return M.sendMcpAuthorizationFailedMessage([{...w(c),error:"Authentication required",error_details:null}]),e.text("Authentication required",401);const f=await x({idToken:a,idpId:await me(a,r.getConfig().ssoDirect),idpAccessTokenEnc:n?L(n,m):void 0,idpRefreshTokenEnc:l?F(l,m):void 0,clientId:i.mcpClientId||"",redirectUri:i.originalRedirectUri,codeChallenge:i.mcpCodeChallenge||void 0,codeChallengeMethod:i.mcpCodeChallengeMethod||void 0,ttlSec:600}),_=new URL(i.originalRedirectUri);return _.searchParams.set("code",f),i.mcpState&&_.searchParams.set("state",i.mcpState),M.sendMcpAuthorizationCompletedMessage([{...w(c),redirect_uri:i.originalRedirectUri||null}]),e.redirect(_.toString())}catch(i){const a=ge(i);return M.sendMcpAuthorizationFailedMessage([{...w(c),...a}]),e.text(`Invalid MCP callback: ${a.error}`,400)}}}function ge(r){return r instanceof Error&&[ne,oe,ie,se,ce].some(e=>r instanceof e)?{error:r.name,error_details:null}:r instanceof Error?{error:r.message,error_details:r.stack??null}:{error:String(r),error_details:null}}export{b as SUPPORTED_MCP_GRANT_TYPES,ue as createMcpContextToken,je as mcpAuthorizationHandler,Fe as mcpCallbackHandler,$e as mcpDynamicClientRegistrationHandler,be as mcpOAuthAuthorizationServerHandler,He as mcpOAuthProtectedResourceHandler,ze as mcpTokenPortalHandler,G as resolveMcpIdpId,fe as verifyAndParseMcpContextToken,_e as verifyPkce};
@@ -1 +1 @@
1
- import{withPathPrefix as A}from"@redocly/theme/core/utils";import{DEFAULT_DOCS_MCP_SERVER_NAME as b,MCP_PROTOCOL_VERSION as S}from"../../../plugins/mcp/constants.js";import{RESERVED_ROUTES as T}from"../../../plugins/get-reserved-routes.js";import{isMcpCodeModeEnabled as g}from"../../../plugins/mcp/utils/mode.js";import{CODE_MODE_TOOL_NAMES as v,FETCH_TOOL_NAME as M}from"../../../plugins/mcp/codemode/constants.js";import{EXECUTE_TOOL_SCHEMA as O}from"../../../plugins/mcp/codemode/tools/execute-schema.js";import{describeToolsSchema as R}from"../../../plugins/mcp/codemode/tools/describe-tools.js";import{normalizeToolDisplayName as L}from"../../../plugins/mcp/utils/tool-registration.js";import{buildCodeModeInstructions as y,buildExecuteDescription as D}from"../../../plugins/mcp/codemode/prompts.js";import{toToolListEntries as I}from"../../../plugins/mcp/codemode/build-tool-list.js";import{composeExecuteAnnotations as P}from"../../../plugins/mcp/codemode/capabilities/index.js";import{ALL_CAPABILITIES as V}from"../../../plugins/mcp/codemode/capabilities/registry.js";import{isGatewayCapabilityEnabled as H}from"../../../plugins/mcp/gateway-mcp/eligibility.js";import{shouldHandleMcpAuth as U}from"../../../utils/rbac/should-handle-mcp-auth.js";import{resolvePublicBaseUrl as x}from"../../../web-server/utils/resolve-public-base-url.js";import{telemetryTraceStep as N}from"../../../telemetry/helpers/trace-step.js";import{MCP_AUTH_SCHEMES as w}from"./constants.js";const j="https://static.modelcontextprotocol.io/schemas/mcp-server-card/v1.json",k="1.0";function se(r){const s=g(),u=new Date().toISOString().slice(0,10);return async a=>await N("com.redocly.mcpServerCard.served",async e=>{e?.setAttribute("id","mcpServerCardId"),e?.setAttribute("object","mcpServerCard"),e?.setAttribute("uri","urn:redocly:realm:api:mcpServerCard:mcpServerCardId");const o=r.getConfig(),m=o.mcp?.docs?.name||b,n=r.getMcpTools().filter(t=>!v.has(t.name)&&!t.hasAvailabilityGate),i=H(o.mcp)?[...V]:[],l=i.some(t=>t.id===M),d=I(n,i),c=(s?[{...O,description:D(l,d),annotations:P(i)},R(n,i)]:n).map(L).map(({name:t,title:E,description:f,schema:h,annotations:_})=>({name:t,title:E,description:f,inputSchema:h,annotations:_})),C=o.skills?.hide===!0?[]:r.getSkills(),p={$schema:j,version:k,protocolVersion:S,serverInfo:{name:m,title:m,version:u},description:o.seo?.description,documentationUrl:x(r,a),transport:{type:"streamable-http",endpoint:A(T.mcp)},capabilities:{logging:{},tools:{listChanged:!0},...C.length>0?{resources:{listChanged:!0},completions:{}}:{}},authentication:U(!!o.access?.requiresLogin,o.access?.rbac)?{required:!0,schemes:w}:void 0,instructions:s?y(l,d):void 0,tools:c.length>0?c:["dynamic"]};return e?.setAttribute("mode",s?"code":"tools"),e?.setAttribute("toolsCount",c.length),e?.setAttribute("authRequired",p.authentication!==void 0),a.json(p,200,{"Access-Control-Allow-Origin":"*","Cache-Control":"public, max-age=3600"})})}export{se as mcpServerCardHandler};
1
+ import{combineUrls as b,withPathPrefix as u}from"@redocly/theme/core/utils";import{DEFAULT_DOCS_MCP_SERVER_NAME as S,MCP_PROTOCOL_VERSION as T}from"../../../plugins/mcp/constants.js";import{RESERVED_ROUTES as g}from"../../../plugins/get-reserved-routes.js";import{isMcpCodeModeEnabled as v}from"../../../plugins/mcp/utils/mode.js";import{CODE_MODE_TOOL_NAMES as M,FETCH_TOOL_NAME as O}from"../../../plugins/mcp/codemode/constants.js";import{EXECUTE_TOOL_SCHEMA as R}from"../../../plugins/mcp/codemode/tools/execute-schema.js";import{describeToolsSchema as L}from"../../../plugins/mcp/codemode/tools/describe-tools.js";import{normalizeToolDisplayName as y}from"../../../plugins/mcp/utils/tool-registration.js";import{buildCodeModeInstructions as D,buildExecuteDescription as I}from"../../../plugins/mcp/codemode/prompts.js";import{toToolListEntries as P}from"../../../plugins/mcp/codemode/build-tool-list.js";import{composeExecuteAnnotations as U}from"../../../plugins/mcp/codemode/capabilities/index.js";import{ALL_CAPABILITIES as V}from"../../../plugins/mcp/codemode/capabilities/registry.js";import{isGatewayCapabilityEnabled as H}from"../../../plugins/mcp/gateway-mcp/eligibility.js";import{shouldHandleMcpAuth as x}from"../../../utils/rbac/should-handle-mcp-auth.js";import{resolvePublicBaseUrl as N}from"../../../web-server/utils/resolve-public-base-url.js";import{telemetryTraceStep as w}from"../../../telemetry/helpers/trace-step.js";import{MCP_AUTH_SCHEMES as j}from"./constants.js";const k="https://static.modelcontextprotocol.io/schemas/mcp-server-card/v1.json",q="1.0";function ne(r){const s=v(),C=new Date().toISOString().slice(0,10);return async m=>await w("com.redocly.mcpServerCard.served",async e=>{e?.setAttribute("id","mcpServerCardId"),e?.setAttribute("object","mcpServerCard"),e?.setAttribute("uri","urn:redocly:realm:api:mcpServerCard:mcpServerCardId");const o=r.getConfig(),a=o.mcp?.docs?.name||S,n=r.getMcpTools().filter(t=>!M.has(t.name)&&!t.hasAvailabilityGate),i=H(o.mcp)?[...V]:[],l=i.some(t=>t.id===O),d=P(n,i),c=(s?[{...R,description:I(l,d),annotations:U(i)},L(n,i)]:n).map(y).map(({name:t,title:f,description:h,schema:_,annotations:A})=>({name:t,title:f,description:h,inputSchema:_,annotations:A})),E=o.skills?.hide===!0?[]:r.getSkills(),p={$schema:k,version:q,protocolVersion:T,serverInfo:{name:a,title:a,version:C},description:o.seo?.description,documentationUrl:b(N(r,m),u("/")),transport:{type:"streamable-http",endpoint:u(g.mcp)},capabilities:{logging:{},tools:{listChanged:!0},...E.length>0?{resources:{listChanged:!0},completions:{}}:{}},authentication:x(!!o.access?.requiresLogin,o.access?.rbac)?{required:!0,schemes:j}:void 0,instructions:s?D(l,d):void 0,tools:c.length>0?c:["dynamic"]};return e?.setAttribute("mode",s?"code":"tools"),e?.setAttribute("toolsCount",c.length),e?.setAttribute("authRequired",p.authentication!==void 0),m.json(p,200,{"Access-Control-Allow-Origin":"*","Cache-Control":"public, max-age=3600"})})}export{ne as mcpServerCardHandler};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/reef",
3
- "version": "0.137.0-next.11",
3
+ "version": "0.137.0-next.12",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,13 +33,13 @@
33
33
  "@opentelemetry/sdk-trace-web": "2.8.0",
34
34
  "@opentelemetry/semantic-conventions": "1.40.0",
35
35
  "@redocly/ajv": "8.18.0",
36
- "@redocly/api-docs": "0.2.0-next.11",
36
+ "@redocly/api-docs": "0.2.0-next.12",
37
37
  "@redocly/config": "0.56.0",
38
- "@redocly/openapi-core": "2.51.2",
38
+ "@redocly/openapi-core": "2.53.0",
39
39
  "@redocly/portal-legacy-ui": "0.19.0",
40
- "@redocly/portal-plugin-mock-server": "0.22.0-next.8",
41
- "@redocly/realm-asyncapi-sdk": "0.15.0-next.3",
42
- "@redocly/theme": "0.69.0-next.10",
40
+ "@redocly/portal-plugin-mock-server": "0.22.0-next.9",
41
+ "@redocly/realm-asyncapi-sdk": "0.15.0-next.4",
42
+ "@redocly/theme": "0.69.0-next.11",
43
43
  "@shikijs/transformers": "3.21.0",
44
44
  "@tanstack/react-query": "5.62.3",
45
45
  "@tanstack/react-table": "8.21.3",