@redocly/redoc 0.137.0-next.4 → 0.137.0-next.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +79 -0
- package/dist/api-schemas/otel-traces.d.ts +1359 -0
- package/dist/api-schemas/otel-traces.js +1 -0
- package/dist/cli/stats/collectors/asyncapi/index.d.ts +3 -0
- package/dist/cli/stats/collectors/asyncapi/index.js +1 -0
- package/dist/cli/stats/collectors/extension-sampling.d.ts +44 -0
- package/dist/cli/stats/collectors/extension-sampling.js +1 -0
- package/dist/cli/stats/collectors/openapi/index.js +1 -1
- package/dist/cli/stats/collectors/prepare-stats-walk.d.ts +10 -0
- package/dist/cli/stats/collectors/prepare-stats-walk.js +1 -0
- package/dist/cli/stats/collectors/run-stats-collector.d.ts +24 -0
- package/dist/cli/stats/collectors/run-stats-collector.js +1 -0
- package/dist/cli/stats/index.js +1 -1
- package/dist/client/app/hooks/useMcpData.d.ts +1 -0
- package/dist/client/app/hooks/useMcpData.js +1 -1
- package/dist/client/app/markdoc/custom-components/openapi/json-schema.js +1 -1
- package/dist/client/app/markdoc/custom-components/openapi/openapi-code-sample.js +1 -1
- package/dist/client/app/markdoc/custom-components/openapi/openapi-example.js +1 -1
- package/dist/client/app/markdoc/custom-components/openapi/openapi-response-sample.js +1 -1
- package/dist/client/app/markdoc/custom-components/openapi/use-markdown-adapter.d.ts +45 -0
- package/dist/client/app/markdoc/custom-components/openapi/use-markdown-adapter.js +1 -0
- package/dist/client/providers/hooks.js +1 -1
- package/dist/client/providers/post-message/PostMessageProvider.js +1 -1
- package/dist/constants/l10n/langs/ar.js +1 -1
- package/dist/constants/l10n/langs/de.js +1 -1
- package/dist/constants/l10n/langs/en.js +1 -1
- package/dist/constants/l10n/langs/es.js +1 -1
- package/dist/constants/l10n/langs/fr.js +1 -1
- package/dist/constants/l10n/langs/hi.js +1 -1
- package/dist/constants/l10n/langs/it.js +1 -1
- package/dist/constants/l10n/langs/ja.js +1 -1
- package/dist/constants/l10n/langs/ko.js +1 -1
- package/dist/constants/l10n/langs/pl.js +1 -1
- package/dist/constants/l10n/langs/pt-BR.js +1 -1
- package/dist/constants/l10n/langs/pt.js +1 -1
- package/dist/constants/l10n/langs/ru.js +1 -1
- package/dist/constants/l10n/langs/uk.js +1 -1
- package/dist/constants/l10n/langs/zh.js +1 -1
- package/dist/server/config/env-config.d.ts +2 -0
- package/dist/server/config/env-schema.d.ts +5 -0
- package/dist/server/config/env-schemas/search.d.ts +3 -0
- package/dist/server/config/env-schemas/search.js +1 -1
- package/dist/server/constants/mcp.d.ts +5 -0
- package/dist/server/constants/mcp.js +1 -0
- package/dist/server/constants/plugins/search.d.ts +0 -2
- package/dist/server/constants/plugins/search.js +1 -1
- package/dist/server/constants/rbac.d.ts +3 -0
- package/dist/server/constants/rbac.js +1 -0
- package/dist/server/plugins/api-docs/search/get-ai-search-documents.js +9 -9
- package/dist/server/plugins/api-docs/search/search-indexer.js +1 -1
- package/dist/server/plugins/markdown/utils/sanitize-malformed-md-content.js +2 -2
- package/dist/server/plugins/mcp/auth/auth-handlers.d.ts +1 -2
- package/dist/server/plugins/mcp/auth/auth-handlers.js +1 -1
- package/dist/server/plugins/mcp/codemode/tools/describe-tools.d.ts +1 -1
- package/dist/server/plugins/mcp/codemode/tools/describe-tools.js +1 -1
- package/dist/server/plugins/mcp/codemode/tools/execute-schema.js +1 -1
- package/dist/server/plugins/mcp/codemode/tools/manage-credentials.js +2 -2
- package/dist/server/plugins/mcp/constants.d.ts +0 -1
- package/dist/server/plugins/mcp/constants.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/tool-schemas.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/elicitation.d.ts +3 -2
- package/dist/server/plugins/mcp/gateway-mcp/elicitation.js +5 -4
- package/dist/server/plugins/mcp/gateway-mcp/eligibility.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-audit.d.ts +2 -1
- package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-audit.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-bridge.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/fetch/upstream-detail.d.ts +7 -0
- package/dist/server/plugins/mcp/gateway-mcp/fetch/upstream-detail.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/upstream-auth.d.ts +6 -0
- package/dist/server/plugins/mcp/gateway-mcp/upstream-auth.js +1 -1
- package/dist/server/plugins/mcp/handlers/docs-mcp-handler.js +1 -1
- package/dist/server/plugins/mcp/handlers/handle-mcp-request.js +1 -1
- package/dist/server/plugins/mcp/handlers/mcp-credentials-handler.js +1 -1
- package/dist/server/plugins/mcp/handlers/mcp-credentials-page.d.ts +6 -0
- package/dist/server/plugins/mcp/handlers/mcp-credentials-page.js +50 -42
- package/dist/server/plugins/mcp/index.d.ts +0 -2
- package/dist/server/plugins/mcp/index.js +1 -1
- package/dist/server/plugins/mcp/servers/docs-server.js +1 -1
- package/dist/server/plugins/mcp/servers/mcp-server.js +3 -3
- package/dist/server/plugins/mcp/types.d.ts +1 -0
- package/dist/server/plugins/mcp/utils/tool-registration.d.ts +17 -0
- package/dist/server/plugins/mcp/utils/tool-registration.js +1 -0
- package/dist/server/plugins/openapi-docs/search/get-ai-search-documents.js +5 -5
- package/dist/server/plugins/search/engines/typesense/index.js +1 -1
- package/dist/server/plugins/search/index.js +1 -1
- package/dist/server/plugins/search/utils.d.ts +2 -1
- package/dist/server/plugins/search/utils.js +2 -2
- package/dist/server/types/plugins/common.d.ts +1 -0
- package/dist/server/utils/rbac/can-anonymous-access-mcp-feature.d.ts +3 -0
- package/dist/server/utils/rbac/can-anonymous-access-mcp-feature.js +1 -0
- package/dist/server/utils/rbac/should-handle-mcp-auth.d.ts +2 -0
- package/dist/server/utils/rbac/should-handle-mcp-auth.js +1 -0
- package/dist/server/utils/rbac.js +1 -1
- package/dist/server/web-server/auth.js +3 -3
- package/dist/server/web-server/handle-api-route-request.js +1 -1
- package/dist/server/web-server/routes/error.js +1 -1
- package/dist/server/web-server/routes/helpers/resolve-ui-locales-for-idp-login.js +1 -1
- package/dist/server/web-server/routes/index.js +1 -1
- package/dist/server/web-server/routes/mcp-routes/agent-card.js +1 -1
- package/dist/server/web-server/routes/mcp-routes/mcp-server-card.js +1 -1
- package/dist/server/web-server/routes/otel/otel.js +1 -1
- package/dist/server/web-server/routes/page-data.js +1 -1
- package/dist/server/web-server/utils/prepare-list-response.d.ts +0 -4
- package/dist/server/web-server/utils/prepare-list-response.js +1 -1
- package/dist/server/web-server/utils.d.ts +1 -1
- package/dist/server/web-server/utils.js +1 -1
- package/dist/types/global-data.d.ts +1 -0
- package/package.json +9 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
import{logger as
|
|
1
|
+
import{logger as E}from"../../../tools/notifiers/logger.js";import{getFeedbackClientIp as F}from"../../../web-server/utils/feedback-ip.js";import{getForwardedOrigin as g}from"../../../web-server/utils/get-request-origin.js";import{GATEWAY_FETCH_LIMIT_DEFAULTS as N}from"../../../../constants/common.js";import{isObject as v}from"../../../../utils/guards/is-object.js";import{getAccessibleSkills as k}from"../../../utils/skills/get-accessible-skills.js";import{getSkillUrl as B}from"../../../utils/skills/get-skill-url.js";import{createDocsMcpServer as L}from"../servers/docs-server.js";import{filterApiDescriptionsByRbac as H,filterGraphqlDescriptionsByRbac as j}from"../utils.js";import{createMethodNotAllowedError as V,withErrorHandling as $}from"./errors.js";import{constructForbiddenResponse as G,constructInvalidTokenResponse as J,constructUnauthorizedResponse as Y,handleMcpAuth as z}from"../auth/auth-handlers.js";import{shouldHandleMcpAuth as K}from"../../../utils/rbac/should-handle-mcp-auth.js";import{canAnonymousAccessMcpFeature as W}from"../../../utils/rbac/can-anonymous-access-mcp-feature.js";import{extractTokenFromAuthHeader as X}from"../utils/jwt.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as Q}from"../constants.js";import{withPathPrefix as Z}from"@redocly/theme/core/utils";import{PUBLIC_MCP_ROUTE as q}from"@redocly/theme/core/constants/mcp";import{canAccessFeature as x}from"../../../utils/rbac.js";import{ANONYMOUS_AUTH_DETAILS as ee}from"../../../constants/rbac.js";import{DEFAULT_ANONYMOUS_VISITOR_TEAM as re,RbacFeatures as oe}from"../../../../constants/common.js";const h="X-Redocly-AI-Metadata";function se(r){if(r)try{const e=JSON.parse(r);if(v(e))return e;E.warn(`Ignoring ${h} header: not a JSON object.`)}catch{E.warn(`Ignoring ${h} header: invalid JSON.`)}}const ne=async(r,e,o)=>{const i=!!e?.config?.access?.requiresLogin,t=e?.config?.access?.rbac||{},A=o?.props?.config?.mcpMode??"tools",T=o?.props?.config?.gatewayFetchBudgetPerExec??N.budgetPerExec,b=o?.props?.config?.searchIgnoredRoutes??[],s=o,S=!!s?.props?.config?.isPublicEndpoint;let d;const M=r.headers.get("Authorization");if(S)e.user={...ee,email:"",idpAccessToken:void 0,idpId:void 0};else if(K(i,t)){const{isAuthenticated:u,isTokenValid:l,currentUser:p,accessToken:f}=await z(r,e);if(!u){const a=g(r),m=e.config.mcp?.docs?.publicEndpoint&&W(t,i)?`${a}${Z(q)}`:void 0;return l===!1?J(a):Y(a,m)}p&&(e.user=p),d=f}else M&&(d=X(M||"")??void 0);const U={isAuthenticated:!!e.user?.isAuthenticated,teams:e.user?.teams||[re],claims:e.user?.claims??{}};if(!x(oe.MCP,U,t,i))return G();let c;return await $(async()=>{const u=o?.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 V();const l=s?.props?.config?.apiDescriptionsMap||{},p=s?.props?.config?.graphqlDescriptions||[],f=s?.props?.config?.mcpDocsServerName||Q,{config:{mcp:a={}}}=e,m=H(l,e.user,t,i),D=j(p,e.user,t,i),I=e.config.products?Object.values(e.config.products).map(n=>n?.name):[],R=a.docs?.name||f,y=se(r.headers.get(h)),O=s?.props?.tools||[],w=s?.props?.skillsBaseUrl||g(r),C=k(s?.props?.skills||[],e.user,t,i).map(n=>({slug:n.slug,name:n.name,description:n.description,content:n.content,uri:B(w,n.slug)})),_=await r.json(),P={name:R,tools:O,skills:C,context:{...e,accessToken:d,outdir:e.outdir||"",baseUrl:e.baseUrl||g(r),apiDescriptionsMap:m,graphqlDescriptions:D,products:I,metadata:y,mcpMode:A,elicitationEnabled:u,gatewayFetchBudgetPerExec:T,searchIgnoredRoutes:b,clientIp:F(r)}};return c=await L({...P,getKv:e.getKv}),await ie(r,c,_)},async()=>{c&&(await c.cleanup(),c=void 0)},A)};async function ie(r,e,o){return e.transport.handleRequest(r,o!==void 0?{parsedBody:o}:void 0)}var ye=ne;export{ye as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{withPathPrefix as g}from"@redocly/theme/core/utils";import{importApiRoutesHandler as D}from"../../../api-routes/import-api-routes-handlers.js";import{enhanceContext as h}from"../../../api-routes/helpers/enhance-context.js";import{telemetry as A}from"../../../telemetry/index.js";import{KvService as E}from"../../../persistence/kv/services/kv-service.js";import{envConfig as l}from"../../../config/env-config.js";import{acceptsHtml as R}from"../../../utils/ai-agent-detection.js";import{handleErrorPageRender as T}from"../../../web-server/utils.js";import{
|
|
1
|
+
import{withPathPrefix as g}from"@redocly/theme/core/utils";import{importApiRoutesHandler as D}from"../../../api-routes/import-api-routes-handlers.js";import{enhanceContext as h}from"../../../api-routes/helpers/enhance-context.js";import{telemetry as A}from"../../../telemetry/index.js";import{KvService as E}from"../../../persistence/kv/services/kv-service.js";import{envConfig as l}from"../../../config/env-config.js";import{acceptsHtml as R}from"../../../utils/ai-agent-detection.js";import{handleErrorPageRender as T}from"../../../web-server/utils.js";import{MCP_DOCS_SERVER_HANDLER_ID as _,MCP_NOT_ALLOWED_TEMPLATE_ID as w}from"../../../constants/mcp.js";import{resolvePublicBaseUrl as S}from"../../../web-server/utils/resolve-public-base-url.js";import{getForwardedOrigin as q}from"../../../web-server/utils/get-request-origin.js";import{applyEdgeRateLimit as O}from"../gateway-mcp/rate-limit/apply-edge-rate-limit.js";async function N(a,t,e){const n=t.req.raw;if(a.requestHandlerId===_&&n.method==="GET"&&R(n.headers.get("accept"))&&e.globalData.mcpData?.docs?.enabled)return T(t,e,{slug:a.slug},405,w);const r=t.get("auth"),s=await O(n,e);if(s)return s;const o=await e.resolveRouteStaticData(a)||{},m=g(a.slug),{requestHandlers:c}=await D(e.serverOutDir),p=c[a.requestHandlerId],d=(await p()).default,u=async()=>await E.getInstance({baseDbDir:e.serverOutDir,sqldRemoteDatabaseUrl:l.SQLD_REMOTE_DATABASE_URL,sqldRemoteDatabaseAuthToken:l.SQLD_REMOTE_DATABASE_AUTH_TOKEN}),f=h({honoCtx:t,ctx:{user:{teams:r.teams,email:r.claims.email,claims:r.claims,idpAccessToken:r.idpAccessToken,idpId:r.claims.idpId,isAuthenticated:r.isAuthenticated},config:e.config,outdir:e.outdir,baseUrl:q(t.req.raw)},telemetry:A,getKv:u}),i=await d(t.req.raw,f,{...o,props:{...o.props,routeSlug:m,outdir:e.outdir,skills:e.config.skills?.hide===!0?[]:e.getSkills(),skillsBaseUrl:S(e,t)}});return i instanceof Response?i:typeof i=="string"?new Response(i,{headers:{"Content-Type":"text/plain"}}):new Response(JSON.stringify(i),{headers:{"Content-Type":"application/json"}})}export{N as handleMcpRequest};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{secretStore as u}from"../gateway-mcp/secret-store.js";import{rejectCredential as
|
|
1
|
+
import{secretStore as u}from"../gateway-mcp/secret-store.js";import{rejectCredential as y}from"../gateway-mcp/credential-validation.js";import{buildAllowedHostMatchers as R}from"../gateway-mcp/fetch/allowed-hosts.js";import{assertUrlAllowed as j}from"../gateway-mcp/fetch/ssrf-guard.js";import{readCappedBody as v,getDefaultAgent as O}from"../gateway-mcp/fetch/fetch-bridge.js";import{logger as N}from"../../../tools/notifiers/logger.js";import{clearedPage as F,formPage as x,multiOutcomePage as I,notFoundPage as D,successPage as $}from"./mcp-credentials-page.js";const w={"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'"},M=3e4,U=64*1024;let P=fetch;function q(e){P=e}const B=async(e,i)=>{const t=await i.getKv();if(e.method==="GET"){const n=new URL(e.url).searchParams.get("token")??"",o=await u.peekElicitation(t,n);return o?new Response(x(o,n),{status:200,headers:w}):m()}if(e.method==="POST"){const n=await e.formData(),o=d(n,"token"),a=d(n,"mode")||"token";return a==="multi"?z(t,n,o):a==="client-credentials"?K(t,n,o):a==="clear"?H(t,o):G(t,n,o)}return new Response("Method Not Allowed",{status:405,headers:{Allow:"GET, POST"}})};async function G(e,i,t){const n=d(i,"secret").trim(),o=await u.peekElicitation(e,t);if(!o)return m();const a=y(o.auth,n);if(a)return h(o,t,a);const l=await u.consumeElicitation(e,t);return l?(await u.setSecret(e,l.subject,l.api,l.scheme,E(l.auth,n)),new Response($(l),{status:200,headers:w})):m()}async function H(e,i){const t=await u.consumeElicitation(e,i);return t?(await u.deleteSecret(e,t.subject,t.api,t.scheme),new Response(F(t),{status:200,headers:w})):m()}async function z(e,i,t){const n=await u.peekElicitation(e,t);if(!n?.schemes||n.schemes.length<=1)return m();const o=n.schemes,a=[];for(let r=0;r<o.length;r++){const s=o[r],p=d(i,`secret__${r}`).trim(),S=d(i,`client_id__${r}`).trim(),b=d(i,`client_secret__${r}`).trim();if(s.hasExistingSecret&&d(i,`remove__${r}`)){if(p||S||b)return h(n,t,`${s.name}: enter a new value or tick "Remove the stored credential", not both.`);a.push({kind:"remove",scheme:s.name});continue}if(s.auth.kind==="oauth2ClientCredentials"){if(p){const C=y(s.auth,p);if(C)return h(n,t,`${s.name}: ${C}`);a.push({kind:"set",scheme:s.name,secret:E(s.auth,p)})}else if(S&&b)a.push({kind:"exchange",scheme:s.name,auth:s.auth,clientId:S,clientSecret:b});else if(S||b)return h(n,t,`${s.name}: enter both the client ID and the client secret, or leave both blank.`);continue}if(!p)continue;const _=y(s.auth,p);if(_)return h(n,t,`${s.name}: ${_}`);a.push({kind:"set",scheme:s.name,secret:E(s.auth,p)})}if(a.length===0)return h(n,t,"Fill in at least one credential, or check a box to remove one, before saving.");const l=[];for(const r of a){if(r.kind!=="exchange"){l.push(r);continue}const s=await T(r.auth,r.clientId,r.clientSecret);if(!s.ok)return A(n,t,s.reason,r.scheme);l.push({kind:"set",scheme:r.scheme,secret:s.secret})}const c=await u.consumeElicitation(e,t);if(!c)return m();for(const r of l)r.kind==="remove"?await u.deleteSecret(e,c.subject,c.api,r.scheme):await u.setSecret(e,c.subject,c.api,r.scheme,r.secret);const k=await u.getSecretsForSubject(e,c.subject),f={saved:l.filter(r=>r.kind==="set").map(r=>r.scheme),removed:l.filter(r=>r.kind==="remove").map(r=>r.scheme),stored:Object.keys(k[c.api]??{})};return new Response(I(c,f),{status:200,headers:w})}async function K(e,i,t){const n=d(i,"client_id").trim(),o=d(i,"client_secret").trim(),a=await u.peekElicitation(e,t);if(!a||a.auth.kind!=="oauth2ClientCredentials")return m();if(!n||!o)return h(a,t,"Enter both the client ID and the client secret before saving.");const l=await T(a.auth,n,o);if(!l.ok)return A(a,t,l.reason);const c=await u.consumeElicitation(e,t);return c?(await u.setSecret(e,c.subject,c.api,c.scheme,l.secret),new Response($(c),{status:200,headers:w})):m()}async function T(e,i,t){try{const n=R([{url:e.tokenUrl}]),o=j(e.tokenUrl,n),a=new AbortController,l=setTimeout(()=>a.abort(),M);let c;try{c=await P(o,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"client_credentials",client_id:i,client_secret:t}),redirect:"manual",signal:a.signal,dispatcher:O()})}finally{clearTimeout(l)}if(!c.ok)return g(`token endpoint returned ${c.status}`);let k;try{k=await v(c,U)}catch{return g("token response exceeded the size cap")}let f;try{f=JSON.parse(k)}catch{return g("token response was not valid JSON")}const r=typeof f=="object"&&f!==null&&"access_token"in f?f.access_token:void 0,s=typeof r=="string"?r.trim():"";return s?{ok:!0,secret:{header:"Authorization",value:`Bearer ${s}`}}:g("token response had no access_token")}catch(n){return g(n instanceof Error?n.message:String(n))}}function g(e){return{ok:!1,reason:e}}function A(e,i,t,n){return N.warn(`gateway oauth2 client_credentials exchange failed for ${e.api}: ${t}`),h(e,i,`${n?`${n}: we`:"We"} could not exchange those client credentials for a token. Check the client ID and secret, then try again.`)}function d(e,i){const t=e.get(i);return typeof t=="string"?t:""}function h(e,i,t){return new Response(x(e,i,t),{status:400,headers:w})}function m(){return new Response(D(),{status:404,headers:w})}function E(e,i){return e.kind==="apiKey"?{header:e.header,value:i}:e.kind==="cookie"?{header:"Cookie",value:`${e.name}=${i}`}:e.kind==="bearer"||e.kind==="oauth2ClientCredentials"?{header:"Authorization",value:X(i)}:{header:"Authorization",value:i}}function X(e){const i=e.trim();return i.toLowerCase().startsWith("bearer")?i:`Bearer ${i}`}var ee=B;export{ee as default,q as setCredentialsFetchImpl};
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { ElicitationTarget } from '../gateway-mcp/secret-store.js';
|
|
2
2
|
export declare function formPage(target: ElicitationTarget, token: string, error?: string): string;
|
|
3
3
|
export declare function clearedPage(target: ElicitationTarget): string;
|
|
4
|
+
export type MultiOutcome = {
|
|
5
|
+
saved: readonly string[];
|
|
6
|
+
removed: readonly string[];
|
|
7
|
+
stored: readonly string[];
|
|
8
|
+
};
|
|
9
|
+
export declare function multiOutcomePage(target: ElicitationTarget, outcome: MultiOutcome): string;
|
|
4
10
|
export declare function successPage(target: ElicitationTarget): string;
|
|
5
11
|
export declare function notFoundPage(): string;
|
|
6
12
|
//# sourceMappingURL=mcp-credentials-page.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{ELICITATION_TTL_MS as
|
|
1
|
+
import{ELICITATION_TTL_MS as m}from"../gateway-mcp/secret-store.js";const f=6,b=`<svg class="logo" width="193" height="56" viewBox="0 0 193 56" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Redocly">
|
|
2
2
|
<g clip-path="url(#redocly-logo-clip)">
|
|
3
3
|
<path d="M84.3634 40.7533H80.0357L73.199 30.9849H69.8119V40.8155H66.1738V14.5592H76.3353C78.9067 14.5592 80.9139 15.368 82.2308 16.9857C83.5483 18.5412 84.1756 20.5322 84.1756 22.8343C84.1756 25.0119 83.6107 26.7541 82.4193 28.3095C81.2902 29.865 79.5339 30.7361 77.2134 30.8605L84.3634 40.7533ZM69.8743 17.8568V27.6251H76.2098C79.22 27.6251 80.5999 25.4475 80.5999 22.7098C80.5999 20.0344 79.22 17.7945 76.3353 17.7945H69.8743V17.8568Z" fill="currentColor"/>
|
|
4
4
|
<path d="M103.117 33.225H88.5654C89.0042 36.1492 91.2623 38.078 94.3355 38.078C96.5312 38.078 98.5384 37.0203 99.4789 35.3403C99.8553 35.4648 100.357 35.7137 101.047 35.9626L102.553 36.4603C101.047 39.5091 97.9111 41.1889 94.2731 41.1889C91.701 41.1889 89.506 40.3179 87.6873 38.638C85.931 36.8959 85.1152 34.7182 85.1152 31.9184C85.1152 29.1185 85.9934 26.9409 87.6873 25.2609C89.4436 23.5188 91.6386 22.71 94.2731 22.71C96.8445 22.71 98.9771 23.581 100.608 25.2609C102.302 26.9409 103.18 29.1807 103.18 31.9184C103.117 32.5405 103.117 32.9139 103.117 33.225ZM99.6674 30.4873C99.228 27.563 97.0954 25.7587 94.21 25.7587C91.1999 25.7587 88.9418 27.6252 88.5654 30.4873H99.6674Z" fill="currentColor"/>
|
|
@@ -13,7 +13,7 @@ import{ELICITATION_TTL_MS as p}from"../gateway-mcp/secret-store.js";const h=6,f=
|
|
|
13
13
|
<path d="M28.5385 16.6127H44.4703C45.3484 16.6127 46.0383 15.9282 46.0383 15.0572C46.0383 14.1861 45.3484 13.5017 44.4703 13.5017H28.5385C27.6604 13.5017 26.9705 14.1861 26.9705 15.0572C26.9077 15.8661 27.6604 16.6127 28.5385 16.6127Z" fill="currentColor"/>
|
|
14
14
|
</g>
|
|
15
15
|
<defs><clipPath id="redocly-logo-clip"><rect width="193" height="56" fill="white"/></clipPath></defs>
|
|
16
|
-
</svg>`;function
|
|
16
|
+
</svg>`;function r(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""")}function s(e){return`<!doctype html>
|
|
17
17
|
<html lang="en">
|
|
18
18
|
<head>
|
|
19
19
|
<meta charset="utf-8">
|
|
@@ -66,7 +66,9 @@ body{font-family:var(--font);margin:0;min-height:100vh;display:flex;align-items:
|
|
|
66
66
|
.logo{color:var(--brand);height:28px;width:auto;display:block;margin:0 0 1.5rem}
|
|
67
67
|
h1{font-size:1.25rem;line-height:1.3;font-weight:600;margin:0 0 .5rem;letter-spacing:-.01em}
|
|
68
68
|
.section{margin-top:1.5rem;padding-top:1.5rem;border-top:1px solid var(--border)}
|
|
69
|
-
.section h2{font-size:.9375rem;font-weight:600;margin:0 0 .5rem}
|
|
69
|
+
.section h2{font-size:.9375rem;font-weight:600;margin:0 0 .5rem;display:flex;align-items:center;gap:.5rem}
|
|
70
|
+
.badge{font-size:.6875rem;font-weight:600;line-height:1;padding:.25rem .5rem;border-radius:999px;border:1px solid var(--border-strong);color:var(--text-muted);background:var(--code-bg)}
|
|
71
|
+
.badge.stored{color:var(--action);border-color:var(--action)}
|
|
70
72
|
p{margin:.75rem 0 0}
|
|
71
73
|
p.hint{color:var(--text-muted);font-size:.8125rem;line-height:1.45}
|
|
72
74
|
p.error{margin:1rem 0 0;padding:.75rem;border:1px solid var(--error-border);border-radius:6px;background:var(--error-bg);color:var(--error-text);font-size:.8125rem;line-height:1.45}
|
|
@@ -86,22 +88,22 @@ label.check input{width:auto;margin:0}
|
|
|
86
88
|
footer{margin-top:1.5rem;padding-top:1rem;border-top:1px solid var(--border);font-size:.75rem;color:var(--text-muted);text-align:center;line-height:1.45}
|
|
87
89
|
</style>
|
|
88
90
|
</head>
|
|
89
|
-
<body><main class="card">${
|
|
90
|
-
</html>`}function l(e){return`<h1>\u{1F512} ${
|
|
91
|
+
<body><main class="card">${b}${e}</main></body>
|
|
92
|
+
</html>`}function l(e){return`<h1>\u{1F512} ${r(e)}</h1>`}function d(){return"<footer>Secured by Redocly · Credentials never enter the AI conversation</footer>"}function c(){return`<p class="hint">This link expires ${Math.round(m/6e4)} minutes after it was issued. If it stops working, request a new one in your AI client.</p>`}function p({name:e,schemeLabel:t,schemes:o}){return o&&o.length>1?e:t?`${e} (${t})`:e}function g(e){return`
|
|
91
93
|
<div class="section">
|
|
92
94
|
<h2>Remove stored credentials</h2>
|
|
93
95
|
<p class="hint">This API already has credentials stored. Enter new credentials to replace them, or remove them to make calls anonymous again.</p>
|
|
94
96
|
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
95
|
-
<input type="hidden" name="token" value="${
|
|
97
|
+
<input type="hidden" name="token" value="${r(e)}">
|
|
96
98
|
<input type="hidden" name="mode" value="clear">
|
|
97
99
|
<button class="danger" type="submit">Remove credentials</button>
|
|
98
100
|
</form>
|
|
99
|
-
</div>`}function
|
|
101
|
+
</div>`}function M(e,t,o){const n=o?`<p class="error">${r(o)}</p>`:"";if(e.schemes&&e.schemes.length>1)return k(e,e.schemes,t,n);const a=e.hasExistingSecret?g(t):"",i=p(e);switch(e.auth.kind){case"bearer":return C(i,t,a,n);case"apiKey":return v(i,t,e.auth.header,a,n);case"cookie":return y(i,t,e.auth.name,a,n);case"oauth2ClientCredentials":return $(i,t,e.auth.scopes,a,n);default:return x(i,t,a,n)}}function C(e,t,o,n){return s(`
|
|
100
102
|
${l(`Credentials for ${e}`)}
|
|
101
|
-
${
|
|
103
|
+
${n}
|
|
102
104
|
<p class="hint">Encrypted and visible only to you for 24 hours (anonymous) or 30 days (signed in).<br />Sent to the API as the <code>Authorization header</code>. It never passes through the AI conversation.</p>
|
|
103
105
|
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
104
|
-
<input type="hidden" name="token" value="${
|
|
106
|
+
<input type="hidden" name="token" value="${r(t)}">
|
|
105
107
|
<input type="hidden" name="mode" value="token">
|
|
106
108
|
<label for="secret">API token</label>
|
|
107
109
|
<input type="password" id="secret" name="secret" placeholder="your API token" required autofocus>
|
|
@@ -110,41 +112,41 @@ ${r}
|
|
|
110
112
|
</form>
|
|
111
113
|
${o}
|
|
112
114
|
${c()}
|
|
113
|
-
${d()}`)}function
|
|
115
|
+
${d()}`)}function v(e,t,o,n,a){return s(`
|
|
114
116
|
${l(`Credentials for ${e}`)}
|
|
115
117
|
${a}
|
|
116
|
-
<p class="hint">Encrypted and visible only to you for 24 hours (anonymous) or 30 days (signed in).<br />Sent to the API as the <code>${
|
|
118
|
+
<p class="hint">Encrypted and visible only to you for 24 hours (anonymous) or 30 days (signed in).<br />Sent to the API as the <code>${r(o)}</code> header. It never passes through the AI conversation.</p>
|
|
117
119
|
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
118
|
-
<input type="hidden" name="token" value="${
|
|
120
|
+
<input type="hidden" name="token" value="${r(t)}">
|
|
119
121
|
<input type="hidden" name="mode" value="token">
|
|
120
|
-
<label for="secret">${
|
|
122
|
+
<label for="secret">${r(o)} value</label>
|
|
121
123
|
<input type="password" id="secret" name="secret" placeholder="value" required autofocus>
|
|
122
124
|
<button type="submit">Save credentials</button>
|
|
123
125
|
</form>
|
|
124
|
-
${
|
|
126
|
+
${n}
|
|
125
127
|
${c()}
|
|
126
|
-
${d()}`)}function
|
|
128
|
+
${d()}`)}function y(e,t,o,n,a){return s(`
|
|
127
129
|
${l(`Credentials for ${e}`)}
|
|
128
130
|
${a}
|
|
129
|
-
<p class="hint">Encrypted and visible only to you for 24 hours (anonymous) or 30 days (signed in).<br />Sent to the API as the <code>${
|
|
131
|
+
<p class="hint">Encrypted and visible only to you for 24 hours (anonymous) or 30 days (signed in).<br />Sent to the API as the <code>${r(o)}</code> cookie. It never passes through the AI conversation.</p>
|
|
130
132
|
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
131
|
-
<input type="hidden" name="token" value="${
|
|
133
|
+
<input type="hidden" name="token" value="${r(t)}">
|
|
132
134
|
<input type="hidden" name="mode" value="token">
|
|
133
|
-
<label for="secret">${
|
|
135
|
+
<label for="secret">${r(o)} cookie value</label>
|
|
134
136
|
<input type="password" id="secret" name="secret" placeholder="value" required autofocus>
|
|
135
|
-
<p class="hint">Paste only the value. It's sent as <code>${
|
|
137
|
+
<p class="hint">Paste only the value. It's sent as <code>${r(o)}=<value></code>.</p>
|
|
136
138
|
<button type="submit">Save credentials</button>
|
|
137
139
|
</form>
|
|
138
|
-
${
|
|
140
|
+
${n}
|
|
139
141
|
${c()}
|
|
140
|
-
${d()}`)}function
|
|
142
|
+
${d()}`)}function $(e,t,o,n,a){const i=o.length>0?o.map(r).join(", "):"default scopes";return s(`
|
|
141
143
|
${l(`Credentials for ${e}`)}
|
|
142
144
|
${a}
|
|
143
145
|
<p class="hint">Paste your token, or provide your OAuth2 client credentials.</p>
|
|
144
146
|
<div class="section">
|
|
145
147
|
<h2>I have a token</h2>
|
|
146
148
|
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
147
|
-
<input type="hidden" name="token" value="${
|
|
149
|
+
<input type="hidden" name="token" value="${r(t)}">
|
|
148
150
|
<input type="hidden" name="mode" value="token">
|
|
149
151
|
<label for="secret">API token</label>
|
|
150
152
|
<input type="password" id="secret" name="secret" placeholder="your API token" required autofocus>
|
|
@@ -156,7 +158,7 @@ ${a}
|
|
|
156
158
|
<h2>Exchange client credentials</h2>
|
|
157
159
|
<p class="hint">We request a token from the API on your behalf. Scopes: ${i}.</p>
|
|
158
160
|
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
159
|
-
<input type="hidden" name="token" value="${
|
|
161
|
+
<input type="hidden" name="token" value="${r(t)}">
|
|
160
162
|
<input type="hidden" name="mode" value="client-credentials">
|
|
161
163
|
<label for="client_id">Client ID</label>
|
|
162
164
|
<input type="text" id="client_id" name="client_id" required>
|
|
@@ -165,14 +167,14 @@ ${a}
|
|
|
165
167
|
<button type="submit">Exchange & save</button>
|
|
166
168
|
</form>
|
|
167
169
|
</div>
|
|
168
|
-
${
|
|
170
|
+
${n}
|
|
169
171
|
${c()}
|
|
170
|
-
${d()}`)}function
|
|
172
|
+
${d()}`)}function x(e,t,o,n){return s(`
|
|
171
173
|
${l(`Credentials for ${e}`)}
|
|
172
|
-
${
|
|
174
|
+
${n}
|
|
173
175
|
<p class="hint">The value is stored and encrypted only for you for 24 hours (anonymous) or 30 days (signed in).<br />It is sent to the API as the <code>Authorization</code> header. It never passes through the AI conversation.</p>
|
|
174
176
|
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
175
|
-
<input type="hidden" name="token" value="${
|
|
177
|
+
<input type="hidden" name="token" value="${r(t)}">
|
|
176
178
|
<input type="hidden" name="mode" value="token">
|
|
177
179
|
<label for="secret">Authorization header value</label>
|
|
178
180
|
<input type="password" id="secret" name="secret" placeholder="Bearer <your token>" required autofocus>
|
|
@@ -180,38 +182,44 @@ ${r}
|
|
|
180
182
|
</form>
|
|
181
183
|
${o}
|
|
182
184
|
${c()}
|
|
183
|
-
${d()}`)}function
|
|
185
|
+
${d()}`)}function k(e,t,o,n){const a=t.map((i,u)=>w(i,u)).join(`
|
|
184
186
|
`);return s(`
|
|
185
187
|
${l(`Credentials for ${e.name}`)}
|
|
186
|
-
${
|
|
188
|
+
${n}
|
|
187
189
|
<p class="hint">This API accepts more than one kind of credential. Fill in the one(s) you have and leave the rest blank - each value is stored encrypted for you only (24 hours for anonymous callers, 30 days when signed in) and sent straight to the API. It never passes through the AI conversation.</p>
|
|
188
190
|
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
189
|
-
<input type="hidden" name="token" value="${
|
|
191
|
+
<input type="hidden" name="token" value="${r(o)}">
|
|
190
192
|
<input type="hidden" name="mode" value="multi">
|
|
191
193
|
${a}
|
|
192
194
|
<button type="submit">Save credentials</button>
|
|
193
195
|
</form>
|
|
194
196
|
${c()}
|
|
195
|
-
${d()}`)}function
|
|
197
|
+
${d()}`)}function w(e,t){const o=P(e.auth),n=e.label?`<p class="hint">${r(e.label)}</p>`:"",a=_(e.operations),i=e.hasExistingSecret?`<label class="check"><input type="checkbox" name="remove__${t}" value="1"> Remove the stored credential</label>`:"",u=e.hasExistingSecret?'<span class="badge stored">Stored</span>':'<span class="badge">Not set</span>';return`
|
|
196
198
|
<div class="section">
|
|
197
|
-
<h2>${
|
|
198
|
-
<p class="hint">Sent to the API as the <code>${
|
|
199
|
-
${
|
|
199
|
+
<h2>${r(e.name)} ${u}</h2>
|
|
200
|
+
<p class="hint">Sent to the API as the <code>${r(o.code)}</code> ${o.suffix}.</p>
|
|
201
|
+
${n}
|
|
200
202
|
${a}
|
|
201
|
-
${
|
|
203
|
+
${I(e.auth,t)}
|
|
202
204
|
${i}
|
|
203
|
-
</div>`}function
|
|
205
|
+
</div>`}function I(e,t){const o=`secret__${t}`;if(e.kind==="oauth2ClientCredentials"){const a=e.scopes.length>0?e.scopes.map(r).join(", "):"default scopes";return`<label for="${o}">API token</label>
|
|
204
206
|
<input type="password" id="${o}" name="${o}" placeholder="paste a token you already have">
|
|
205
207
|
<p class="hint">Or exchange client credentials for one (scopes: ${a}). We add <code>Bearer</code> automatically.</p>
|
|
206
208
|
<label for="client_id__${t}">Client ID</label>
|
|
207
209
|
<input type="text" id="client_id__${t}" name="client_id__${t}">
|
|
208
210
|
<label for="client_secret__${t}">Client secret</label>
|
|
209
|
-
<input type="password" id="client_secret__${t}" name="client_secret__${t}">`}const
|
|
210
|
-
<input type="password" id="${o}" name="${o}" placeholder="value">`}function
|
|
211
|
+
<input type="password" id="client_secret__${t}" name="client_secret__${t}">`}const n=A(e);return`<label for="${o}">${r(n)}</label>
|
|
212
|
+
<input type="password" id="${o}" name="${o}" placeholder="value">`}function A(e){switch(e.kind){case"apiKey":return`${e.header} value`;case"cookie":return`${e.name} cookie value`;case"bearer":return"API token";default:return"Authorization header value"}}function P(e){switch(e.kind){case"apiKey":return{code:e.header,suffix:"header"};case"cookie":return{code:e.name,suffix:"cookie"};case"bearer":return{code:"Authorization",suffix:"header (bearer token)"};case"oauth2ClientCredentials":return{code:"Authorization",suffix:"header (OAuth2 bearer token)"};default:return{code:"Authorization",suffix:"header"}}}function _(e){if(!e||e.length===0)return"";const t=e.slice(0,f).map(r).join(", "),o=e.length-f,n=o>0?`, and ${o} more`:"";return`<p class="hint">Used by ${t}${n}.</p>`}function S(e){return s(`
|
|
211
213
|
${l("Credentials removed")}
|
|
212
|
-
<p>Credentials for ${
|
|
213
|
-
${d()}`)}function
|
|
214
|
+
<p>Credentials for ${r(p(e))} are removed. Calls to the API are anonymous again until you add new credentials.</p>
|
|
215
|
+
${d()}`)}function z(e,t){const o=t.saved.length===0?"Credentials removed":t.removed.length===0?"Credentials saved":"Credentials updated",n=[t.saved.length>0?`<p>Saved: ${h(t.saved)}.</p>`:"",t.removed.length>0?`<p>Removed: ${h(t.removed)}.</p>`:"",t.stored.length>0?`<p>${r(e.name)} now has stored credentials for ${h(t.stored)}.</p>`:`<p>${r(e.name)} has no stored credentials. Calls to the API are anonymous until you add new ones.</p>`];return s(`
|
|
216
|
+
${l(o)}
|
|
217
|
+
${n.join(`
|
|
218
|
+
`)}
|
|
219
|
+
<p>Retry in your AI client, or confirm any prompt it left open. You can close this tab.</p>
|
|
220
|
+
<p class="hint">This link is single-use and is now spent. To change anything, request a new one in your AI client.</p>
|
|
221
|
+
${d()}`)}function h(e){return e.map(t=>`<code>${r(t)}</code>`).join(", ")}function L(e){return s(`
|
|
214
222
|
${l("Credentials saved")}
|
|
215
|
-
<p>Credentials for ${
|
|
223
|
+
<p>Credentials for ${r(p(e))} are saved. Retry in your AI client, or confirm any prompt it left open. You can close this tab.</p>
|
|
216
224
|
<p class="hint">This link is single-use and is now spent. To change anything, request a new one in your AI client.</p>
|
|
217
|
-
${d()}`)}function
|
|
225
|
+
${d()}`)}function T(){return s(`${l("Link expired")}<p>This credentials link is invalid, already used, or older than ${Math.round(m/6e4)} minutes. Retry in your AI client to get a fresh link.</p>${d()}`)}export{S as clearedPage,M as formPage,z as multiOutcomePage,T as notFoundPage,L as successPage};
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import type { LifecyclePluginInstance } from '../../types';
|
|
2
|
-
export declare const MCP_DOCS_SERVER_HANDLER_ID = "mcp-docs-server-handler";
|
|
3
|
-
export declare const MCP_CREDENTIALS_HANDLER_ID = "mcp-credentials-handler";
|
|
4
2
|
export declare function mcpServerPlugin(): Promise<LifecyclePluginInstance>;
|
|
5
3
|
export default mcpServerPlugin;
|
|
6
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{fileURLToPath as
|
|
1
|
+
import{fileURLToPath as N}from"node:url";import{dirname as G,join as D}from"node:path";import{MCP_ROUTE as j,PUBLIC_MCP_ROUTE as M}from"@redocly/theme/core/constants/mcp";import{logger as a}from"../../tools/notifiers/logger.js";import{reporter as U}from"../../tools/notifiers/reporter.js";import{isDefined as F}from"../../../utils/guards/is-defined.js";import{canAnonymousAccessMcpFeature as R}from"../../utils/rbac/can-anonymous-access-mcp-feature.js";import{shouldHandleMcpAuth as v}from"../../utils/rbac/should-handle-mcp-auth.js";import{MCP_CREDENTIALS_HANDLER_ID as T,MCP_CREDENTIALS_ROUTE as $,MCP_DOCS_SERVER_HANDLER_ID as C,MCP_NOT_ALLOWED_TEMPLATE_ID as k}from"../../constants/mcp.js";import{bundledOpenApiDocumentsToMcpOpenApi as x,bundledGraphqlDocumentsToMcpOpenApi as B,buildGraphqlDescriptions as V,collectIgnoredRoutes as J,filterIgnoredApiDescriptions as _,getCleanedUpApiDescriptions as W,isMcpInRedirects as X}from"./utils.js";import{getMcpMode as z,isMcpElicitationEnabled as K}from"./utils/mode.js";import{findGatedToolNames as Q}from"./utils/find-gated-tool-names.js";import{getDefaultToolSchemas as Y}from"./docs-mcp/tool-schemas.js";import{EXECUTE_TOOL_SCHEMA as Z}from"./codemode/tools/execute-schema.js";import{isGatewayCapabilityEnabled as ee,isGatewayEligible as te}from"./gateway-mcp/eligibility.js";import{resolveGatewayFetchLimits as oe}from"./gateway-mcp/rate-limit/limits.js";import{telemetryTraceStep as ie}from"../../../cli/telemetry/helpers/trace-step.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as S}from"./constants.js";const E=G(N(import.meta.url));async function re(){let o=!1;return{id:"mcp",async processContent(e,i){o||await ie("com.redocly.build.plugin.mcp",async n=>{const r=x(await e.loadApiDocuments(i)),d=B(await e.loadApiDocuments(i)),c=await i.getConfig(),{mcp:s,redirects:f}=c;if(n?.setAttribute("config",JSON.stringify(s||{})),!(s?.hide||s?.docs?.hide)&&(X(f)&&await U.panicOnBuildContentError('The reserved route "/mcp" cannot be used in redirect configuration. Please remove any redirects involving "/mcp" from your redocly.yaml config.'),e.createTemplate(k,"@redocly/theme/layouts/McpNotAllowed"),!s?.docs?.hide)){a.info("Configuring MCP servers...");const g=D(E,"./handlers/docs-mcp-handler.js"),l=s?.docs?.ignore||[],p=W(_(r,l)),b=!!c?.access?.requiresLogin,m=c?.access?.rbac||{},u=ee(s),P=new Map;for(const t of p){const I=t.definition.info?.title||"",H=t.definition.info?.version||"";P.set(t.relativePath,{name:I,description:typeof t.definition.info?.description=="string"?t.definition.info.description:"",version:H,servers:t.definition.servers||[],relativePath:t.relativePath,...u&&te(t.definition.info,t.relativePath,m)?{fetchEligible:!0}:{}})}const h=V(_(d,l)),O=Object.values(c.products||{}).map(t=>t?.name).filter(F),L=c.l10n?.locales?.map(t=>t.code)??i.fs.localeFolders,y=z(),A=K(),{budgetPerExec:q}=oe(),w=Y({products:O,locales:L,requiresLogin:b,rbac:m,hasGraphqlApis:h.length>0,elicitationEnabled:A});e.addMcpTools(D(E,"./docs-mcp/tools/index.js"),w),y==="code"&&e.addMcpTools(D(E,"./codemode/tools/execute.js"),[Z]),await se({actions:e,handlerPath:g,apiDescriptionsMap:Object.fromEntries(P),graphqlDescriptions:h,mcpConfig:s,mcpMode:y,elicitationEnabled:A,gatewayFetchBudgetPerExec:q,requiresLogin:b,rbacConfig:m}),o=!0}})},async afterRoutesCreated(e,i){const n=await i.getConfig(),{mcp:r}=n,d=!r?.hide&&!r?.docs?.hide;e.markGatedMcpTools(await Q(e.getMcpTools())),e.setGlobalData({mcpData:{docs:{enabled:d,name:r?.docs?.name||S,publicEndpoint:d&&ne(r,n)}}})}}}var Re=re;function ne(o,e){const i=!!e.access?.requiresLogin,n=e.access?.rbac||{};return!!o?.docs?.publicEndpoint&&v(i,n)&&R(n,i)}async function se({actions:o,handlerPath:e,apiDescriptionsMap:i,graphqlDescriptions:n,mcpConfig:r,mcpMode:d,elicitationEnabled:c,gatewayFetchBudgetPerExec:s,requiresLogin:f,rbacConfig:g}){o.createRequestHandler(C,e);const l=p=>async(b,m)=>{const u=J(m.getAllRoutes(),r?.docs?.ignore||[]);return u.length>0&&a.verbose(`mcp.docs.ignore: hiding ${u.length} page routes from MCP search`),{props:{config:{apiDescriptionsMap:i,graphqlDescriptions:n,mcpDocsServerName:r?.docs?.name||S,mcpMode:d,elicitationEnabled:c,gatewayFetchBudgetPerExec:s,searchIgnoredRoutes:u,...p?{isPublicEndpoint:p}:{}},serverOutDir:o.serverOutDir,tools:o.getMcpTools().map(({importPath:P,...h})=>h)}}};if(o.addApiRoute({slug:j,requestHandlerId:C,httpMethod:"all",getStaticData:l(!1)}),a.info("Registered Docs MCP Server endpoint at /mcp"),r?.docs?.publicEndpoint&&(v(f,g)?R(g,f)?(o.addApiRoute({slug:M,requestHandlerId:C,httpMethod:"all",getStaticData:l(!0)}),a.info(`Registered public Docs MCP Server endpoint at ${M}`)):a.info(`mcp.docs.publicEndpoint is enabled, but anonymous users are denied the MCP feature (rbac.features.mcp or requiresLogin) - skipping ${M}.`):a.info(`mcp.docs.publicEndpoint is enabled, but the project has no protected content - /mcp already serves anonymous users, skipping ${M}.`)),c){const p=D(E,"./handlers/mcp-credentials-handler.js");o.createRequestHandler(T,p),o.addApiRoute({slug:$,requestHandlerId:T,httpMethod:"all",getStaticData:async()=>({props:{}})}),a.info("Registered MCP credentials endpoint at /mcp/credentials")}}export{Re as default,re as mcpServerPlugin};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{logger as S}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as v,MCP_TOOL_IS_AVAILABLE_KEY as k}from"../../../workers/mcp-tool-worker-pool.js";import{CREATE_ANONYMOUS_SESSION_TOOL_NAME as L}from"../constants.js";import{isAnonymousSessionToolAvailable as C}from"../docs-mcp/tools/core/create-anonymous-session.js";import{isManageApiCredentialsToolAvailable as y,manageApiCredentials as D,manageApiCredentialsSchema as N}from"../codemode/tools/manage-credentials.js";import{EXECUTE_TOOL_SCHEMA as A}from"../codemode/tools/execute-schema.js";import{EXECUTE_TOOL_NAME as w,CODE_MODE_TOOL_NAMES as H,FETCH_TOOL_NAME as x}from"../codemode/constants.js";import{buildCodeModeInstructions as I,buildExecuteDescription as U}from"../codemode/prompts.js";import{toToolListEntries as F}from"../codemode/build-tool-list.js";import{composeExecuteAnnotations as P,getActiveCapabilities as R}from"../codemode/capabilities/index.js";import{describeToolsSchema as z,describeTools as W}from"../codemode/tools/describe-tools.js";import{toRegisterToolConfig as _}from"../utils/tool-registration.js";import{connectMcpServer as X,createMcpServer as Y,createToolDispatch as $,extractSerializableContext as B}from"./mcp-server.js";function g(c,r){const o=r.schema;if(C(c))return o;const{sessionHandle:f,...l}=o.properties;return{...o,properties:l}}async function se({name:c,tools:r,context:o,skills:f=[],getKv:l}){const s=o.mcpMode==="code",a=s?R(o):[],T=a.some(e=>e.id===x),m=s?r.filter(e=>!H.has(e.name)):[],E=F(m,a),O=s&&y(o),{server:n,transport:h}=Y(c,s?I(T,E):void 0),p=B(o),b={getClientCapabilities:()=>n.server.getClientCapabilities(),getKv:l},d=$(p,b),u=async e=>{try{return await v.exec(k,[{toolName:e,context:p}],{timeout:1e4})}catch(i){return S.error(`Failed to check MCP tool availability for "${e}": ${i instanceof Error?i.message:String(i)}`),!1}};if(s){const e=[{tool:{...A,description:U(T,E),schema:g(o,A),annotations:P(a)},callback:d(w)},{tool:z(m,a),callback:W(m,u,a,o)}],i=r.find(t=>t.name===L);if(i&&C(o)&&e.push({tool:i,callback:d(i.name)}),O){const t=N(o);e.push({tool:{...t,schema:g(o,t)},callback:D(p,b)})}for(const{tool:t,callback:M}of e)n.registerTool(t.name,_(t),M)}else await Promise.all(r.map(async e=>{const i=n.registerTool(e.name,_(e),d(e.name));await u(e.name)||i.disable()}));for(const e of f)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 X(n,h)}export{se as createDocsMcpServer};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{McpServer as L,WebStandardStreamableHTTPServerTransport as j}from"@modelcontextprotocol/server";import{logger as S}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as D,MCP_TOOL_WORKER_KEY as K}from"../../../workers/mcp-tool-worker-pool.js";import{SSE_KEEPALIVE_INTERVAL_MS as x}from"../constants.js";import{secretStore as F}from"../gateway-mcp/secret-store.js";import{readAuthRequiredMeta as H,stripAuthRequiredMeta as M,upstreamAuthKey as b}from"../gateway-mcp/upstream-auth.js";import{buildCredentialsToolHint as P,buildSchemeChoiceHint as v,buildUpstreamCredentialFallback as T,buildUpstreamCredentialInputRequired as $,isCredentialElicitationDeclined as N,schemeLabelOf as B}from"../gateway-mcp/elicitation.js";import{DEFAULT_SCHEME_NAME as q,resolveUpstreamApiAuth as G,schemeChoicesOf as W}from"../gateway-mcp/upstream-auth-scheme.js";import{MISSING_SESSION_HANDLE_MESSAGE as V,resolveSecretSubject as Y}from"../gateway-mcp/subject.js";const J=6e4,E=new Map;function Q(e,t){const
|
|
1
|
+
import{McpServer as L,WebStandardStreamableHTTPServerTransport as j}from"@modelcontextprotocol/server";import{logger as S}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as D,MCP_TOOL_WORKER_KEY as K}from"../../../workers/mcp-tool-worker-pool.js";import{SSE_KEEPALIVE_INTERVAL_MS as x}from"../constants.js";import{secretStore as F}from"../gateway-mcp/secret-store.js";import{readAuthRequiredMeta as H,stripAuthRequiredMeta as M,upstreamAuthKey as b}from"../gateway-mcp/upstream-auth.js";import{buildCredentialsToolHint as P,buildSchemeChoiceHint as v,buildUpstreamCredentialFallback as T,buildUpstreamCredentialInputRequired as $,isCredentialElicitationDeclined as N,schemeLabelOf as B}from"../gateway-mcp/elicitation.js";import{DEFAULT_SCHEME_NAME as q,resolveUpstreamApiAuth as G,schemeChoicesOf as W}from"../gateway-mcp/upstream-auth-scheme.js";import{MISSING_SESSION_HANDLE_MESSAGE as V,resolveSecretSubject as Y}from"../gateway-mcp/subject.js";const J=6e4,E=new Map;function Q(e,t){const n=E.get(e);if(n)return n;const r=t().finally(()=>{E.delete(e)});return E.set(e,r),r}function fe(e,t){const n=new L({name:e,version:new Date().toISOString().slice(0,10)},{capabilities:{logging:{}},...t?{instructions:t}:{}}),r=new j({keepAliveMs:x,sessionIdGenerator:void 0});return{server:n,transport:r}}async function he(e,t){return await e.connect(t),{server:e,transport:t,cleanup:async()=>{t.close()}}}function ge(e,t){return n=>async(r,s)=>{S.info(`MCP tool called: ${n}`);const i=e.elicitationEnabled??!1,c=s.mcpReq.envelope!==void 0,o=i?Y(e.user,r.sessionHandle):{kind:"anonymous"},a=o.kind==="resolved"?o.subject:void 0;if(o.kind==="invalid")return{content:[{type:"text",text:o.message}],isError:!0};const p=async()=>{if(!(!i||!a||!t?.getKv))try{const m=await t.getKv();return await F.getSecretsForSubject(m,a)}catch(m){S.warn(`gateway mcp secret store error, continuing unauthenticated: ${String(m)}`);return}};let u;const l=async()=>{u=await p();const m={toolName:n,args:r,context:{...e,upstreamSecrets:u},extra:ie(s)};return await D.exec(K,[m],{timeout:J})};let d=await l();const f=H(d);if(f.length>0&&i&&!a)return M({...d,content:[{type:"text",text:V}],isError:!0});if(f.length>0&&i&&a&&t?.getKv){S.info(`gateway mcp credentials prompt: era=${c?"2026-07-28":"2025"} clientElicitation=${Z(t)}`);const m=await t.getKv(),h=await z(e,f),k=f.map(U=>ee(U,h)),y=te(u),g=c?await ne(e,s,k,a,m,y,h,t):await re(e,k,a,m,y,h);switch(g.kind){case"inputRequired":return g.result;case"retry":d=await l();break;case"prependText":d={...d,content:[{type:"text",text:g.text},...d.content],isError:!0};break;case"none":break}}return M(d)}}function X(e){return!!e?.getClientCapabilities()?.elicitation?.url}function Z(e){const t=e?.getClientCapabilities()?.elicitation;if(!t)return"none";const n=[t.form?"form":void 0,t.url?"url":void 0].filter(Boolean);return n.length>0?n.join("+"):"none"}async function z(e,t){const n=new Map;for(const r of new Set(t.map(s=>s.api)))n.set(r,await G(e,r));return n}function ee(e,t){const n=t.get(e.api);if(!n)return{...e,scheme:e.scheme??q};const r=e.scheme&&n.schemes[e.scheme]?e.scheme:n.defaultScheme;return{...e,scheme:r}}function te(e){const t=new Set;for(const[n,r]of Object.entries(e??{}))for(const s of Object.keys(r))t.add(b({api:n,scheme:s}));return t}function A(e,t){return e.status===401&&!t.has(b(e))}function I(e,t){const n=t.get(e.api),r=e.sentScheme??e.scheme;return n&&r?B(n.schemes,r):void 0}function R(e,t){const n=t.get(e.api);return!n||!e.schemes?.length?[]:W(n,e.schemes)}function _(e,t){return R(e,t).length>1}function w(e,t){return e.map(n=>P(n,I(n,t))).join(`
|
|
2
2
|
|
|
3
|
-
`)}function C(e,t,
|
|
3
|
+
`)}function C(e,t,n){const r=e.map(s=>v(s,R(s,n)));return t.length>0&&r.push(w(t,n)),r.join(`
|
|
4
4
|
|
|
5
|
-
`)}function O(e,t,r,
|
|
5
|
+
`)}function O(e,t,n,r,s){const[i,...c]=t,o=s.get(i.api),a=i.scheme??o?.defaultScheme??q,p=I(i,s);return{subject:n,baseUrl:e.baseUrl??"",target:{api:i.api,name:i.name,scheme:a,...p?{schemeLabel:p}:{}},auth:o?.schemes[a]??{kind:"raw"},...o?{apiAuth:o}:{},kv:r,hasExistingSecret:!1,reason:{kind:"authFailure",status:i.status,...i.detail?{detail:i.detail}:{},otherApiNames:c.map(u=>u.name)}}}async function ne(e,t,n,r,s,i,c,o){if(N(t.mcpReq.inputResponses))return{kind:"none"};const a=n.filter(l=>A(l,i));if(a.length===0)return{kind:"prependText",text:w(n,c)};const p=a.filter(l=>_(l,c));if(p.length>0){const l=n.filter(d=>!p.includes(d));return{kind:"prependText",text:C(p,l,c)}}const u=O(e,a,r,s,c);return X(o)?{kind:"inputRequired",result:await $(u)}:{kind:"prependText",text:await T(u)}}async function re(e,t,n,r,s,i){const c=t.filter(u=>A(u,s));if(c.length===0)return{kind:"prependText",text:w(t,i)};const o=c.filter(u=>_(u,i));if(o.length>0){const u=t.filter(l=>!o.includes(l));return{kind:"prependText",text:C(o,u,i)}}const a=O(e,c,n,r,i);return{kind:"prependText",text:await Q(`${n}:${b(c[0])}`,()=>T(a))}}function ie(e){return{sessionId:e.sessionId,authInfo:e.http?.authInfo,requestId:e.mcpReq.id,_meta:e.mcpReq._meta}}function Se(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,searchIgnoredRoutes:e.searchIgnoredRoutes}}export{he as connectMcpServer,fe as createMcpServer,ge as createToolDispatch,Se as extractSerializableContext};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { McpToolSchema } from '../../../types';
|
|
2
|
+
import type { ToolArgs } from '../types.js';
|
|
3
|
+
export declare function normalizeToolDisplayName(tool: McpToolSchema): McpToolSchema;
|
|
4
|
+
/** The `registerTool` config for a tool schema, with its input schema compiled for validation. */
|
|
5
|
+
export declare function toRegisterToolConfig(tool: McpToolSchema): {
|
|
6
|
+
title: string | undefined;
|
|
7
|
+
description: string;
|
|
8
|
+
inputSchema: import("@modelcontextprotocol/server").StandardSchemaWithJSON<ToolArgs, ToolArgs>;
|
|
9
|
+
annotations: {
|
|
10
|
+
title?: string | undefined;
|
|
11
|
+
readOnlyHint?: boolean | undefined;
|
|
12
|
+
destructiveHint?: boolean | undefined;
|
|
13
|
+
idempotentHint?: boolean | undefined;
|
|
14
|
+
openWorldHint?: boolean | undefined;
|
|
15
|
+
} | undefined;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=tool-registration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{fromJsonSchema as a}from"@modelcontextprotocol/server";import{mcpJsonSchemaValidator as r}from"./json-schema-validator.js";function m(t){const n=t.title??t.annotations?.title;return n?{...t,title:n,annotations:{...t.annotations,title:n}}:t}function p(t){const{title:n,description:o,schema:i,annotations:e}=m(t);return{title:n,description:o,inputSchema:a(i,r),annotations:e}}export{m as normalizeToolDisplayName,p as toRegisterToolConfig};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{REDOCLY_TEAMS_RBAC as x}from"@redocly/config";import{basename as C,join as
|
|
2
|
-
`;for(const[S,b]of y?.publiclyAccessibleTaggedSearchDocuments?.entries()||[]){if(!b.length)continue;const k=s.definition.tags?.find(L=>L.name===S);d+=E({title:k?.["x-displayName"]||S,description:k?.description,operationSearchDocuments:b,headingLevel:`${h}#`})}const g=y?.publiclyAccessibleUntaggedSearchDocuments||[];g.length&&(d+=E({title:"Other",description:void 0,operationSearchDocuments:g,headingLevel:`${h}#`})),$=await N({parser:s,info:n,staticData:p,relativePath:t,headingLevel:h,pageName:U,config:
|
|
1
|
+
import{REDOCLY_TEAMS_RBAC as x}from"@redocly/config";import{basename as C,join as q}from"node:path";import _ from"@markdoc/markdoc";import{joinSectionContent as G}from"../../../plugins/markdown/search/join-section-content.js";import{toMarkdown as J}from"../../../plugins/markdown/search/to-markdown.js";import{AstToSearchNodeTransformer as B}from"../../../plugins/markdown/search/walk-sections.js";import{createRenderTagFn as z}from"../../../plugins/markdown/search/create-render-tag-fn.js";import{getBuildTranslateContext as v}from"../../../utils/l10n/get-build-translate-context.js";import{canDownloadApiDefinition as Y,isResourcePubliclyAccessible as H}from"../../../utils/rbac.js";import{PUBLIC_API_DEFINITIONS_FOLDER as V}from"../../../constants/common.js";import{ANONYMOUS_AUTH_DETAILS as K}from"../../../constants/rbac.js";import{getLlmsTxtMdPathBySlug as Q}from"../../../utils/llmstxt/get-llms-txt-md-path-by-slug.js";import{AiSearchIndexer as W}from"../ai-search-indexer.js";import{getLocaleFromRelativePath as X}from"../../../fs/utils/get-locale-from-relative-path.js";import{extractDocumentSearchFacets as Z}from"./search-facets.js";import{formatDocumentMetadata as I}from"../../search/utils.js";import{llmsTxtLink as M}from"../../search/llmstxt/index.js";import{replaceFileExtension as ee}from"../store-definition-bundles.js";const j=new Map,Ee=({parser:s,options:c,info:n,tagOperations:r,relativePath:t,openapiContentItem:e,metadata:i,getSearchFacets:u,includeInLLMsTxt:a,excludeFromSearch:f})=>async(o,p,l,O)=>{if(f)return;const U=await o.getNavText?.()||C(o.fsPath),w=new W(s,c,o.baseSlug||o.slug),m=w.addItem(e);if(!m)return;const T=await l.getConfig(),D=Array.isArray(m.title)?m.title.join(" "):m.title;let $,d,y=j.get(t);if((e.type==="tag"||e.type==="section"&&e.infoDefinition)&&!y){const{all:A,publiclyAccessible:g}=ue(r.tagged,w,T),{all:S,publiclyAccessible:b}=le(r.untagged,w,T);j.set(t,{taggedSearchDocuments:new Map(A),untaggedSearchDocuments:S,publiclyAccessibleTaggedSearchDocuments:new Map(g),publiclyAccessibleUntaggedSearchDocuments:b})}y=j.get(t);const h="#";switch(e.type){case"operation":const A=w.getOperation(e);$=d=ce({title:D,security:s.definition.security,document:m,version:m.version||n.version,headingLevel:h,operation:A});break;case"section":if(e.infoDefinition){y=j.get(t),d=await N({parser:s,info:n,staticData:p,relativePath:t,headingLevel:h,pageName:U,config:T}),d+=`
|
|
2
|
+
`;for(const[S,b]of y?.publiclyAccessibleTaggedSearchDocuments?.entries()||[]){if(!b.length)continue;const k=s.definition.tags?.find(L=>L.name===S);d+=E({title:k?.["x-displayName"]||S,description:k?.description,operationSearchDocuments:b,headingLevel:`${h}#`})}const g=y?.publiclyAccessibleUntaggedSearchDocuments||[];g.length&&(d+=E({title:"Other",description:void 0,operationSearchDocuments:g,headingLevel:`${h}#`})),$=await N({parser:s,info:n,staticData:p,relativePath:t,headingLevel:h,pageName:U,config:T})}else if(e.ast){const g=new _.Ast.Node("document",{},e.ast),{locale:S,translate:b}=await v(o.slug,O.getGlobalData?.().l10n),k=Array.from(new B({ast:g,partials:{},renderTag:z(O.markdocOptions.tags,S,b),getInnerContent:J,skipConditionals:!0}).transform());d=$=G(k)}break;case"tag":d=E({title:D,description:e.description,operationSearchDocuments:y?.publiclyAccessibleTaggedSearchDocuments.get(e.name)||[],headingLevel:h}),$=E({title:D,description:e.description,operationSearchDocuments:[],headingLevel:h});break;case"rsrc":case"prompt":case"tool":d=re({title:D,description:e.description,name:e.name,xMcpConfig:s.definition["x-mcp"],headingLevel:h}),$=d;break}return{async getLLMsTxts(){return[{title:e.name,description:e.type==="tag"?e.description:void 0,content:d||"",slug:o.slug,fsPath:o.fsPath,includeInLLMsTxt:a}]},async getSearchDocuments(){if(e.type==="operation"||e.type==="section"&&(e.infoDefinition||e.ast)||e.type==="tag"){const A=X(o.fsPath),g=Z({...m,...i},n,u);return[{title:D,description:Array.isArray(m.text)?m.text.join(" "):m.text,content:$||"",url:m.url??o.slug,fsPath:o.fsPath,locale:A,product:o.product?.name,rbacTeams:m.rbacTeams,facets:g}]}return[]}}};async function N({parser:s,info:c,staticData:n,relativePath:r,pageName:t,headingLevel:e,config:i}){const u=I(c["x-metadata"]);let a=c.title?`${e} ${c.title}
|
|
3
3
|
|
|
4
4
|
`:`${e} ${t}
|
|
5
5
|
|
|
@@ -18,7 +18,7 @@ ${u}
|
|
|
18
18
|
|
|
19
19
|
`),n.length&&n.forEach(e=>{const i=Array.isArray(e.title)?e.title.join(" "):e.title;t+=`${r}# ${i}${e.deprecated?" (deprecated)":""}
|
|
20
20
|
|
|
21
|
-
`,t+=` - ${M({title:`${e.httpMethod?.toUpperCase()} ${e.httpPath}`,description:Array.isArray(e.text)?e.text.join(" "):e.text,slug:
|
|
21
|
+
`,t+=` - ${M({title:`${e.httpMethod?.toUpperCase()} ${e.httpPath}`,description:Array.isArray(e.text)?e.text.join(" "):e.text,slug:Q(e.url)})}`,t+=`
|
|
22
22
|
`}),t}function ne({parser:s,headingLevel:c}){const{servers:n}=s.definition;if(n&&n.length){let r=`${c} Servers
|
|
23
23
|
|
|
24
24
|
`;return n.forEach(t=>{r+=t.description?`${t.description}
|
|
@@ -31,7 +31,7 @@ ${t.url}
|
|
|
31
31
|
`,r+=i.default?`Default: ${JSON.stringify(i.default)}
|
|
32
32
|
`:"",r+=i.enum?`Enum: ${i.enum.map(u=>JSON.stringify(u)).join(", ")}
|
|
33
33
|
`:""}),r+=`
|
|
34
|
-
`)}),r}return""}async function te({info:s,staticData:c,relativePath:n,pageName:r,headingLevel:t,config:e}){const i=
|
|
34
|
+
`)}),r}return""}async function te({info:s,staticData:c,relativePath:n,pageName:r,headingLevel:t,config:e}){const i=q(c.props?.outdir||"",V,ee(n,".yaml")),u=e.access?.rbac,a=e.access?.requiresLogin;if(Y(i,u??{},a??!1,K)){let f=`${t} Download OpenAPI description
|
|
35
35
|
|
|
36
36
|
`;return f+=M({title:s.title||r||"OpenAPI definition",description:void 0,slug:i}),f}return""}function se({parser:s,headingLevel:c}){if(!s.definition.components?.securitySchemes)return"";let n=`${c} Security
|
|
37
37
|
|
|
@@ -91,4 +91,4 @@ ${JSON.stringify(t.outputSchema,null,2)}
|
|
|
91
91
|
|
|
92
92
|
${u.join(`
|
|
93
93
|
`)}`}).join(`
|
|
94
|
-
`)}function ue(s,c,n){const r=[],t=[];return s.forEach((e,i)=>{const u=[],a=[];e.forEach(f=>{const o=c.addItem(f);o&&(u.push(o),F({[x]:f[x],slug:o.url},n)&&a.push(o))}),r.push([i,u]),t.push([i,a])}),{all:r,publiclyAccessible:t}}function le(s,c,n){const r=[],t=[];return s.forEach(e=>{const i=c.addItem(e);i&&(r.push(i),F({[x]:e[x],slug:i.url},n)&&t.push(i))}),{all:r,publiclyAccessible:t}}function R(s){return s.charAt(0).toUpperCase()+s.slice(1)}function F(s,c){return c.access?.requiresLogin?!0:
|
|
94
|
+
`)}function ue(s,c,n){const r=[],t=[];return s.forEach((e,i)=>{const u=[],a=[];e.forEach(f=>{const o=c.addItem(f);o&&(u.push(o),F({[x]:f[x],slug:o.url},n)&&a.push(o))}),r.push([i,u]),t.push([i,a])}),{all:r,publiclyAccessible:t}}function le(s,c,n){const r=[],t=[];return s.forEach(e=>{const i=c.addItem(e);i&&(r.push(i),F({[x]:e[x],slug:i.url},n)&&t.push(i))}),{all:r,publiclyAccessible:t}}function R(s){return s.charAt(0).toUpperCase()+s.slice(1)}function F(s,c){return c.access?.requiresLogin?!0:H(s,c)}export{Ee as getAiDocumentsStore};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{appendFile as D}from"node:fs/promises";import{existsSync as
|
|
1
|
+
import{appendFile as D}from"node:fs/promises";import{existsSync as v}from"node:fs";import b from"node:path";import{Client as N}from"typesense";import{SEARCH_GROUP_FACET_FIELD as A,SEARCH_PRODUCT_FIELD as q,SEARCH_RBAC_FIELD as T,SEARCH_VERSION_FIELD as h}from"../../../../../constants/common.js";import{DISABLE_DEEP_LINK_IF_FIELDS_EXIST as L,HIGHLIGHTED_TEXT_MAX_LENGTH as R,SEARCH_DATA_EXPORT_FOLDER as C,SEARCH_DOCUMENT_METADATA_KEY as j,SEARCH_GROUP_LIMIT as O,SEARCH_MAX_FACET_VALUES as y,SEARCH_MAX_INMEMORY_DOCUMENTS_COUNT as k}from"../../../../constants/plugins/search.js";import{telemetryTraceStep as P}from"../../../../telemetry/helpers/trace-step.js";import{envConfig as _}from"../../../../config/env-config.js";import{logger as F}from"../../../../tools/notifiers/logger.js";import{ensureDir as E}from"../../../../utils/index.js";class X{#e=null;#o="";#t=[{name:"title",type:"string",facet:!1,optional:!0},{name:"text",type:"string",facet:!1,optional:!0},{name:"path",type:"string[]",facet:!1,optional:!0},{name:"isAdditionalOperation",type:"bool",facet:!1,optional:!0,queryable:!1},{name:"deprecated",type:"bool",facet:!1,optional:!0,queryable:!1},{name:"parameters",type:"object[]",facet:!1,optional:!0},{name:"parameters.name",type:"string[]",facet:!1,optional:!0,queryable:!1},{name:"parameters.description",type:"string[]",facet:!1,optional:!0,queryable:!1},{name:"parameters.place",type:"string[]",facet:!1,optional:!0,queryable:!1},{name:"parameters.path",type:"string[]",facet:!1,optional:!0,queryable:!1},{name:q,type:"string",facet:!0,optional:!0},{name:h,type:"object",facet:!0,optional:!0},{name:T,type:"string[]",facet:!0},{name:"metadata_curated",type:"bool",facet:!1,optional:!0,queryable:!1},{name:"metadata_keywords_excludes",type:"string[]",facet:!1,optional:!0,queryable:!1},{name:"metadata_keywords_includes",type:"string[]",facet:!1,optional:!0,queryable:!1}];#s=new Map;#r={ORGANIZATION_ID:_.ORGANIZATION_ID??"",PROJECT_ID:_.PROJECT_ID??"",TYPESENSE_API_URL:_.TYPESENSE_API_URL??"",TYPESENSE_API_KEY:_.TYPESENSE_API_KEY??""};constructor(){if(!_.isBuildMode){for(const[e,t]of Object.entries(this.#r))if(!t)throw new Error(`Cannot initialize search index. ${e} is not set`);this.#o=`${this.#r.ORGANIZATION_ID}_${this.#r.PROJECT_ID}_`,this.#e=new N({nodes:[{url:this.#r.TYPESENSE_API_URL}],apiKey:this.#r.TYPESENSE_API_KEY,connectionTimeoutSeconds:5})}}async initIndexSchema(e){this.#_(e)}async addDocuments(e,t){const{locale:s,outDir:a}=t,r=e.map(n=>{const o={...n},c=n[j];return c&&(c.curated&&(o.metadata_curated=c.curated),c.excludes&&(o.metadata_keywords_excludes=c.excludes),c.includes&&(o.metadata_keywords_includes=c.includes)),o}),i=this.#s.get(s)??[];this.#s.set(s,i.concat(r)),i.length+e.length>=k&&await this.exportDocuments(a)}async search(e,t){return await P("com.redocly.search.performed",async s=>{s?.setAttribute("id","searchQueryId"),s?.setAttribute("object","searchQuery"),s?.setAttribute("uri","urn:redocly:realm:api:searchQuery:searchQueryId");const{query:a,locale:r,filter:i,loadMore:n}=e,o=this.#u(r),c={collection:o,q:a||"*",query_by:this.#f(),max_facet_values:y};if(s?.setAttribute("collectionName",o),s?.setAttribute("locale",r),this.#e)if(n){const{groupKey:l,offset:u}=n,f={offset:u,filter_by:this.#a(t,i),facet_by:"*",per_page:O};return s?.setAttribute("isLoadMore",!0),s?.setAttribute("filter",f.filter_by),this.#p(await this.#e.collections(o).documents().search({...c,...f}),l)}else{const l=this.#a(t,i);s?.setAttribute("filter",l);const u=await this.#e.multiSearch.perform({searches:this.#b(t,i)},c),f=this.#i(u,this.#t.filter(p=>p.facet).map(p=>p.name));f&&F.warn("Search facet requests failed: %s",f);const m=this.#m(u),d=(m[A]??[]).map(p=>p.value);s?.setAttribute("groups",d.join(",")),d.length===y&&F.warn("Search category facet list reached the limit of %s; results for some categories may be missing",y);const I=[...new Set(this.#h(i))].slice(0,y),g=I.length?I:d;if(!g.length)return{facets:m,documents:{},groupCounts:{}};const $=await this.#e.multiSearch.perform({searches:g.map(p=>({filter_by:`${A}:=${this.#n(p)} && ${l}`,per_page:O}))},c),S=this.#i($,g);return S&&F.warn("Search category requests failed: %s",S),this.#d($,g,m,i)}else return{facets:{},documents:{}}})}#p(e,t){const s={facets:{},documents:{[t]:[]},groupCounts:typeof e.found=="number"?{[t]:e.found}:{}};if(e.hits&&e.hits.length)for(const a of e.hits)s.documents[t].push({document:this.#c(a),highlight:this.#l(a)});return s}#m(e){const t={};if("results"in e){for(const s of e.results)if(s.facet_counts&&s.facet_counts.length)for(const a of s.facet_counts)t[a.field_name]=a.counts.map(r=>({value:r.value,count:r.count}))}return t}#i(e,t){return"results"in e?e.results.map((s,a)=>({result:s,label:t[a]})).filter(({result:s})=>"error"in s).map(({result:s,label:a})=>`${a}: ${s.error}`).join("; "):""}#d(e,t,s,a){const r={},i={facets:s,documents:{},groupCounts:r};for(const n of this.#h(a))i.documents[n]=[];return"results"in e&&e.results.forEach((n,o)=>{const c=t[o];typeof n.found=="number"&&(r[c]=n.found);const l=n.hits??[];l.length&&(i.documents[c]=l.map(u=>({document:this.#c(u),highlight:this.#l(u)})))}),i}#c(e){return this.#y(e)}#l(e){const t={},s=[];for(const[a,r]of Object.entries(e.highlight??{}))if(Array.isArray(r)){if(a==="path"){const i=r,n=[];for(const o of i)n.push(o.snippet);t.path=n}else if(a==="parameters"){const i=r;for(const n of i){let o=!1;for(const[c,l]of Object.entries(n))if(c!=="deepLink")if(Array.isArray(l))for(const u of l)u.matched_tokens?.length&&(o=!0);else l.matched_tokens?.length&&(o=!0);if(o){const c={name:n.name?.snippet||"",description:n.description?.snippet||"",place:n.place?.snippet||"",path:n.path?.map(l=>l?.snippet)||[]};t.parameters=[c];break}}}}else t[a]=r.snippet,s.push(a);for(const[a,r]of Object.entries(e.document))!s.includes(a)&&typeof r=="string"&&(t[a]=r.length>R?`${r.substring(0,R)}...`:r);return t.parameters||(t.parameters=[]),t}#y(e){let t;const s=Object.keys(e.highlight??{});for(const r of L)if(s.some(i=>i===r))return e.document;const a=e.highlight?.parameters;if(a){for(const r of a)for(const[i,n]of Object.entries(r))if(i!=="deepLink"){if(Array.isArray(n)){for(const o of n)if(o.matched_tokens?.length){t=r.deepLink?.snippet??t;break}}else if(n.matched_tokens?.length){t=r.deepLink?.snippet??t;break}}}if(t){const r=t.split("#")[1];return{...e.document,url:`${e.document.url}#${r}`}}return e.document}async exportDocuments(e){const t='{"documents":[',s=E(b.join(e,C));for(const[a,r]of this.#s){if(r.length===0)continue;const i=E(b.join(s,`${a}.json`)),n=!v(i),o=JSON.stringify(r).substring(1).slice(0,-1),c=n?t+o:","+o;await D(i,c,{encoding:"utf8"}),this.#s.set(a,[])}}async exportIndexes(e){const t=E(b.join(e,C));for(const s of this.#s.keys()){const a=E(b.join(t,`${s}.json`)),r=`],"schemaFields":${JSON.stringify(this.#t)}}`;await D(a,r,{encoding:"utf8"})}}async import(e){}async countFacets(e,t){return await P("com.redocly.search.facets",async s=>{s?.setAttribute("id","searchFacetsId"),s?.setAttribute("object","searchFacets"),s?.setAttribute("uri","urn:redocly:realm:api:searchFacets:searchFacetsId");const{locale:a,query:r,facetQuery:i,filter:n,field:o}=e,c=this.#u(a),l={q:r||"*",query_by:this.#f(),facet_by:"*",facet_query:this.#g(i,o),filter_by:this.#a(t,n,o),max_facet_values:y};if(s?.setAttribute("collectionName",c),s?.setAttribute("query",l.q),s?.setAttribute("facetQuery",l.facet_query),s?.setAttribute("filter",l.filter_by),this.#e){const u={},f=await this.#e.collections(c).documents().search(l);if(f.facet_counts&&f.facet_counts.length)for(const m of f.facet_counts)u[m.field_name]=m.counts.map(d=>({value:d.value,count:d.count,isCounterVisible:!!r||(n?.length||0)>0}));return u}else return{}})}#u(e){return`${this.#o}${e}`}#_(e){for(const[t,{queryable:s}]of e)this.#t.find(a=>a.name===t)||this.#t.push({queryable:s,name:t,type:"string",facet:!0,optional:!0})}#f(){return this.#t.filter(e=>e.facet?e.queryable:e.queryable!==!1).map(e=>e.name).join(",")}#n(e){return`\`${e.replaceAll("`","")}\``}#a(e,t,s){const a=`${T}:[${e.teams.map(i=>`'${i}'`).join(", ")}]`;let r=`${h}.isDefault:true`;if(t&&t.length){const i=t.filter(o=>o.field!==s&&o.field!==h).map(o=>{const c=o.values.map(l=>this.#n(l));return c.length?`${o.field}:=[${c.join(", ")}]`:""}).join(" && "),n=t.find(o=>o.field===h);if(n&&n.values.length&&n.values.length===2){const o=n.values[0],c=n.values[1],l=`(${h}.folderId:! ${o} && ${h}.isDefault:true)`,u=`(${h}.folderId: ${o} && ${h}.version:=${this.#n(c)})`;r=`(${l} || ${u})`}return i?`${a} && ${i} && ${r}`:`${a} && ${r}`}else return`${a} && ${r}`}#g(e,t){return e&&t?`${t}:${e}`:""}#h(e){const t=[];if(e&&e.length)for(const s of e)s.field===A&&t.push(...s.values);return t}#b(e,t){const s=[],a=this.#t.filter(r=>r.facet);for(const r of a){const i={facet_by:r.name,filter_by:this.#a(e,t,r.name)};s.push(i)}return s}cleanupFacetValues(e){const t=e.getSearchFacets();t.forEach(s=>{s.values=[]}),e.setSearchFacets(t)}}export{X as Typesense};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{DEFAULT_SEARCH_ENGINE as s,REDOCLY_TEAMS_RBAC as
|
|
1
|
+
import{DEFAULT_SEARCH_ENGINE as s,REDOCLY_TEAMS_RBAC as p}from"@redocly/config";import{envConfig as o}from"../../config/env-config.js";import{logger as E}from"../../tools/notifiers/logger.js";import{reporter as u}from"../../tools/notifiers/reporter.js";import{prepareSearchDocuments as S}from"./documents/search-documents.js";import{SearchEngine as b}from"./engines/search-engine.js";import{prepareSemanticDocuments as D}from"./ai-indexer/prepare-semantic-documents.js";import{isAiSearchEnabled as y,shouldGenerateEmbeddings as A,shouldIndexSearchDocuments as C}from"./utils.js";import{telemetryTraceStep as R}from"../../../cli/telemetry/helpers/trace-step.js";import{EntitlementsProvider as _}from"../../entitlements/entitlements-provider.js";async function B(){let e=!0;return{async afterRoutesCreated(t,n){(o.SEARCH_DEV_REINIT||e)&&await w(t,n),e=!1},id:"search"}}async function w(e,t){await R("com.redocly.build.plugin.search",async n=>{const r=e.getConfig();if(n?.setAttribute("config",JSON.stringify(r.search||{})),r.search?.hide){E.info("Search is disabled in the config, skipping");return}const m=r.search?.engine||s,i=o.isDevelopMode?o.SEARCH_DEV_DEBUG?m:s:m,{l10n:l}=e.getGlobalData(),d=y(r),c=r.access?.rbac?.features?.aiSearch,h=A(r);let a;try{a=new b(i,l)}catch(g){await u.panic(g);return}await a.initIndexSchema(e.getSearchFacets()),e.setSearchEngine(a),e.setGlobalData({searchFeatures:{advanced:{enabled:i!==s&&!r.search?.filters?.hide},ai:{enabled:d,[p]:c&&Object.keys(c).length?c:void 0}}}),C(i)&&await S(t.fs,e,a);const f=_.instance().canAccessFeature("seo")===!0&&r.seo?.llmstxt?.hide!==!0;await D(e,t,{embeddingsEnabled:h,llmstxtEnabled:f})})}export{B as searchPlugin};
|