@redocly/realm 0.138.0-next.7 → 0.138.0-next.9

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 (41) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/client/App.js +1 -1
  3. package/dist/client/app/ReviewComments/ReviewComments.js +2 -2
  4. package/dist/client/app/l10n/hooks/useTranslationKeys.js +1 -1
  5. package/dist/client/app/markdoc/custom-components/openapi/openapi-example.js +1 -1
  6. package/dist/client/app/markdoc/custom-components/openapi/replay-openapi.js +1 -1
  7. package/dist/client/app/markdoc/hooks/use-store.d.ts +2 -3
  8. package/dist/client/app/search/useSearch.js +1 -1
  9. package/dist/client/runtime/loader.js +1 -1
  10. package/dist/constants/l10n/langs/ar.js +1 -1
  11. package/dist/constants/l10n/langs/de.js +1 -1
  12. package/dist/constants/l10n/langs/en.js +1 -1
  13. package/dist/constants/l10n/langs/es.js +1 -1
  14. package/dist/constants/l10n/langs/fr.js +1 -1
  15. package/dist/constants/l10n/langs/hi.js +1 -1
  16. package/dist/constants/l10n/langs/it.js +1 -1
  17. package/dist/constants/l10n/langs/ja.js +1 -1
  18. package/dist/constants/l10n/langs/ko.js +1 -1
  19. package/dist/constants/l10n/langs/pl.js +1 -1
  20. package/dist/constants/l10n/langs/pt-BR.js +1 -1
  21. package/dist/constants/l10n/langs/pt.js +1 -1
  22. package/dist/constants/l10n/langs/ru.js +1 -1
  23. package/dist/constants/l10n/langs/uk.js +1 -1
  24. package/dist/constants/l10n/langs/zh.js +1 -1
  25. package/dist/server/config/env-config.d.ts +0 -2
  26. package/dist/server/config/env-schema.d.ts +0 -5
  27. package/dist/server/config/env-schemas/feature-flags.d.ts +0 -4
  28. package/dist/server/config/env-schemas/feature-flags.js +1 -1
  29. package/dist/server/plugins/dev-onboarding/template/components/ApiKeyOverview.js +3 -3
  30. package/dist/server/plugins/markdown/search/get-ai-search-documents.js +1 -1
  31. package/dist/server/plugins/mcp/custom-tools/scan-custom-tools.js +1 -1
  32. package/dist/server/plugins/mcp/docs-mcp/tool-schemas.js +1 -1
  33. package/dist/server/plugins/mcp/docs-mcp/tools/core/whoami.js +1 -1
  34. package/dist/server/plugins/mcp/gateway-mcp/credential-validation.js +1 -1
  35. package/dist/server/plugins/mcp/gateway-mcp/upstream-auth-scheme.js +2 -2
  36. package/dist/server/plugins/mcp/handlers/mcp-credentials-handler.js +1 -1
  37. package/dist/server/plugins/scorecard-classic/template/views.js +1 -1
  38. package/dist/server/plugins/sidebars/tabs.js +1 -1
  39. package/dist/server/web-server/routes/cors-proxy.js +2 -2
  40. package/dist/server/web-server/routes/mcp-routes/mcp-oauth.js +1 -1
  41. package/package.json +5 -5
@@ -57,7 +57,6 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
57
57
  REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_URL: z.ZodOptional<z.ZodString>;
58
58
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT: z.ZodOptional<z.ZodString>;
59
59
  REDOCLY_DISABLE_API_FUNCTION_SANDBOX: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
60
- REDOCLY_EXP_MCP_CUSTOM_TOOLS_ENABLED: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
61
60
  REDOCLY_EXP_REPLAY_GRAPHQL_ENABLED: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
62
61
  } & {
63
62
  SEARCH_DEV_REINIT: z.ZodOptional<z.ZodString>;
@@ -137,7 +136,6 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
137
136
  REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_URL?: string | undefined;
138
137
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
139
138
  REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: boolean | undefined;
140
- REDOCLY_EXP_MCP_CUSTOM_TOOLS_ENABLED?: boolean | undefined;
141
139
  REDOCLY_EXP_REPLAY_GRAPHQL_ENABLED?: boolean | undefined;
142
140
  SEARCH_DEV_REINIT?: string | undefined;
143
141
  SEARCH_DEV_DEBUG: boolean;
@@ -210,7 +208,6 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
210
208
  REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_URL?: string | undefined;
211
209
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
212
210
  REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: "false" | "true" | undefined;
213
- REDOCLY_EXP_MCP_CUSTOM_TOOLS_ENABLED?: "false" | "true" | undefined;
214
211
  REDOCLY_EXP_REPLAY_GRAPHQL_ENABLED?: "false" | "true" | undefined;
215
212
  SEARCH_DEV_REINIT?: string | undefined;
216
213
  SEARCH_DEV_DEBUG?: "false" | "true" | undefined;
@@ -283,7 +280,6 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
283
280
  REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_URL?: string | undefined;
284
281
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
285
282
  REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: boolean | undefined;
286
- REDOCLY_EXP_MCP_CUSTOM_TOOLS_ENABLED?: boolean | undefined;
287
283
  REDOCLY_EXP_REPLAY_GRAPHQL_ENABLED?: boolean | undefined;
288
284
  SEARCH_DEV_REINIT?: string | undefined;
289
285
  SEARCH_DEV_DEBUG: boolean;
@@ -356,7 +352,6 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
356
352
  REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_URL?: string | undefined;
357
353
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
358
354
  REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: "false" | "true" | undefined;
359
- REDOCLY_EXP_MCP_CUSTOM_TOOLS_ENABLED?: "false" | "true" | undefined;
360
355
  REDOCLY_EXP_REPLAY_GRAPHQL_ENABLED?: "false" | "true" | undefined;
361
356
  SEARCH_DEV_REINIT?: string | undefined;
362
357
  SEARCH_DEV_DEBUG?: "false" | "true" | undefined;
@@ -17,8 +17,6 @@ export declare const featureFlagsSchema: z.ZodObject<{
17
17
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT: z.ZodOptional<z.ZodString>;
18
18
  /** Escape hatch: disables the Node Permission Model sandbox on API function and MCP tool worker processes (hosted Reunite environments only). */
19
19
  REDOCLY_DISABLE_API_FUNCTION_SANDBOX: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
20
- /** Experimental: register custom MCP tools from `@mcp` folders. */
21
- REDOCLY_EXP_MCP_CUSTOM_TOOLS_ENABLED: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
22
20
  /** Experimental: enables the GraphQL request body in Try It and embedded Replay. */
23
21
  REDOCLY_EXP_REPLAY_GRAPHQL_ENABLED: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
24
22
  }, "strip", z.ZodTypeAny, {
@@ -32,7 +30,6 @@ export declare const featureFlagsSchema: z.ZodObject<{
32
30
  REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_URL?: string | undefined;
33
31
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
34
32
  REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: boolean | undefined;
35
- REDOCLY_EXP_MCP_CUSTOM_TOOLS_ENABLED?: boolean | undefined;
36
33
  REDOCLY_EXP_REPLAY_GRAPHQL_ENABLED?: boolean | undefined;
37
34
  }, {
38
35
  NEW_CATALOG_ENABLED?: "false" | "true" | undefined;
@@ -45,7 +42,6 @@ export declare const featureFlagsSchema: z.ZodObject<{
45
42
  REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_URL?: string | undefined;
46
43
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
47
44
  REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: "false" | "true" | undefined;
48
- REDOCLY_EXP_MCP_CUSTOM_TOOLS_ENABLED?: "false" | "true" | undefined;
49
45
  REDOCLY_EXP_REPLAY_GRAPHQL_ENABLED?: "false" | "true" | undefined;
50
46
  }>;
51
47
  //# sourceMappingURL=feature-flags.d.ts.map
@@ -1 +1 @@
1
- import{z as _}from"zod";import{envBooleanStringOptional as E}from"../../utils/envs/env-boolean-string.js";const D=_.object({NEW_CATALOG_ENABLED:E(),NEW_SCORECARDS_ENABLED:E(),ENABLE_COMMENTS:_.string().optional(),REDOCLY_EXP_DISABLE_MD_VALIDATION:E(),ENABLE_SOURCE_MAPS:E(),PLAN_GATES:_.string().optional(),REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_ENABLED:E(),REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_URL:_.string().optional(),REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT:_.string().optional(),REDOCLY_DISABLE_API_FUNCTION_SANDBOX:E(),REDOCLY_EXP_MCP_CUSTOM_TOOLS_ENABLED:E(),REDOCLY_EXP_REPLAY_GRAPHQL_ENABLED:E()});export{D as featureFlagsSchema};
1
+ import{z as _}from"zod";import{envBooleanStringOptional as E}from"../../utils/envs/env-boolean-string.js";const D=_.object({NEW_CATALOG_ENABLED:E(),NEW_SCORECARDS_ENABLED:E(),ENABLE_COMMENTS:_.string().optional(),REDOCLY_EXP_DISABLE_MD_VALIDATION:E(),ENABLE_SOURCE_MAPS:E(),PLAN_GATES:_.string().optional(),REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_ENABLED:E(),REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_URL:_.string().optional(),REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT:_.string().optional(),REDOCLY_DISABLE_API_FUNCTION_SANDBOX:E(),REDOCLY_EXP_REPLAY_GRAPHQL_ENABLED:E()});export{D as featureFlagsSchema};
@@ -1,4 +1,4 @@
1
- import*as t from"react";import i from"styled-components";import{Flex as a}from"@redocly/portal-legacy-ui";import{WarningAltIcon as l}from"@redocly/theme/icons/WarningAltIcon/WarningAltIcon";import n from"./ApiKeyRow";import s from"./CreatedAtRow";import c from"./ExpiresAtRow";import m from"./RevokeAPIKey";import p from"./RollAPIKey";import d from"./SecretRow";import x from"./StageRow";import f from"./StatusRow";function u(r){const{apiKey:e}=r,o=Number(e.expiresAt)>0&&Math.abs(Number(e.expiresAt)-Date.now())<1440*60*1e3;return t.createElement(a,{flexDirection:"column",flexGrow:1},o&&t.createElement(v,null,t.createElement("div",null,"Expires soon"),t.createElement(l,{size:"24px",color:"#C8102E"})),t.createElement(g,{borderColor:o?"var(--color-error-border)":e.stage==="production"?"var(--color-success-border)":""},e.stage&&t.createElement(x,{stage:e.stage})||null,e.clientId&&t.createElement(n,{apiKey:e.clientId})||null,e.clientSecret&&t.createElement(d,{secret:e.clientSecret,hasClientId:!!e.clientId})||null,e.status&&t.createElement(f,{status:e.status})||null,t.createElement(s,{createdAt:e.issuedAt}),t.createElement(c,{expiresAt:e.expiresAt,expiresSoon:o})),e.canBeRolled&&t.createElement(p,{apiKey:e}),e.canBeRevoked&&t.createElement(m,{apiKey:e}))}const g=i.div`
1
+ import*as t from"react";import a from"styled-components";import{Flex as l}from"@redocly/portal-legacy-ui";import{WarningAltIcon as n}from"@redocly/theme/icons/WarningAltIcon/WarningAltIcon";import s from"./ApiKeyRow";import c from"./CreatedAtRow";import m from"./ExpiresAtRow";import p from"./RevokeAPIKey";import d from"./RollAPIKey";import x from"./SecretRow";import f from"./StageRow";import u from"./StatusRow";function g(r){const{apiKey:e}=r,[i]=t.useState(()=>Date.now()),o=Number(e.expiresAt)>0&&Math.abs(Number(e.expiresAt)-i)<1440*60*1e3;return t.createElement(l,{flexDirection:"column",flexGrow:1},o&&t.createElement(E,null,t.createElement("div",null,"Expires soon"),t.createElement(n,{size:"24px",color:"#C8102E"})),t.createElement(v,{borderColor:o?"var(--color-error-border)":e.stage==="production"?"var(--color-success-border)":""},e.stage&&t.createElement(f,{stage:e.stage})||null,e.clientId&&t.createElement(s,{apiKey:e.clientId})||null,e.clientSecret&&t.createElement(x,{secret:e.clientSecret,hasClientId:!!e.clientId})||null,e.status&&t.createElement(u,{status:e.status})||null,t.createElement(c,{createdAt:e.issuedAt}),t.createElement(m,{expiresAt:e.expiresAt,expiresSoon:o})),e.canBeRolled&&t.createElement(d,{apiKey:e}),e.canBeRevoked&&t.createElement(p,{apiKey:e}))}const v=a.div`
2
2
  display: flex;
3
3
  flex-direction: column;
4
4
 
@@ -9,7 +9,7 @@ import*as t from"react";import i from"styled-components";import{Flex as a}from"@
9
9
  > :not(:last-child) {
10
10
  border-bottom: 1px solid var(--border-color-primary);
11
11
  }
12
- `,v=i.div`
12
+ `,E=a.div`
13
13
  display: flex;
14
14
  gap: var(--spacing-xs);
15
15
  font-weight: var(--font-weight-regular);
@@ -20,4 +20,4 @@ import*as t from"react";import i from"styled-components";import{Flex as a}from"@
20
20
  width: 18px;
21
21
  height: 18px;
22
22
  }
23
- `;export{u as default};
23
+ `;export{g as default};
@@ -6,4 +6,4 @@ ${g}
6
6
  `)});else{const a=t.parentNode?.id??j,m=c.get(a)??{title:r,content:"",url:i,level:1/0,toc:""};m.content+=m.content?`
7
7
  `:"",m.content+=t.content.trimEnd(),c.set(a,m)}const d=$(Array.from(c.values())),u=L(n.fsPath);return d.filter(t=>t.content).map(t=>({title:t.title,url:t.url,content:[A,t.toc||`# ${r}`,t.content].filter(Boolean).join(`
8
8
  `),fsPath:n.fsPath,locale:u,product:n.product?.name,facets:f}))}function _(n){return n.length/4}function $(n){if(n.length===0)return[];const o=[];let e=n[0];for(let r=1;r<n.length;r++){const i=n[r],c=_(e.content),l=_(i.content),f=r===n.length-1;l<R&&(c<v||f)&&i.level>e.level?e.content+=`
9
- `+i.content:(o.push(e),e=i)}return o.push(e),o}const k=String.raw`\((?:[^()]|\([^()]*\))*\)`,G=new RegExp(String.raw`!\[[^\]]*\]`+k,"g"),z=new RegExp(String.raw`\[([^\]]*)\]`+k,"g");function W(n,o){const e=h(o);return o.content===n||!e?n:`${n} \u2192 ${e}`}function h(n){return B(n.content.replace(/^#+/,""))}function B(n){return n.replace(G,"").replace(z,"$1").trim()}export{at as getAiDocumentsStore};
9
+ `+i.content:(o.push(e),e=i)}return o.push(e),o}const k=String.raw`\((?:[^()]|\([^()]*\))*\)`,G=new RegExp(String.raw`!\[[^\]]*\]`+k,"g"),z=new RegExp(String.raw`\[(?<!\[[^\]()]*?\[)([^\]]*)\]`+k,"g");function W(n,o){const e=h(o);return o.content===n||!e?n:`${n} \u2192 ${e}`}function h(n){return B(n.content.replace(/^#+/,""))}function B(n){return n.replace(G,"").replace(z,"$1").trim()}export{at as getAiDocumentsStore};
@@ -1 +1 @@
1
- import{basename as d,join as p}from"node:path";import{envConfig as f}from"../../../config/env-config.js";import{logger as h}from"../../../tools/notifiers/logger.js";import{reporter as s}from"../../../tools/notifiers/reporter.js";import{CODE_MODE_TOOL_NAMES as O}from"../codemode/constants.js";import{ALL_DEFAULT_TOOL_NAMES as E}from"../docs-mcp/tool-schemas.js";import{extractToolDefinition as T}from"./extract-tool-definition.js";const M=/(^|\/)@mcp\/.*\.(ts|js)$/,_=/^[a-zA-Z0-9_-]{1,64}$/,C=new Set([...O,...E]);async function w(o,a,n){if(!f.REDOCLY_EXP_MCP_CUSTOM_TOOLS_ENABLED)return 0;N(o,n);const u=new Set(o.getMcpTools().map(r=>r.name)),l=new Map;for(const r of a.fs.scan(M)){if(r.isVirtual||r.relativePath.endsWith(".d.ts"))continue;const{relativePath:t}=r,e=d(t).replace(/\.(ts|js)$/,"");if(!_.test(e)){await s.panicOnBuildContentError(`Invalid MCP tool file "${t}": the tool name "${e}" is derived from the file name and may only contain letters, numbers, hyphens, and underscores (max 64 characters). Rename the file.`);continue}const c=l.get(e);if(c){await s.panicOnBuildContentError(`Duplicate MCP tool name "${e}": defined by both "${c}" and "${t}". Tool names are file names, so each file name must be unique across @mcp folders.`);continue}if(C.has(e)||u.has(e)){await s.panicOnBuildContentError(`Invalid MCP tool file "${t}": the tool name "${e}" is already used by a built-in MCP tool. Rename the file.`);continue}l.set(e,t);const m=p(o.contentDir,t),i=await T({toolName:e,absoluteSourcePath:m,contentDir:o.contentDir,serverOutDir:o.serverOutDir});if(!i.ok){await s.panicOnBuildContentError(`Invalid MCP tool file "${t}": ${i.errors.join(" ")}`);continue}!i.schema.outputSchema&&!n.warnedNoOutputSchemaTools.has(e)&&(n.warnedNoOutputSchemaTools.add(e),h.warn(`MCP tool "${e}" in %rp declares no \`outputSchema\`. Add one that describes the returned value.`,t)),o.addMcpTools(m,[i.schema]),n.registeredToolNames.push(e)}return n.registeredToolNames.length}function N(o,a){o.removeMcpTools(a.registeredToolNames),a.registeredToolNames=[]}export{N as resetCustomMcpTools,w as scanCustomMcpTools};
1
+ import{basename as d,join as p}from"node:path";import{logger as f}from"../../../tools/notifiers/logger.js";import{reporter as s}from"../../../tools/notifiers/reporter.js";import{CODE_MODE_TOOL_NAMES as h}from"../codemode/constants.js";import{ALL_DEFAULT_TOOL_NAMES as O}from"../docs-mcp/tool-schemas.js";import{extractToolDefinition as T}from"./extract-tool-definition.js";const E=/(^|\/)@mcp\/.*\.(ts|js)$/,M=/^[a-zA-Z0-9_-]{1,64}$/,C=new Set([...h,...O]);async function g(o,a,n){_(o,n);const u=new Set(o.getMcpTools().map(r=>r.name)),l=new Map;for(const r of a.fs.scan(E)){if(r.isVirtual||r.relativePath.endsWith(".d.ts"))continue;const{relativePath:t}=r,e=d(t).replace(/\.(ts|js)$/,"");if(!M.test(e)){await s.panicOnBuildContentError(`Invalid MCP tool file "${t}": the tool name "${e}" is derived from the file name and may only contain letters, numbers, hyphens, and underscores (max 64 characters). Rename the file.`);continue}const c=l.get(e);if(c){await s.panicOnBuildContentError(`Duplicate MCP tool name "${e}": defined by both "${c}" and "${t}". Tool names are file names, so each file name must be unique across @mcp folders.`);continue}if(C.has(e)||u.has(e)){await s.panicOnBuildContentError(`Invalid MCP tool file "${t}": the tool name "${e}" is already used by a built-in MCP tool. Rename the file.`);continue}l.set(e,t);const m=p(o.contentDir,t),i=await T({toolName:e,absoluteSourcePath:m,contentDir:o.contentDir,serverOutDir:o.serverOutDir});if(!i.ok){await s.panicOnBuildContentError(`Invalid MCP tool file "${t}": ${i.errors.join(" ")}`);continue}!i.schema.outputSchema&&!n.warnedNoOutputSchemaTools.has(e)&&(n.warnedNoOutputSchemaTools.add(e),f.warn(`MCP tool "${e}" in %rp declares no \`outputSchema\`. Add one that describes the returned value.`,t)),o.addMcpTools(m,[i.schema]),n.registeredToolNames.push(e)}return n.registeredToolNames.length}function _(o,a){o.removeMcpTools(a.registeredToolNames),a.registeredToolNames=[]}export{_ as resetCustomMcpTools,g as scanCustomMcpTools};
@@ -1 +1 @@
1
- import{SEMANTIC_SEARCH_API_URL as y}from"../../../constants/common.js";import{shouldHandleMcpAuth as f}from"../../../utils/rbac/should-handle-mcp-auth.js";import{CREATE_ANONYMOUS_SESSION_TOOL_NAME as g,SUBMIT_FEEDBACK_TOOL_NAME as b}from"../constants.js";import{GRAPHQL_TYPE_KINDS as l,GRAPHQL_LIST_SCOPES as v}from"./tools/graphql/constants.js";import{OPERATION_METHODS as c}from"./tools/openapi/constants.js";const i={type:"string",nullable:!0},s={limit:{type:"number"},total:{type:"number"},page:{type:"number"},totalPages:{type:"number"}},a={codemodeType:"OpenApiServer[]"},o={codemodeType:"OpenApiSecurityRequirement[]"},u={codemodeType:"Record<string, OpenApiSecurityScheme>"},P={codemodeType:"OpenApiDocument"},A={codemodeType:"OpenApiOperation & { path: string; method: string }"},S={codemodeType:"OpenApiComponents",description:"Components the endpoint references. A `$ref` in `endpoint` resolves against the root of the result."},n={filePath:{type:"string",description:"API definition file path"},otherMatches:{type:"array",description:"Other API definitions the lookup also matched. Present only when several matched; pass `filePath` to pick another one",items:{type:"object",required:["filePath"],properties:{version:{type:"string"},filePath:{type:"string"}}}}},r="When several definitions match the name, the highest version or the shortest file path is used and the rest are listed in `otherMatches`; tell the user which version or file the answer is based on.",H="A `$ref` in `endpoint` points into the `components` key at the root of the result: read `#/components/<section>/<name>` there.",L={type:"object",additionalProperties:!1,required:[],properties:{filter:{type:"string",description:"Matches part of an API name, or of a description when no name matches",minLength:1,nullable:!0},page:{type:"number",description:"Page number",minimum:1,default:1,nullable:!0},limit:{type:"number",description:"Number of APIs per page. Default is 300",minimum:1,default:300,nullable:!0}}},p={type:"object",required:[],additionalProperties:!1,properties:{name:{type:"string",description:"API name, or a unique part of it. Omit when `filePath` is given.",minLength:1,nullable:!0},apiName:{type:"string",description:"Alias of `name`",nullable:!0},api:{type:"string",description:"Alias of `name`",nullable:!0},version:{type:"string",description:"API version. When omitted and several versions exist, the highest one is used",nullable:!0},filePath:{type:"string",description:"API definition file path, as returned by `listApis`",nullable:!0}}},q={type:"object",required:["path","method"],additionalProperties:!1,properties:{name:{type:"string",description:"API name, or a unique part of it. Omit when `filePath` is given.",minLength:1,nullable:!0},apiName:{type:"string",description:"Alias of `name`",nullable:!0},api:{type:"string",description:"Alias of `name`",nullable:!0},path:{type:"string",description:"Endpoint path (e.g. /api/v1/users)",minLength:1},method:{type:"string",description:"HTTP method (GET, POST, PUT, DELETE, etc.)",enum:[...c.map(e=>e.toUpperCase()),...c],minLength:1},version:{type:"string",description:"API version. When omitted and several versions exist, the highest one is used",nullable:!0},filePath:{type:"string",description:"API definition file path, as returned by `listApis`",nullable:!0}}},O={type:"object",required:[],additionalProperties:!1,properties:{}},T={type:"object",required:[],additionalProperties:!1,properties:{}},G={type:"object",required:["path","summary"],additionalProperties:!1,properties:{path:{type:"string",description:"Path or URL of the documentation page the feedback is about (e.g. /guides/auth)",minLength:1,maxLength:1e3},summary:{type:"string",description:"One sentence that names what is wrong with the page",minLength:1,maxLength:500},details:{type:"string",description:"Expected versus actual behavior, missing steps, or the snippet that failed",maxLength:4e3,nullable:!0},targetFeature:{type:"string",description:"The API or feature the page documents",maxLength:500,nullable:!0}}};function I(e){const t=[{name:"list-apis",title:"List APIs",description:"Lists the APIs documented in this project",schema:L,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",required:["items","limit","total","page","totalPages"],properties:{items:{type:"array",items:{type:"object",required:["name","description","version","servers","filePath"],properties:{name:{type:"string"},description:{type:"string"},version:{type:"string"},servers:a,filePath:{type:"string",description:"API definition file path"},ambiguousTitle:{type:"boolean",description:"true = another API shares this title; a lookup picks the highest version, or the shortest file path at the same version, and lists the rest in `otherMatches`. Pass `filePath` to pick another one"},...e.hasCallableApis?{callable:{type:"boolean",description:"true = the documented hosts of this API are reachable via `fetch`; false = documentation only"}}:{}}}},...s}}},{name:"get-endpoints",title:"Get endpoints",description:`Get all endpoints for a specific API. ${r}`,schema:p,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",required:["api","version","filePath","servers","endpoints"],properties:{api:{type:"string"},version:{type:"string"},...n,servers:a,endpoints:{type:"array",items:{type:"object",required:["path","method"],properties:{path:{type:"string"},method:{type:"string"},summary:i,description:i,security:o}}}}}},{name:"get-endpoint-info",title:"Get endpoint info",description:`Get comprehensive information about specific endpoint including parameters, security, and examples. ${H} ${r}`,schema:q,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",required:["api","version","filePath","servers","endpoint","globalSecurity","securitySchemes"],properties:{api:{type:"string"},version:{type:"string"},...n,servers:a,endpoint:A,globalSecurity:o,securitySchemes:u,components:S}}},{name:"get-security-schemes",title:"Get security schemes",description:`Get the security schemes for a specific API. ${r}`,schema:p,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",required:["api","version","filePath","securitySchemes","security"],properties:{api:{type:"string"},version:{type:"string"},...n,securitySchemes:u,security:o}}},{name:"get-full-api-description",title:"Get full API description",description:`Get the complete OpenAPI description. ${r}`,schema:p,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",required:["api","version","filePath","definition"],properties:{api:{type:"string"},version:{type:"string"},...n,definition:P}}},...y?[d(e.products,e.locales)]:[]];return f(e.requiresLogin,e.rbac)&&t.push({name:"whoami",title:"Who am I",description:"Get information about the currently authenticated user",schema:O,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{email:{type:"string"},name:{type:"string"},subject:{type:"string"},clientId:{type:"string"},scope:{type:"string"},issuedAt:{type:"string"},expiresAt:{type:"string"}}}}),e.feedbackHidden||t.push({name:b,title:"Submit feedback",description:"Report a documentation page that is wrong or incomplete",schema:G,annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1},outputSchema:{type:"object",required:["status","page","message"],properties:{status:{type:"string"},page:{type:"string"},message:{type:"string"}}}}),e.hasGraphqlApis&&t.push(...k()),e.elicitationEnabled&&e.hasCallableApis&&t.push({name:g,title:"Create anonymous session",description:"Create an anonymous session handle for storing upstream API credentials. Pass the returned sessionHandle to execute when an API needs credentials and the caller is not signed in. Treat it as a secret and reuse it across calls.",schema:T,annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1},outputSchema:{type:"object",required:["sessionHandle"],properties:{sessionHandle:{type:"string"}}}}),t}const w={type:"object",required:[],additionalProperties:!1,properties:{filter:{type:"string",description:"GraphQL schema name (or part of it)",minLength:1,nullable:!0},page:{type:"number",description:"Page number",minimum:1,default:1,nullable:!0},limit:{type:"number",description:"Number of results per page. Default is 300",minimum:1,default:300,nullable:!0}}},j={type:"object",required:["name"],additionalProperties:!1,properties:{name:{type:"string",description:"GraphQL schema name",minLength:1},kind:{type:"string",description:'Optional: return only a single category of schema members. With scope "fields", restricts to the fields of that parent category ("objects", "interfaces", or "inputs" only).',enum:[...l,null],nullable:!0},scope:{type:"string",description:'Optional: what each list item is. "members" (default) lists top-level types and operations. "fields" flattens every field of every object/interface/input type into "ParentType.fieldName" entries with their descriptions \u2014 combine with `filter` to search nested fields anywhere in the schema.',enum:[...v,null],nullable:!0},filter:{type:"string",description:'Optional: only members whose name or description contains this substring, case-insensitive (e.g. "custom tags")',minLength:1,nullable:!0},referencesType:{type:"string",description:'Optional: only members that reference this type directly or transitively (via return, field, argument, union-member or input-field types). Finds cross-cutting members that never name it \u2014 e.g. referencesType "Book" surfaces a search query returning a union that includes Book.',minLength:1,nullable:!0},page:{type:"number",description:"Page number",minimum:1,default:1,nullable:!0},limit:{type:"number",description:"Number of results per page. Default is 300",minimum:1,default:300,nullable:!0}}},E={type:"object",required:["name"],additionalProperties:!1,properties:{name:{type:"string",description:"GraphQL schema name",minLength:1},type:{type:"string",description:"Name of the type, operation, or directive",minLength:1,nullable:!0},typeName:{type:"string",description:"Alias of `type`",nullable:!0},includeReferencedTypes:{type:"boolean",description:"Also include the SDL of every type referenced transitively. Gives the whole subgraph at once, but can be large for highly-connected types.",default:!1,nullable:!0}}},N={type:"object",required:["name"],additionalProperties:!1,properties:{name:{type:"string",description:"GraphQL schema name",minLength:1}}};function k(){return[{name:"list-graphql-apis",title:"List GraphQL APIs",description:"Lists available GraphQL schemas with their context and purpose",schema:w,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",required:["items","limit","total","page","totalPages"],properties:{items:{type:"array",items:{type:"object",required:["name"],properties:{name:{type:"string"},description:i}}},...s}}},{name:"get-graphql-type-list",title:"Get GraphQL type list",description:'List all operations and types in a GraphQL schema as a flat, paginated list. Each item is tagged with its kind (query, object, enum, \u2026) and carries a one-line description; operations also include their return type and argument signature \u2014 use it to decide which members to fetch in detail. Narrow the result with `kind` (one category), `filter` (case-insensitive substring of the member name or description, e.g. "book"), or `referencesType` (only members that use a given type, directly or transitively) to explore a domain cheaply instead of pulling the full schema. To search nested fields \u2014 not just type names \u2014 set scope "fields": it flattens every field of every object/interface/input type into "ParentType.fieldName" entries with their descriptions, so `filter` finds things like an input field whose docstring mentions your keyword.',schema:j,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",required:["name","items","limit","total","page","totalPages"],properties:{name:{type:"string"},items:{type:"array",items:{type:"object",required:["kind","name"],properties:{kind:{type:"string",enum:l},name:{type:"string"},description:i,type:{type:"string"},args:{type:"string"},directives:{type:"string"}}}},...s}}},{name:"get-graphql-type",title:"Get GraphQL type",description:'Get the SDL for a single GraphQL type, operation (query/mutation/subscription field), or directive. Without includeReferencedTypes, the SDL is followed by a one-level manifest of every directly referenced type\'s field names (e.g. "input ActionOperationInput { addTags, removeTags }"), so you can see what nested types contain before fetching them. Set includeReferencedTypes to instead pull in the full SDL of every type it references transitively \u2014 the whole structural subgraph in one call, ideal for complete exploration of an entity (e.g. Book and all the types it touches). That traversal only follows what the target depends on; to go the other direction and find the members that use a type, call get-graphql-type-list with `referencesType`.',schema:E,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"string"}},{name:"get-graphql-schema",title:"Get GraphQL schema",description:"Get the complete GraphQL schema as SDL \u2014 the whole type graph in one call. Returns everything, so it can be very large and token-expensive for big schemas; prefer get-graphql-type-list (filter/paginate) plus targeted get-graphql-type calls, and reach for this only for small schemas or when you truly need the full SDL.",schema:N,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"string"}}]}function d(e,t){const h=e&&e.length>0,m=t&&t.length>0;return{name:"search",title:"Search documentation",description:"Search across the documentation to fetch relevant content for a given query",schema:{type:"object",required:["query"],additionalProperties:!1,properties:{query:{type:"string",description:"Search query. Should be a single word or that phrase that is presented in a documentation.",minLength:1},...h?{product:{type:"string",description:"Optional product name to filter search results by specific product.",enum:[...e,null],nullable:!0}}:{},...m?{locale:{type:"string",description:"Optional locale code to filter search results by documentation language (e.g. 'en', 'ja'). Use the locale matching the language of the user's request. Defaults to the project's default locale.",enum:[...t,null],nullable:!0}}:{}}},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1},outputSchema:{type:"string"}}}const Q=new Set([...I({requiresLogin:!0,hasGraphqlApis:!0,elicitationEnabled:!0,hasCallableApis:!0}).map(e=>e.name),d().name]);export{Q as ALL_DEFAULT_TOOL_NAMES,I as getDefaultToolSchemas};
1
+ import{SEMANTIC_SEARCH_API_URL as y}from"../../../constants/common.js";import{shouldHandleMcpAuth as f}from"../../../utils/rbac/should-handle-mcp-auth.js";import{CREATE_ANONYMOUS_SESSION_TOOL_NAME as g,SUBMIT_FEEDBACK_TOOL_NAME as b}from"../constants.js";import{GRAPHQL_TYPE_KINDS as l,GRAPHQL_LIST_SCOPES as v}from"./tools/graphql/constants.js";import{OPERATION_METHODS as c}from"./tools/openapi/constants.js";const i={type:"string",nullable:!0},s={limit:{type:"number"},total:{type:"number"},page:{type:"number"},totalPages:{type:"number"}},a={codemodeType:"OpenApiServer[]"},o={codemodeType:"OpenApiSecurityRequirement[]"},u={codemodeType:"Record<string, OpenApiSecurityScheme>"},P={codemodeType:"OpenApiDocument"},A={codemodeType:"OpenApiOperation & { path: string; method: string }"},S={codemodeType:"OpenApiComponents",description:"Components the endpoint references. A `$ref` in `endpoint` resolves against the root of the result."},n={filePath:{type:"string",description:"API definition file path"},otherMatches:{type:"array",description:"Other API definitions the lookup also matched. Present only when several matched; pass `filePath` to pick another one",items:{type:"object",required:["filePath"],properties:{version:{type:"string"},filePath:{type:"string"}}}}},r="When several definitions match the name, the highest version or the shortest file path is used and the rest are listed in `otherMatches`; tell the user which version or file the answer is based on.",H="A `$ref` in `endpoint` points into the `components` key at the root of the result: read `#/components/<section>/<name>` there.",L={type:"object",additionalProperties:!1,required:[],properties:{filter:{type:"string",description:"Matches part of an API name, or of a description when no name matches",minLength:1,nullable:!0},page:{type:"number",description:"Page number",minimum:1,default:1,nullable:!0},limit:{type:"number",description:"Number of APIs per page. Default is 300",minimum:1,default:300,nullable:!0}}},p={type:"object",required:[],additionalProperties:!1,properties:{name:{type:"string",description:"API name, or a unique part of it. Omit when `filePath` is given.",minLength:1,nullable:!0},apiName:{type:"string",description:"Alias of `name`",nullable:!0},api:{type:"string",description:"Alias of `name`",nullable:!0},version:{type:"string",description:"API version. When omitted and several versions exist, the highest one is used",nullable:!0},filePath:{type:"string",description:"API definition file path, as returned by `listApis`",nullable:!0}}},q={type:"object",required:["path","method"],additionalProperties:!1,properties:{name:{type:"string",description:"API name, or a unique part of it. Omit when `filePath` is given.",minLength:1,nullable:!0},apiName:{type:"string",description:"Alias of `name`",nullable:!0},api:{type:"string",description:"Alias of `name`",nullable:!0},path:{type:"string",description:"Endpoint path (e.g. /api/v1/users)",minLength:1},method:{type:"string",description:"HTTP method (GET, POST, PUT, DELETE, etc.)",enum:[...c.map(e=>e.toUpperCase()),...c],minLength:1},version:{type:"string",description:"API version. When omitted and several versions exist, the highest one is used",nullable:!0},filePath:{type:"string",description:"API definition file path, as returned by `listApis`",nullable:!0}}},O={type:"object",required:[],additionalProperties:!1,properties:{}},T={type:"object",required:[],additionalProperties:!1,properties:{}},G={type:"object",required:["path","summary"],additionalProperties:!1,properties:{path:{type:"string",description:"Path or URL of the documentation page the feedback is about (e.g. /guides/auth)",minLength:1,maxLength:1e3},summary:{type:"string",description:"One sentence that names what is wrong with the page",minLength:1,maxLength:500},details:{type:"string",description:"Expected versus actual behavior, missing steps, or the snippet that failed",maxLength:4e3,nullable:!0},targetFeature:{type:"string",description:"The API or feature the page documents",maxLength:500,nullable:!0}}};function I(e){const t=[{name:"list-apis",title:"List APIs",description:"Lists the APIs documented in this project",schema:L,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",required:["items","limit","total","page","totalPages"],properties:{items:{type:"array",items:{type:"object",required:["name","description","version","servers","filePath"],properties:{name:{type:"string"},description:{type:"string"},version:{type:"string"},servers:a,filePath:{type:"string",description:"API definition file path"},ambiguousTitle:{type:"boolean",description:"true = another API shares this title; a lookup picks the highest version, or the shortest file path at the same version, and lists the rest in `otherMatches`. Pass `filePath` to pick another one"},...e.hasCallableApis?{callable:{type:"boolean",description:"true = the documented hosts of this API are reachable via `fetch`; false = documentation only"}}:{}}}},...s}}},{name:"get-endpoints",title:"Get endpoints",description:`Get all endpoints for a specific API. ${r}`,schema:p,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",required:["api","version","filePath","servers","endpoints"],properties:{api:{type:"string"},version:{type:"string"},...n,servers:a,endpoints:{type:"array",items:{type:"object",required:["path","method"],properties:{path:{type:"string"},method:{type:"string"},summary:i,description:i,security:o}}}}}},{name:"get-endpoint-info",title:"Get endpoint info",description:`Get comprehensive information about specific endpoint including parameters, security, and examples. ${H} ${r}`,schema:q,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",required:["api","version","filePath","servers","endpoint","globalSecurity","securitySchemes"],properties:{api:{type:"string"},version:{type:"string"},...n,servers:a,endpoint:A,globalSecurity:o,securitySchemes:u,components:S}}},{name:"get-security-schemes",title:"Get security schemes",description:`Get the security schemes for a specific API. ${r}`,schema:p,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",required:["api","version","filePath","securitySchemes","security"],properties:{api:{type:"string"},version:{type:"string"},...n,securitySchemes:u,security:o}}},{name:"get-full-api-description",title:"Get full API description",description:`Get the complete OpenAPI description. ${r}`,schema:p,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",required:["api","version","filePath","definition"],properties:{api:{type:"string"},version:{type:"string"},...n,definition:P}}},...y?[d(e.products,e.locales)]:[]];return f(e.requiresLogin,e.rbac)&&t.push({name:"whoami",title:"Who am I",description:"Get information about the currently authenticated user",schema:O,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",required:["authenticated"],properties:{authenticated:{type:"boolean"},email:{type:"string"},name:{type:"string"},subject:{type:"string"},clientId:{type:"string"},scope:{type:"string"},issuedAt:{type:"string"},expiresAt:{type:"string"}}}}),e.feedbackHidden||t.push({name:b,title:"Submit feedback",description:"Report a documentation page that is wrong or incomplete",schema:G,annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1},outputSchema:{type:"object",required:["status","page","message"],properties:{status:{type:"string"},page:{type:"string"},message:{type:"string"}}}}),e.hasGraphqlApis&&t.push(...k()),e.elicitationEnabled&&e.hasCallableApis&&t.push({name:g,title:"Create anonymous session",description:"Create an anonymous session handle for storing upstream API credentials. Pass the returned sessionHandle to execute when an API needs credentials and the caller is not signed in. Treat it as a secret and reuse it across calls.",schema:T,annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1},outputSchema:{type:"object",required:["sessionHandle"],properties:{sessionHandle:{type:"string"}}}}),t}const w={type:"object",required:[],additionalProperties:!1,properties:{filter:{type:"string",description:"GraphQL schema name (or part of it)",minLength:1,nullable:!0},page:{type:"number",description:"Page number",minimum:1,default:1,nullable:!0},limit:{type:"number",description:"Number of results per page. Default is 300",minimum:1,default:300,nullable:!0}}},j={type:"object",required:["name"],additionalProperties:!1,properties:{name:{type:"string",description:"GraphQL schema name",minLength:1},kind:{type:"string",description:'Optional: return only a single category of schema members. With scope "fields", restricts to the fields of that parent category ("objects", "interfaces", or "inputs" only).',enum:[...l,null],nullable:!0},scope:{type:"string",description:'Optional: what each list item is. "members" (default) lists top-level types and operations. "fields" flattens every field of every object/interface/input type into "ParentType.fieldName" entries with their descriptions \u2014 combine with `filter` to search nested fields anywhere in the schema.',enum:[...v,null],nullable:!0},filter:{type:"string",description:'Optional: only members whose name or description contains this substring, case-insensitive (e.g. "custom tags")',minLength:1,nullable:!0},referencesType:{type:"string",description:'Optional: only members that reference this type directly or transitively (via return, field, argument, union-member or input-field types). Finds cross-cutting members that never name it \u2014 e.g. referencesType "Book" surfaces a search query returning a union that includes Book.',minLength:1,nullable:!0},page:{type:"number",description:"Page number",minimum:1,default:1,nullable:!0},limit:{type:"number",description:"Number of results per page. Default is 300",minimum:1,default:300,nullable:!0}}},E={type:"object",required:["name"],additionalProperties:!1,properties:{name:{type:"string",description:"GraphQL schema name",minLength:1},type:{type:"string",description:"Name of the type, operation, or directive",minLength:1,nullable:!0},typeName:{type:"string",description:"Alias of `type`",nullable:!0},includeReferencedTypes:{type:"boolean",description:"Also include the SDL of every type referenced transitively. Gives the whole subgraph at once, but can be large for highly-connected types.",default:!1,nullable:!0}}},N={type:"object",required:["name"],additionalProperties:!1,properties:{name:{type:"string",description:"GraphQL schema name",minLength:1}}};function k(){return[{name:"list-graphql-apis",title:"List GraphQL APIs",description:"Lists available GraphQL schemas with their context and purpose",schema:w,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",required:["items","limit","total","page","totalPages"],properties:{items:{type:"array",items:{type:"object",required:["name"],properties:{name:{type:"string"},description:i}}},...s}}},{name:"get-graphql-type-list",title:"Get GraphQL type list",description:'List all operations and types in a GraphQL schema as a flat, paginated list. Each item is tagged with its kind (query, object, enum, \u2026) and carries a one-line description; operations also include their return type and argument signature \u2014 use it to decide which members to fetch in detail. Narrow the result with `kind` (one category), `filter` (case-insensitive substring of the member name or description, e.g. "book"), or `referencesType` (only members that use a given type, directly or transitively) to explore a domain cheaply instead of pulling the full schema. To search nested fields \u2014 not just type names \u2014 set scope "fields": it flattens every field of every object/interface/input type into "ParentType.fieldName" entries with their descriptions, so `filter` finds things like an input field whose docstring mentions your keyword.',schema:j,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",required:["name","items","limit","total","page","totalPages"],properties:{name:{type:"string"},items:{type:"array",items:{type:"object",required:["kind","name"],properties:{kind:{type:"string",enum:l},name:{type:"string"},description:i,type:{type:"string"},args:{type:"string"},directives:{type:"string"}}}},...s}}},{name:"get-graphql-type",title:"Get GraphQL type",description:'Get the SDL for a single GraphQL type, operation (query/mutation/subscription field), or directive. Without includeReferencedTypes, the SDL is followed by a one-level manifest of every directly referenced type\'s field names (e.g. "input ActionOperationInput { addTags, removeTags }"), so you can see what nested types contain before fetching them. Set includeReferencedTypes to instead pull in the full SDL of every type it references transitively \u2014 the whole structural subgraph in one call, ideal for complete exploration of an entity (e.g. Book and all the types it touches). That traversal only follows what the target depends on; to go the other direction and find the members that use a type, call get-graphql-type-list with `referencesType`.',schema:E,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"string"}},{name:"get-graphql-schema",title:"Get GraphQL schema",description:"Get the complete GraphQL schema as SDL \u2014 the whole type graph in one call. Returns everything, so it can be very large and token-expensive for big schemas; prefer get-graphql-type-list (filter/paginate) plus targeted get-graphql-type calls, and reach for this only for small schemas or when you truly need the full SDL.",schema:N,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"string"}}]}function d(e,t){const h=e&&e.length>0,m=t&&t.length>0;return{name:"search",title:"Search documentation",description:"Search across the documentation to fetch relevant content for a given query",schema:{type:"object",required:["query"],additionalProperties:!1,properties:{query:{type:"string",description:"Search query. Should be a single word or that phrase that is presented in a documentation.",minLength:1},...h?{product:{type:"string",description:"Optional product name to filter search results by specific product.",enum:[...e,null],nullable:!0}}:{},...m?{locale:{type:"string",description:"Optional locale code to filter search results by documentation language (e.g. 'en', 'ja'). Use the locale matching the language of the user's request. Defaults to the project's default locale.",enum:[...t,null],nullable:!0}}:{}}},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1},outputSchema:{type:"string"}}}const Q=new Set([...I({requiresLogin:!0,hasGraphqlApis:!0,elicitationEnabled:!0,hasCallableApis:!0}).map(e=>e.name),d().name]);export{Q as ALL_DEFAULT_TOOL_NAMES,I as getDefaultToolSchemas};
@@ -1 +1 @@
1
- const s=async(i,e)=>e.user?.isAuthenticated?{content:[{type:"text",text:JSON.stringify({email:e.user.email,name:e.user.claims?.name,subject:e.user.claims?.sub,clientId:e.user.claims?.client_id,scope:e.user.claims?.scope,issuedAt:e.user.claims?.iat?new Date(Number(e.user.claims.iat)*1e3).toISOString():void 0,expiresAt:e.user.claims?.exp?new Date(Number(e.user.claims.exp)*1e3).toISOString():void 0})}],isError:!1}:{content:[{type:"text",text:JSON.stringify({error:"Not authenticated",message:"No valid authentication token found. Please authenticate first."})}],isError:!0};var r={whoami:s};export{r as default};
1
+ const s=async(i,e)=>e.user?.isAuthenticated?{content:[{type:"text",text:JSON.stringify({authenticated:!0,email:e.user.email,name:e.user.claims?.name,subject:e.user.claims?.sub,clientId:e.user.claims?.client_id,scope:e.user.claims?.scope,issuedAt:e.user.claims?.iat?new Date(Number(e.user.claims.iat)*1e3).toISOString():void 0,expiresAt:e.user.claims?.exp?new Date(Number(e.user.claims.exp)*1e3).toISOString():void 0})}]}:{content:[{type:"text",text:JSON.stringify({authenticated:!1})}]};var a={whoami:s};export{a as default};
@@ -1 +1 @@
1
- const r=/[\r\n]/,o=255;function a(n){for(let e=0;e<n.length;e+=1){const t=n.charCodeAt(e);if(t===0||t>255)return!0}return!1}function s(n,e){if(!e)return"Enter a value before saving.";if(r.test(e))return"The value contains a line break. Paste it as a single line and save again.";if(a(e))return"The value contains a character that cannot be sent in an HTTP header, such as a curly quote or an invisible character. Copy the value again as plain text and save.";if(n.kind==="cookie")return i(n.name,e)}function i(n,e){if(e.includes(";")||e.toLowerCase().includes(`${n.toLowerCase()}=`))return`This looks like a whole cookie header. Paste only the value of the ${n} cookie, without the "${n}=" prefix and without any other name=value pairs.`}function c(n,e){if(!(!r.test(e)&&!a(e)))throw new Error(`The credential cannot be sent as the ${n} header value.`)}export{c as assertHeaderSafe,s as rejectCredential};
1
+ const r=/[\r\n]/,s=255,i=/^bearer$/i;function a(t){for(let e=0;e<t.length;e+=1){const n=t.charCodeAt(e);if(n===0||n>255)return!0}return!1}function c(t,e){if(!e)return"Enter a value before saving.";if(r.test(e))return"The value contains a line break. Paste it as a single line and save again.";if(a(e))return"The value contains a character that cannot be sent in an HTTP header, such as a curly quote or an invisible character. Copy the value again as plain text and save.";if(t.kind==="cookie")return o(t.name,e);if((t.kind==="bearer"||t.kind==="oauth2ClientCredentials")&&i.test(e))return"Paste the token that follows the word Bearer, not only the word."}function o(t,e){if(e.includes(";")||e.toLowerCase().includes(`${t.toLowerCase()}=`))return`This looks like a whole cookie header. Paste only the value of the ${t} cookie, without the "${t}=" prefix and without any other name=value pairs.`}function h(t,e){if(!(!r.test(e)&&!a(e)))throw new Error(`The credential cannot be sent as the ${t} header value.`)}export{h as assertHeaderSafe,c as rejectCredential};
@@ -1,2 +1,2 @@
1
- import{getApiDescriptionFromFs as S}from"../docs-mcp/tools/openapi/utils.js";import{OPERATION_METHODS as b}from"../docs-mcp/tools/openapi/constants.js";const A=120,l="default",g={kind:"raw"},h=["oauth2ClientCredentials","bearer","apiKey","cookie","raw"],E=new Set(b),j=/\{(?<!\{[^}]*?\{)[^}]*\}/g;function c(){return{schemes:{[l]:g},defaultScheme:l,routes:[]}}async function M(e,t,n=S){const r=Object.values(e.apiDescriptionsMap??{}).find(i=>i.relativePath===t);if(!r)return c();try{const i=await n({relativePath:r.relativePath,outdir:e.outdir||"",user:e.user,rbac:e.config?.access?.rbac||{},requiresLogin:!!e.config?.access?.requiresLogin});return i?w(i,r.servers??[]):c()}catch{return c()}}function w(e,t){const n=x(e),r=Object.keys(n);if(r.length===0)return c();const i=m(e.security,n)[0]??a(r,n)[0],o=C(e,r);return{schemes:n,defaultScheme:i,routes:H(e,n,t),...Object.keys(o).length>0?{descriptions:o}:{}}}function L(e){return[...new Set(Object.values(e).map(P))]}function N(e){return a(Object.keys(e),e)}function U(e,t){const n=new Set;for(const r of e.routes)r.schemes.includes(t)&&n.add(`${r.method} ${r.path}`);return[...n]}function P(e){return e.kind==="apiKey"?e.header:e.kind==="cookie"?"Cookie":"Authorization"}function C(e,t){const n={};for(const r of t){const i=e.components?.securitySchemes?.[r],o=O(i)?i.description:void 0;if(typeof o!="string")continue;const s=o.split(`
2
- `)[0].trim();s&&(n[r]=s.slice(0,A))}return n}function x(e){const t={};for(const[n,r]of Object.entries(e.components?.securitySchemes??{})){if(!O(r))continue;const i=T(r);i&&(t[n]=i)}return t}function T(e){if(e.type==="oauth2"){const t=e.flows?.clientCredentials;return t?{kind:"oauth2ClientCredentials",tokenUrl:t.tokenUrl,scopes:Object.keys(t.scopes??{})}:{kind:"bearer"}}if(e.type==="http"&&e.scheme==="bearer")return{kind:"bearer"};if(e.type==="apiKey"&&e.in==="header")return{kind:"apiKey",header:e.name||"X-Api-Key"};if(e.type==="apiKey"&&e.in==="cookie"&&e.name)return{kind:"cookie",name:e.name}}function a(e,t){return[...e].sort((n,r)=>h.indexOf(t[n].kind)-h.indexOf(t[r].kind)||e.indexOf(n)-e.indexOf(r))}function m(e,t){const n=[];for(const r of e??[])for(const i of Object.keys(r))t[i]&&!n.includes(i)&&n.push(i);return n}function H(e,t,n){const r=_(n),i=[];for(const[o,s]of Object.entries(e.paths??{})){if(!s||typeof s!="object")continue;const f=R(r,o);if(f)for(const[p,k]of Object.entries(s)){if(!E.has(p.toLowerCase()))continue;const u=k?.security??e.security;if(u===void 0)continue;const d=m(u,t);d.length===0&&u.length>0||i.push({pattern:f,path:o,method:p.toUpperCase(),schemes:a(d,t)})}}return i}function R(e,t){if(t.startsWith("/"))return new RegExp(`^${e}${y(t)}/?$`)}function y(e){return e.split(j).map(v).join("[^/]+").replace(/(?<!\/)\/+$/,"")}function _(e){const t=[...new Set(e.map(n=>$(n.url)).filter(Boolean).map(y))];return t.length>0?`(?:${t.join("|")})?`:""}function $(e){if(!e)return"";const t=e.replace(/^[a-z][a-z0-9+.-]*:\/\//i,""),n=t.indexOf("/");return n===-1?"":t.slice(n).split(/[?#]/)[0].replace(/(?<!\/)\/+$/,"")}function v(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function O(e){return typeof e=="object"&&e!==null&&"type"in e}export{l as DEFAULT_SCHEME_NAME,L as credentialHeadersOf,U as operationsForScheme,N as orderedSchemesOf,M as resolveUpstreamApiAuth};
1
+ import{getApiDescriptionFromFs as S}from"../docs-mcp/tools/openapi/utils.js";import{OPERATION_METHODS as b}from"../docs-mcp/tools/openapi/constants.js";const A=120,l="default",g={kind:"raw"},h=["oauth2ClientCredentials","bearer","apiKey","cookie","raw"],E=new Set(b),j=/\{(?<!\{[^}]*?\{)[^}]*\}/g;function c(){return{schemes:{[l]:g},defaultScheme:l,routes:[]}}async function L(e,t,n=S){const r=Object.values(e.apiDescriptionsMap??{}).find(i=>i.relativePath===t);if(!r)return c();try{const i=await n({relativePath:r.relativePath,outdir:e.outdir||"",user:e.user,rbac:e.config?.access?.rbac||{},requiresLogin:!!e.config?.access?.requiresLogin});return i?w(i,r.servers??[]):c()}catch{return c()}}function w(e,t){const n=x(e),r=Object.keys(n);if(r.length===0)return c();const i=m(e.security,n)[0]??a(r,n)[0],o=P(e,r);return{schemes:n,defaultScheme:i,routes:H(e,n,t),...Object.keys(o).length>0?{descriptions:o}:{}}}function M(e){return[...new Set(Object.values(e).map(C))]}function N(e){return a(Object.keys(e),e)}function U(e,t){const n=new Set;for(const r of e.routes)r.schemes.includes(t)&&n.add(`${r.method} ${r.path}`);return[...n]}function C(e){return e.kind==="apiKey"?e.header:e.kind==="cookie"?"Cookie":"Authorization"}function P(e,t){const n={};for(const r of t){const i=e.components?.securitySchemes?.[r],o=k(i)?i.description:void 0;if(typeof o!="string")continue;const s=o.split(`
2
+ `)[0].trim();s&&(n[r]=s.slice(0,A))}return n}function x(e){const t={};for(const[n,r]of Object.entries(e.components?.securitySchemes??{})){if(!k(r))continue;const i=T(r);i&&(t[n]=i)}return t}function T(e){if(e.type==="oauth2"){const t=e.flows?.clientCredentials;return t?{kind:"oauth2ClientCredentials",tokenUrl:t.tokenUrl,scopes:Object.keys(t.scopes??{})}:{kind:"bearer"}}if(e.type==="openIdConnect")return{kind:"bearer"};if(e.type==="http"&&e.scheme?.toLowerCase()==="bearer")return{kind:"bearer"};if(e.type==="apiKey"&&e.in==="header")return{kind:"apiKey",header:e.name||"X-Api-Key"};if(e.type==="apiKey"&&e.in==="cookie"&&e.name)return{kind:"cookie",name:e.name}}function a(e,t){return[...e].sort((n,r)=>h.indexOf(t[n].kind)-h.indexOf(t[r].kind)||e.indexOf(n)-e.indexOf(r))}function m(e,t){const n=[];for(const r of e??[])for(const i of Object.keys(r))t[i]&&!n.includes(i)&&n.push(i);return n}function H(e,t,n){const r=_(n),i=[];for(const[o,s]of Object.entries(e.paths??{})){if(!s||typeof s!="object")continue;const f=R(r,o);if(f)for(const[p,O]of Object.entries(s)){if(!E.has(p.toLowerCase()))continue;const u=O?.security??e.security;if(u===void 0)continue;const d=m(u,t);d.length===0&&u.length>0||i.push({pattern:f,path:o,method:p.toUpperCase(),schemes:a(d,t)})}}return i}function R(e,t){if(t.startsWith("/"))return new RegExp(`^${e}${y(t)}/?$`)}function y(e){return e.split(j).map(v).join("[^/]+").replace(/(?<!\/)\/+$/,"")}function _(e){const t=[...new Set(e.map(n=>$(n.url)).filter(Boolean).map(y))];return t.length>0?`(?:${t.join("|")})?`:""}function $(e){if(!e)return"";const t=e.replace(/^[a-z][a-z0-9+.-]*:\/\//i,""),n=t.indexOf("/");return n===-1?"":t.slice(n).split(/[?#]/)[0].replace(/(?<!\/)\/+$/,"")}function v(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function k(e){return typeof e=="object"&&e!==null&&"type"in e}export{l as DEFAULT_SCHEME_NAME,M as credentialHeadersOf,U as operationsForScheme,N as orderedSchemesOf,L as resolveUpstreamApiAuth};
@@ -1 +1 @@
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
+ import{secretStore as u}from"../gateway-mcp/secret-store.js";import{rejectCredential as b}from"../gateway-mcp/credential-validation.js";import{buildAllowedHostMatchers as A}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 F}from"../../../tools/notifiers/logger.js";import{clearedPage as I,formPage as x,multiOutcomePage as N,notFoundPage as D,successPage as $}from"./mcp-credentials-page.js";const g={"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'"},B=3e4,M=64*1024,U=/^bearer\s+/i;let P=fetch;function ee(e){P=e}const G=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:g}):m()}if(e.method==="POST"){const n=await e.formData(),o=d(n,"token"),s=d(n,"mode")||"token";return s==="multi"?z(t,n,o):s==="client-credentials"?K(t,n,o):s==="clear"?X(t,o):H(t,n,o)}return new Response("Method Not Allowed",{status:405,headers:{Allow:"GET, POST"}})};async function H(e,i,t){const n=d(i,"secret").trim(),o=await u.peekElicitation(e,t);if(!o)return m();const s=b(o.auth,n);if(s)return h(o,t,s);const l=await u.consumeElicitation(e,t);return l?(await u.setSecret(e,l.subject,l.api,l.scheme,S(l.auth,n)),new Response($(l),{status:200,headers:g})):m()}async function X(e,i){const t=await u.consumeElicitation(e,i);return t?(await u.deleteSecret(e,t.subject,t.api,t.scheme),new Response(I(t),{status:200,headers:g})):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,s=[];for(let r=0;r<o.length;r++){const a=o[r],p=d(i,`secret__${r}`).trim(),E=d(i,`client_id__${r}`).trim(),_=d(i,`client_secret__${r}`).trim();if(a.hasExistingSecret&&d(i,`remove__${r}`)){if(p||E||_)return h(n,t,`${a.name}: enter a new value or tick "Remove the stored credential", not both.`);s.push({kind:"remove",scheme:a.name});continue}if(a.auth.kind==="oauth2ClientCredentials"){if(p){const C=b(a.auth,p);if(C)return h(n,t,`${a.name}: ${C}`);s.push({kind:"set",scheme:a.name,secret:S(a.auth,p)})}else if(E&&_)s.push({kind:"exchange",scheme:a.name,auth:a.auth,clientId:E,clientSecret:_});else if(E||_)return h(n,t,`${a.name}: enter both the client ID and the client secret, or leave both blank.`);continue}if(!p)continue;const y=b(a.auth,p);if(y)return h(n,t,`${a.name}: ${y}`);s.push({kind:"set",scheme:a.name,secret:S(a.auth,p)})}if(s.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 s){if(r.kind!=="exchange"){l.push(r);continue}const a=await R(r.auth,r.clientId,r.clientSecret);if(!a.ok)return T(n,t,a.reason,r.scheme);l.push({kind:"set",scheme:r.scheme,secret:a.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(N(c,f),{status:200,headers:g})}async function K(e,i,t){const n=d(i,"client_id").trim(),o=d(i,"client_secret").trim(),s=await u.peekElicitation(e,t);if(!s||s.auth.kind!=="oauth2ClientCredentials")return m();if(!n||!o)return h(s,t,"Enter both the client ID and the client secret before saving.");const l=await R(s.auth,n,o);if(!l.ok)return T(s,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:g})):m()}async function R(e,i,t){try{const n=A([{url:e.tokenUrl}]),o=j(e.tokenUrl,n),s=new AbortController,l=setTimeout(()=>s.abort(),B);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:s.signal,dispatcher:O()})}finally{clearTimeout(l)}if(!c.ok)return w(`token endpoint returned ${c.status}`);let k;try{k=await v(c,M)}catch{return w("token response exceeded the size cap")}let f;try{f=JSON.parse(k)}catch{return w("token response was not valid JSON")}const r=typeof f=="object"&&f!==null&&"access_token"in f?f.access_token:void 0,a=typeof r=="string"?r.trim():"";return a?b(e,a)?w("token response had an access_token that cannot be sent in a header"):{ok:!0,secret:{header:"Authorization",value:`Bearer ${a}`}}:w("token response had no access_token")}catch(n){return w(n instanceof Error?n.message:String(n))}}function w(e){return{ok:!1,reason:e}}function T(e,i,t,n){return F.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:g})}function m(){return new Response(D(),{status:404,headers:g})}function S(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:J(i)}:{header:"Authorization",value:i}}function J(e){return`Bearer ${e.trim().replace(U,"")}`}var te=G;export{te as default,ee as setCredentialsFetchImpl};
@@ -1 +1 @@
1
- import*as e from"react";import{useCallback as H,useMemo as b,useState as G,useEffect as _}from"react";import{useLocation as T}from"react-router";import{Flex as U}from"@redocly/portal-legacy-ui";import{Button as x}from"@redocly/theme/components/Button/Button";import{AddIcon as I}from"@redocly/theme/icons/AddIcon/AddIcon";import{SubtractIcon as K}from"@redocly/theme/icons/SubtractIcon/SubtractIcon";import{ArrowLeftIcon as D}from"@redocly/theme/icons/ArrowLeftIcon/ArrowLeftIcon";import{getScorecardColorVariable as z,withoutPathPrefix as O}from"@redocly/theme/core/utils";import{Link as P}from"../../../../client/app/Link";import N from"../../../../client/app/CircularProgress";import{isDefined as R}from"../../../../utils/guards/is-defined.js";import{removeLeadingSlash as Z}from"../../../../utils/url/remove-leading-slash.js";import{StatusByLevelWidget as J}from"./StatusByLevelWidget";import{OTHER_TEAMS_LABEL as Q,NON_CONFORMANT as X}from"./utils";import{LevelIndicator as Y}from"./LevelIndicator";import{AppPageWrapper as V,CardValue as v,CardsWrapper as j,CatalogDescription as ee,ModeSwitcherTab as te,ModeSwitcherTabs as re,CardRow as B,ProblemsByStatusCardBody as W,ScoreCardStatusCardBody as F,TableWrap as ne,LevelComplianceCardTitle as le,ContentWithDotWrap as C,ScorecardCard as S,ScorecardCardTitle as L,LinkButton as ae}from"./index.styles";import{ApiLevel as M,InstanceStatus as k,LevelStatus as $,RuleStatus as q}from"./components";import{Grid as A}from"./Grid/Grid";import{useDetailedScorecard as se}from"./useDetailedScorecard";import{Gauge as ce,GaugeValue as oe}from"./Gauge";function Ie(p){const u=T(),{data:d,levelNames:i,hasPublishedAt:n,hasTeam:s,teamLabel:l}=p,f=H((a,o,E)=>{const g=a.getValue(E)?new Date(a.getValue(E)).getTime():0,t=o.getValue(E)?new Date(o.getValue(E)).getTime():0;return g>t?1:-1},[]),m=b(()=>[{id:"title",header:"API",accessorKey:"api.title",size:200,cell:({getValue:a,row:o})=>e.createElement(P,{to:O(u.pathname)+"/apis/"+encodeURIComponent(Z(o.original.api.link))},a())},n?{header:"Published at",accessorKey:"api.publishedAt",sortingFn:f,cell:({getValue:a})=>{const o=a();return o?new Date(o).toLocaleDateString():"-"}}:void 0,s?{header:l,accessorKey:"api.team"}:void 0,{id:"level",header:"Level",accessorKey:"scorecard.scorecardLevel",cell:({getValue:a,row:o})=>e.createElement(M,{level:a(),colorVariable:z(o.original?.scorecard.scorecardLevelIdx,i.length+1)})},...i.map(a=>({id:a,header:`${a}`,accessorKey:"scorecard.levels",cell:({getValue:o,row:E})=>{const g=o(),t=E.original.api.link;return g?.[a]?e.createElement(Y,{key:t,uniqueErrors:g[a].uniqueErrors,uniqueWarnings:g[a].uniqueWarnings,id:t}):null}}))].filter(R),[f,i,u.pathname,n,s,l]);return e.createElement(A,{columns:m,data:d,sortBy:[{id:"title",desc:!1}]})}function Ke(p){const u=T(),{data:d}=p,i=b(()=>[{header:"Rules",accessorKey:"ruleId",cell:({getValue:n})=>e.createElement(P,{to:O(u.pathname)+"/rules/"+encodeURIComponent(n())},n())},{id:"status",header:"Status",accessorKey:"status",size:200,cell:({row:n})=>e.createElement(q,{errors:n.original.errorApisCount,warnings:n.original.warningApisCount})},{header:"Problem APIs",size:200,accessorKey:"apis",cell:({getValue:n})=>n().length,sortDescFirst:!0},{header:"Problem count",size:200,accessorKey:"count",sortDescFirst:!0}],[u.pathname]);return e.createElement(A,{columns:i,data:d,sortBy:[{id:"status",desc:!0}]})}function De(p){const{data:u,levelNames:d,filters:i,setActiveTab:n,teamLabel:s,teamProperty:l}=p,f=b(()=>[{id:"team",header:`API ${s}`,accessorKey:"team",size:200,cell:({getValue:m})=>e.createElement(ae,{onClick:()=>{n("apis"),i.find(a=>a.property===l)?.selectOption(m())}},m())},...[X,...d].map(m=>({id:m,header:`${m}`,accessorFn:a=>a.levels[m]?.apis}))],[i,d,n,s,l]);return e.createElement(A,{columns:f,data:u,sortBy:[{id:"team",desc:!1}],groupHeaderTitle:"APIs by level"})}function ie(p){const{data:u,title:d}=p,i=b(()=>[{id:"expand",maxSize:40,size:40,header:()=>null,enableSorting:!1,cell:({row:n})=>n.getCanExpand()?e.createElement(x,{onClick:n.getToggleExpandedHandler(),variant:"text",size:"medium",icon:n.getIsExpanded()?e.createElement(K,null):e.createElement(I,null)}):null},{header:"Rules",accessorKey:"ruleId",cell:({getValue:n,row:s})=>s.getCanExpand()?e.createElement("span",{onClick:s.getToggleExpandedHandler()},n()):null},{header:"Status",accessorKey:"status",size:200,cell:({getValue:n})=>{const s=n();return e.createElement(q,{errors:s?.errorsCount,warnings:s?.warningsCount})},enableSorting:!1},{header:"Problem count",accessorKey:"problemsCount",size:200,sortDescFirst:!0}],[]);return e.createElement(A,{columns:i,data:u,sortBy:[{id:"ruleId",desc:!1}],empty:e.createElement(e.Fragment,null,"\u2705 This API is compliant with ",e.createElement("strong",null,d)," level rules"),subRowKey:"problems"})}function ze(p){const{catalogSlug:u,slug:d,data:i,hasTeam:n,hasPublishedAt:s,teamLabel:l}=p,{apis:f,apisCount:m,warningApisCount:a,errorApisCount:o,levels:E,count:g}=i,t=f.length===0,h=t?void 0:{id:"expand",maxSize:40,size:40,header:()=>null,enableSorting:!1,cell:({row:r})=>r.getCanExpand()?e.createElement(x,{onClick:r.getToggleExpandedHandler(),variant:"text",size:"medium",icon:r.getIsExpanded()?e.createElement(K,null):e.createElement(I,null)}):null},w=b(()=>[h,{header:"API",accessorKey:"title"},s?{header:"Published at",accessorKey:"publishedAt",cell:({getValue:r})=>{const c=r();return c?new Date(c).toLocaleDateString():"-"}}:void 0,n?{header:l,accessorKey:"team",cell:({getValue:r})=>r()||Q}:void 0,{header:"Problem count",size:200,accessorKey:"count",cell:({getValue:r})=>r()||0,sortDescFirst:!0}].filter(R),[n,s,l,t]),y=b(()=>Object.entries(i.teams).sort((r,c)=>c[1]-r[1]),[i.teams]);return e.createElement(V,null,e.createElement(x,{icon:e.createElement(D,null),iconPosition:"left",variant:"ghost",size:"large",to:`${u}scorecard`},"Back to dashboard"),e.createElement("h1",null,"Rule ",d),e.createElement(j,null,e.createElement(S,null,e.createElement(L,null,"Rule status"),e.createElement(F,null,e.createElement("div",null,e.createElement(v,null,m),e.createElement("br",null),"APIs"),e.createElement("div",null,e.createElement(C,null,e.createElement(v,{style:{textAlign:"right"}},m-a-o),e.createElement(k,{severity:"Pass"})),e.createElement(C,null,e.createElement(v,{style:{textAlign:"right"}},a),e.createElement(k,{severity:"Warn"})),e.createElement(C,null,e.createElement(v,{style:{textAlign:"right"}},o),e.createElement(k,{severity:"Error"}))))),e.createElement(S,null,e.createElement(L,null,"Problems by level"),e.createElement(W,null,Object.entries(E).map(([r,c])=>e.createElement(B,{key:r},e.createElement("span",null,r),e.createElement(ce,{chunks:[{share:c.errors/g*100,colorVariable:"--scorecard-color-error"},{share:c.warnings/g*100,colorVariable:"--scorecard-color-warning"}]}),e.createElement(oe,null,c.errors+c.warnings))))),e.createElement(S,null,e.createElement(L,null,`Problems by ${l}`),e.createElement(W,null,y.slice(0,4).map(([r,c])=>e.createElement(B,{key:r},e.createElement("span",{style:{flex:1,textAlign:"left"}},r),e.createElement(v,null,c)))))),e.createElement(A,{columns:w,data:f,sortBy:[{id:"title",desc:!1}],subRowKey:"problems",slug:d}))}function Oe(p){const{slug:u,api:d,teamLabel:i,catalogSlug:n}=p,{api:s}=d||{},{scorecard:l,isLoading:f}=se(`/${u}`),m=l?.levels||{},[a,o]=G(Object.keys(m)[0]),E=b(()=>s?.publishedAt instanceof Date?s?.publishedAt.toLocaleDateString():s?.publishedAt,[s]);_(()=>{if(l){const t=l.levels||{};o(Object.keys(t)[0])}},[l]);const g=b(()=>{if(!l?.levels)return{};const t={};for(const h of Object.keys(l.levels)){const w=l.levels[h].problems.reduce((y,r)=>(y[r.ruleId]||(y[r.ruleId]=[]),y[r.ruleId].push(r),y),{});t[h]=Object.entries(w).map(([y,r])=>({ruleId:y,problems:[...r.filter(c=>c.severity==="error"),...r.filter(c=>c.severity==="warn")],problemsCount:r.length,status:{errorsCount:r.filter(c=>c.severity==="error").length,warningsCount:r.filter(c=>c.severity==="warn").length}}))}return t},[l?.levels]);return f?e.createElement(U,{justifyContent:"center",mt:"2em"},e.createElement(N,null)):l?e.createElement(V,null,e.createElement(x,{icon:e.createElement(D,null),iconPosition:"left",variant:"ghost",size:"large",to:`${n}scorecard`},"Back to dashboard"),e.createElement("h1",null,s?.title||u),e.createElement(ee,null,E&&e.createElement(e.Fragment,null,"Published at: ",new Date().toLocaleString(void 0,{timeZoneName:"short"}),e.createElement("br",null)),e.createElement(P,{to:u,external:!0},"Documentation")),e.createElement(j,null,e.createElement(S,null,e.createElement(le,null,e.createElement("span",null,"Scorecard level"),e.createElement(M,{level:l.scorecardLevel,colorVariable:z(l.scorecardLevelIdx,Object.keys(m).length)})),e.createElement(F,null,e.createElement("div",null,"Compliance by level"),e.createElement("div",null,Object.entries(l.levels).map(([t,h])=>e.createElement(C,{key:t},e.createElement(v,{style:{width:"100px",textAlign:"right"}},t),e.createElement($,{errors:h.errors,warnings:h.warnings})))))),e.createElement(J,{title:"Rules compliance",levels:Object.entries(l.levels).map(([t,h])=>({errors:h.uniqueErrors,warnings:h.uniqueWarnings,total:p.rulesPerLevel[t].length,name:t}))}),e.createElement(S,null,e.createElement(L,null,i),e.createElement(v,{style:{fontWeight:"normal"}},s?.team))),e.createElement(re,{style:{justifyContent:"flex-start"},"data-component-name":"Scorecard/ModeSwitcherTabs"},Object.keys(l.levels).map(t=>e.createElement(te,{"data-component-name":"Scorecard/ModeSwitcherTab",key:t,isActive:a===t,onClick:()=>o(t)},e.createElement(C,null,e.createElement("span",null,t)," ",e.createElement($,{errors:l.levels[t].errors,warnings:l.levels[t].warnings}))))),Object.keys(m).map(t=>e.createElement(ne,{key:t,isActive:a===t},e.createElement(ie,{title:t,data:g[t]})))):e.createElement("div",null,"Scorecard not found")}export{Ie as ApiListView,Oe as ApiPage,ie as ApiView,ze as RuleView,Ke as RulesView,De as TeamsView};
1
+ import*as e from"react";import{useCallback as H,useMemo as b,useState as G,useEffect as _}from"react";import{useLocation as T}from"react-router";import{Flex as U}from"@redocly/portal-legacy-ui";import{Button as x}from"@redocly/theme/components/Button/Button";import{AddIcon as I}from"@redocly/theme/icons/AddIcon/AddIcon";import{SubtractIcon as K}from"@redocly/theme/icons/SubtractIcon/SubtractIcon";import{ArrowLeftIcon as D}from"@redocly/theme/icons/ArrowLeftIcon/ArrowLeftIcon";import{getScorecardColorVariable as z,withoutPathPrefix as O}from"@redocly/theme/core/utils";import{Link as w}from"../../../../client/app/Link";import J from"../../../../client/app/CircularProgress";import{isDefined as R}from"../../../../utils/guards/is-defined.js";import{removeLeadingSlash as N}from"../../../../utils/url/remove-leading-slash.js";import{StatusByLevelWidget as Q}from"./StatusByLevelWidget";import{OTHER_TEAMS_LABEL as X,NON_CONFORMANT as Y}from"./utils";import{LevelIndicator as Z}from"./LevelIndicator";import{AppPageWrapper as V,CardValue as v,CardsWrapper as j,CatalogDescription as ee,ModeSwitcherTab as te,ModeSwitcherTabs as re,CardRow as B,ProblemsByStatusCardBody as W,ScoreCardStatusCardBody as F,TableWrap as ne,LevelComplianceCardTitle as le,ContentWithDotWrap as C,ScorecardCard as A,ScorecardCardTitle as L,LinkButton as ae}from"./index.styles";import{ApiLevel as M,InstanceStatus as k,LevelStatus as $,RuleStatus as q}from"./components";import{Grid as S}from"./Grid/Grid";import{useDetailedScorecard as se}from"./useDetailedScorecard";import{Gauge as ce,GaugeValue as oe}from"./Gauge";function Ie(p){const u=T(),{data:d,levelNames:i,hasPublishedAt:n,hasTeam:s,teamLabel:l}=p,f=H((a,o,E)=>{const g=a.getValue(E)?new Date(a.getValue(E)).getTime():0,t=o.getValue(E)?new Date(o.getValue(E)).getTime():0;return g>t?1:-1},[]),m=b(()=>[{id:"title",header:"API",accessorKey:"api.title",size:200,cell:({getValue:a,row:o})=>e.createElement(w,{to:O(u.pathname)+"/apis/"+encodeURIComponent(N(o.original.api.link))},a())},n?{header:"Published at",accessorKey:"api.publishedAt",sortingFn:f,cell:({getValue:a})=>{const o=a();return o?new Date(o).toLocaleDateString():"-"}}:void 0,s?{header:l,accessorKey:"api.team"}:void 0,{id:"level",header:"Level",accessorKey:"scorecard.scorecardLevel",cell:({getValue:a,row:o})=>e.createElement(M,{level:a(),colorVariable:z(o.original?.scorecard.scorecardLevelIdx,i.length+1)})},...i.map(a=>({id:a,header:`${a}`,accessorKey:"scorecard.levels",cell:({getValue:o,row:E})=>{const g=o(),t=E.original.api.link;return g?.[a]?e.createElement(Z,{key:t,uniqueErrors:g[a].uniqueErrors,uniqueWarnings:g[a].uniqueWarnings,id:t}):null}}))].filter(R),[f,i,u.pathname,n,s,l]);return e.createElement(S,{columns:m,data:d,sortBy:[{id:"title",desc:!1}]})}function Ke(p){const u=T(),{data:d}=p,i=b(()=>[{header:"Rules",accessorKey:"ruleId",cell:({getValue:n})=>e.createElement(w,{to:O(u.pathname)+"/rules/"+encodeURIComponent(n())},n())},{id:"status",header:"Status",accessorKey:"status",size:200,cell:({row:n})=>e.createElement(q,{errors:n.original.errorApisCount,warnings:n.original.warningApisCount})},{header:"Problem APIs",size:200,accessorKey:"apis",cell:({getValue:n})=>n().length,sortDescFirst:!0},{header:"Problem count",size:200,accessorKey:"count",sortDescFirst:!0}],[u.pathname]);return e.createElement(S,{columns:i,data:d,sortBy:[{id:"status",desc:!0}]})}function De(p){const{data:u,levelNames:d,filters:i,setActiveTab:n,teamLabel:s,teamProperty:l}=p,f=b(()=>[{id:"team",header:`API ${s}`,accessorKey:"team",size:200,cell:({getValue:m})=>e.createElement(ae,{onClick:()=>{n("apis"),i.find(a=>a.property===l)?.selectOption(m())}},m())},...[Y,...d].map(m=>({id:m,header:`${m}`,accessorFn:a=>a.levels[m]?.apis}))],[i,d,n,s,l]);return e.createElement(S,{columns:f,data:u,sortBy:[{id:"team",desc:!1}],groupHeaderTitle:"APIs by level"})}function ie(p){const{data:u,title:d}=p,i=b(()=>[{id:"expand",maxSize:40,size:40,header:()=>null,enableSorting:!1,cell:({row:n})=>n.getCanExpand()?e.createElement(x,{onClick:n.getToggleExpandedHandler(),variant:"text",size:"medium",icon:n.getIsExpanded()?e.createElement(K,null):e.createElement(I,null)}):null},{header:"Rules",accessorKey:"ruleId",cell:({getValue:n,row:s})=>s.getCanExpand()?e.createElement("span",{onClick:s.getToggleExpandedHandler()},n()):null},{header:"Status",accessorKey:"status",size:200,cell:({getValue:n})=>{const s=n();return e.createElement(q,{errors:s?.errorsCount,warnings:s?.warningsCount})},enableSorting:!1},{header:"Problem count",accessorKey:"problemsCount",size:200,sortDescFirst:!0}],[]);return e.createElement(S,{columns:i,data:u,sortBy:[{id:"ruleId",desc:!1}],empty:e.createElement(e.Fragment,null,"\u2705 This API is compliant with ",e.createElement("strong",null,d)," level rules"),subRowKey:"problems"})}function ze(p){const{catalogSlug:u,slug:d,data:i,hasTeam:n,hasPublishedAt:s,teamLabel:l}=p,{apis:f,apisCount:m,warningApisCount:a,errorApisCount:o,levels:E,count:g}=i,t=f.length===0,h=t?void 0:{id:"expand",maxSize:40,size:40,header:()=>null,enableSorting:!1,cell:({row:r})=>r.getCanExpand()?e.createElement(x,{onClick:r.getToggleExpandedHandler(),variant:"text",size:"medium",icon:r.getIsExpanded()?e.createElement(K,null):e.createElement(I,null)}):null},P=b(()=>[h,{header:"API",accessorKey:"title"},s?{header:"Published at",accessorKey:"publishedAt",cell:({getValue:r})=>{const c=r();return c?new Date(c).toLocaleDateString():"-"}}:void 0,n?{header:l,accessorKey:"team",cell:({getValue:r})=>r()||X}:void 0,{header:"Problem count",size:200,accessorKey:"count",cell:({getValue:r})=>r()||0,sortDescFirst:!0}].filter(R),[n,s,l,t]),y=b(()=>Object.entries(i.teams).sort((r,c)=>c[1]-r[1]),[i.teams]);return e.createElement(V,null,e.createElement(x,{icon:e.createElement(D,null),iconPosition:"left",variant:"ghost",size:"large",to:`${u}scorecard`},"Back to dashboard"),e.createElement("h1",null,"Rule ",d),e.createElement(j,null,e.createElement(A,null,e.createElement(L,null,"Rule status"),e.createElement(F,null,e.createElement("div",null,e.createElement(v,null,m),e.createElement("br",null),"APIs"),e.createElement("div",null,e.createElement(C,null,e.createElement(v,{style:{textAlign:"right"}},m-a-o),e.createElement(k,{severity:"Pass"})),e.createElement(C,null,e.createElement(v,{style:{textAlign:"right"}},a),e.createElement(k,{severity:"Warn"})),e.createElement(C,null,e.createElement(v,{style:{textAlign:"right"}},o),e.createElement(k,{severity:"Error"}))))),e.createElement(A,null,e.createElement(L,null,"Problems by level"),e.createElement(W,null,Object.entries(E).map(([r,c])=>e.createElement(B,{key:r},e.createElement("span",null,r),e.createElement(ce,{chunks:[{share:c.errors/g*100,colorVariable:"--scorecard-color-error"},{share:c.warnings/g*100,colorVariable:"--scorecard-color-warning"}]}),e.createElement(oe,null,c.errors+c.warnings))))),e.createElement(A,null,e.createElement(L,null,`Problems by ${l}`),e.createElement(W,null,y.slice(0,4).map(([r,c])=>e.createElement(B,{key:r},e.createElement("span",{style:{flex:1,textAlign:"left"}},r),e.createElement(v,null,c)))))),e.createElement(S,{columns:P,data:f,sortBy:[{id:"title",desc:!1}],subRowKey:"problems",slug:d}))}function Oe(p){const{slug:u,api:d,teamLabel:i,catalogSlug:n}=p,{api:s}=d||{},{scorecard:l,isLoading:f}=se(`/${u}`),m=l?.levels||{},[a,o]=G(Object.keys(m)[0]),E=b(()=>s?.publishedAt instanceof Date?s?.publishedAt.toLocaleDateString():s?.publishedAt,[s]);_(()=>{if(l){const t=l.levels||{};o(Object.keys(t)[0])}},[l]);const g=b(()=>{if(!l?.levels)return{};const t={};for(const h of Object.keys(l.levels)){const P=l.levels[h].problems.reduce((y,r)=>(y[r.ruleId]||(y[r.ruleId]=[]),y[r.ruleId].push(r),y),{});t[h]=Object.entries(P).map(([y,r])=>({ruleId:y,problems:[...r.filter(c=>c.severity==="error"),...r.filter(c=>c.severity==="warn")],problemsCount:r.length,status:{errorsCount:r.filter(c=>c.severity==="error").length,warningsCount:r.filter(c=>c.severity==="warn").length}}))}return t},[l?.levels]);return f?e.createElement(U,{justifyContent:"center",mt:"2em"},e.createElement(J,null)):l?e.createElement(V,null,e.createElement(x,{icon:e.createElement(D,null),iconPosition:"left",variant:"ghost",size:"large",to:`${n}scorecard`},"Back to dashboard"),e.createElement("h1",null,s?.title||u),e.createElement(ee,null,E&&e.createElement(e.Fragment,null,"Published at: ",E,e.createElement("br",null)),e.createElement(w,{to:u,external:!0},"Documentation")),e.createElement(j,null,e.createElement(A,null,e.createElement(le,null,e.createElement("span",null,"Scorecard level"),e.createElement(M,{level:l.scorecardLevel,colorVariable:z(l.scorecardLevelIdx,Object.keys(m).length)})),e.createElement(F,null,e.createElement("div",null,"Compliance by level"),e.createElement("div",null,Object.entries(l.levels).map(([t,h])=>e.createElement(C,{key:t},e.createElement(v,{style:{width:"100px",textAlign:"right"}},t),e.createElement($,{errors:h.errors,warnings:h.warnings})))))),e.createElement(Q,{title:"Rules compliance",levels:Object.entries(l.levels).map(([t,h])=>({errors:h.uniqueErrors,warnings:h.uniqueWarnings,total:p.rulesPerLevel[t].length,name:t}))}),e.createElement(A,null,e.createElement(L,null,i),e.createElement(v,{style:{fontWeight:"normal"}},s?.team))),e.createElement(re,{style:{justifyContent:"flex-start"},"data-component-name":"Scorecard/ModeSwitcherTabs"},Object.keys(l.levels).map(t=>e.createElement(te,{"data-component-name":"Scorecard/ModeSwitcherTab",key:t,isActive:a===t,onClick:()=>o(t)},e.createElement(C,null,e.createElement("span",null,t)," ",e.createElement($,{errors:l.levels[t].errors,warnings:l.levels[t].warnings}))))),Object.keys(m).map(t=>e.createElement(ne,{key:t,isActive:a===t},e.createElement(ie,{title:t,data:g[t]})))):e.createElement("div",null,"Scorecard not found")}export{Ie as ApiListView,Oe as ApiPage,ie as ApiView,ze as RuleView,Ke as RulesView,De as TeamsView};
@@ -1 +1 @@
1
- import{REDOCLY_ROUTE_RBAC as p,REDOCLY_TEAMS_RBAC as c}from"@redocly/config";import{collectPropValueDeep as v}from"../../../utils/tree/collect-prop-value-deep.js";import{isDefined as T}from"../../../utils/guards/is-defined.js";import{resolveItem as $,resolveItems as w}from"../nav-utils.js";function F(n){return Array.isArray(n)&&n.some(b)}function I(n,e){const a=e.normalize("NFKD").replace(/[\u0300-\u036f]/g,"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"");return`${n}-${a||"tab"}`}function x(n){const e=[],a=n.filter(o=>!b(o));a.length&&e.push(`when a sidebar uses tabs, every top-level entry must be a tab; found ${a.length} other entr${a.length===1?"y":"ies"}: ${JSON.stringify(a)}`);for(const{tab:o,page:i,menu:r,items:f,$ref:t,directory:l,group:s,href:m}of n.filter(b)){const u=`tab "${o}"`;if(r!==void 0&&!Array.isArray(r)){e.push(`${u} "menu" must be a list of entries`);continue}i?.includes("#")&&e.push(`${u} page must not contain a heading anchor`),r&&f?e.push(`${u} cannot have both "menu" and "items"`):r&&i?e.push(`${u} has "menu" and cannot also have "page"`):!r&&!i&&e.push(`${u} needs "page" or "menu"`),(t||l||s||m)&&e.push(`${u} accepts only "page", "items", "menu" and "rbac"; move "$ref"/"directory"/"group"/"href" into "items"`),r?.length===0&&e.push(`${u} has an empty "menu"`),r?.forEach((d,y)=>{const h=`menu entry ${y+1} of ${u}`;d?.page||e.push(`${h} needs "page"`),d?.page?.includes("#")&&e.push(`${h} page must not contain a heading anchor`)});const g=[...r??[],...f??[]];v(g,"tab").length&&e.push(`${u} contains a nested "tab"; tabs are only allowed at the top level`),v(g,"menu").length&&e.push(`${u} contains a nested "menu"; "menu" is only allowed directly on a tab`)}return e}async function C(n,e,a,o,i){const r=[];for(const[f,t]of n.filter(b).entries()){const l=I(f,t.tab);if(t.menu){const g=(await Promise.all(t.menu.map((y,h)=>A(y,l,h,e,a,o,i)))).filter(T),d=t.icon?S(await $({group:t.tab,icon:t.icon,items:[]},e,a,o,i)):void 0;r.push({type:"tab",tabId:l,label:t.tab,labelTranslationKey:t.tabTranslationKey,menu:g,icon:d?.icon,srcSet:d?.srcSet,[c]:t.rbac});continue}const s=S(await $({group:t.tab,rbac:t.rbac,page:t.page,icon:t.icon,items:[]},e,a,o,i)),m=s?.routeSlug?a.getRouteBySlug(s.routeSlug):void 0,u=await w(t.items,e,a,o,i)??[];r.push({type:"tab",tabId:l,label:t.tab,labelTranslationKey:t.tabTranslationKey,link:s?.link,routeSlug:s?.routeSlug,fsPath:s?.fsPath,icon:s?.icon,srcSet:s?.srcSet,items:[...s?.items??[],...u],...k(m?.versions,s),[c]:t.rbac??s?.[c],[p]:s?.[p]})}return r}function M(n){return n.flatMap(e=>e.menu?e.menu.flatMap(a=>R(`${e.label} > ${a.label}`,a.routeSlug,a.items)):R(e.label,e.routeSlug,e.items??[]))}function N(n){const e=new Map;for(const{routeSlug:a,owner:o}of n){const i=a.split("#")[0],r=e.get(i)??new Set;r.add(o),e.set(i,r)}return[...e.entries()].filter(([,a])=>a.size>1).map(([a,o])=>({routeSlug:a,owners:[...o]}))}function O(n){return n.map(e=>({type:"group",label:e.label,labelTranslationKey:e.labelTranslationKey,link:e.link,routeSlug:e.routeSlug,fsPath:e.fsPath,version:e.version,isDefault:e.isDefault,versionFolderId:e.versionFolderId,items:e.menu??e.items??[],[c]:e[c],[p]:e[p]}))}function b(n){return typeof n?.tab=="string"}async function A(n,e,a,o,i,r,f){const t=S(await $({...n,group:n.label||n.page,items:[]},o,i,r,f));if(!t?.link)return;const l=t.routeSlug?i.getRouteBySlug(t.routeSlug):void 0,s=n.label||await l?.getNavText?.()||t.link,m=await w(n.items,o,i,r,f)??[],u=[...t.items??[],...m];return{type:"link",tabId:`${e}/${I(a,s)}`,label:s,labelTranslationKey:n.labelTranslationKey,link:t.link,routeSlug:t.routeSlug,fsPath:t.fsPath,metadata:t.metadata,icon:t.icon,srcSet:t.srcSet,badges:t.badges,items:u,...k(l?.versions,t),[c]:n.rbac??l?.[c]??t[c],[p]:t[p]}}function S(n){const e=Array.isArray(n)?n[0]:n;return e?.type==="group"?e:void 0}function k(n,e){const a=n?.find(o=>o.active);return a?{version:a.version,isDefault:a.default,versionFolderId:a.folderId}:e?.version===void 0?{}:{version:e.version,isDefault:e.isDefault,versionFolderId:e.versionFolderId}}function R(n,e,a){return[e,...v(a,"routeSlug")].filter(T).map(o=>({routeSlug:o,owner:n,items:a}))}export{M as collectTabRoutes,N as findDuplicateRoutes,F as hasTabs,I as makeTabId,C as resolveTabs,O as tabsAsGroups,x as validateTabs};
1
+ import{REDOCLY_ROUTE_RBAC as p,REDOCLY_TEAMS_RBAC as c}from"@redocly/config";import{collectPropValueDeep as v}from"../../../utils/tree/collect-prop-value-deep.js";import{isDefined as T}from"../../../utils/guards/is-defined.js";import{resolveItem as $,resolveItems as w}from"../nav-utils.js";function F(n){return Array.isArray(n)&&n.some(b)}function I(n,e){const a=e.normalize("NFKD").replace(/[\u0300-\u036f]/g,"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|(?<!-)-+$/g,"");return`${n}-${a||"tab"}`}function x(n){const e=[],a=n.filter(o=>!b(o));a.length&&e.push(`when a sidebar uses tabs, every top-level entry must be a tab; found ${a.length} other entr${a.length===1?"y":"ies"}: ${JSON.stringify(a)}`);for(const{tab:o,page:i,menu:r,items:f,$ref:t,directory:l,group:s,href:m}of n.filter(b)){const u=`tab "${o}"`;if(r!==void 0&&!Array.isArray(r)){e.push(`${u} "menu" must be a list of entries`);continue}i?.includes("#")&&e.push(`${u} page must not contain a heading anchor`),r&&f?e.push(`${u} cannot have both "menu" and "items"`):r&&i?e.push(`${u} has "menu" and cannot also have "page"`):!r&&!i&&e.push(`${u} needs "page" or "menu"`),(t||l||s||m)&&e.push(`${u} accepts only "page", "items", "menu" and "rbac"; move "$ref"/"directory"/"group"/"href" into "items"`),r?.length===0&&e.push(`${u} has an empty "menu"`),r?.forEach((d,y)=>{const h=`menu entry ${y+1} of ${u}`;d?.page||e.push(`${h} needs "page"`),d?.page?.includes("#")&&e.push(`${h} page must not contain a heading anchor`)});const g=[...r??[],...f??[]];v(g,"tab").length&&e.push(`${u} contains a nested "tab"; tabs are only allowed at the top level`),v(g,"menu").length&&e.push(`${u} contains a nested "menu"; "menu" is only allowed directly on a tab`)}return e}async function C(n,e,a,o,i){const r=[];for(const[f,t]of n.filter(b).entries()){const l=I(f,t.tab);if(t.menu){const g=(await Promise.all(t.menu.map((y,h)=>A(y,l,h,e,a,o,i)))).filter(T),d=t.icon?S(await $({group:t.tab,icon:t.icon,items:[]},e,a,o,i)):void 0;r.push({type:"tab",tabId:l,label:t.tab,labelTranslationKey:t.tabTranslationKey,menu:g,icon:d?.icon,srcSet:d?.srcSet,[c]:t.rbac});continue}const s=S(await $({group:t.tab,rbac:t.rbac,page:t.page,icon:t.icon,items:[]},e,a,o,i)),m=s?.routeSlug?a.getRouteBySlug(s.routeSlug):void 0,u=await w(t.items,e,a,o,i)??[];r.push({type:"tab",tabId:l,label:t.tab,labelTranslationKey:t.tabTranslationKey,link:s?.link,routeSlug:s?.routeSlug,fsPath:s?.fsPath,icon:s?.icon,srcSet:s?.srcSet,items:[...s?.items??[],...u],...k(m?.versions,s),[c]:t.rbac??s?.[c],[p]:s?.[p]})}return r}function M(n){return n.flatMap(e=>e.menu?e.menu.flatMap(a=>R(`${e.label} > ${a.label}`,a.routeSlug,a.items)):R(e.label,e.routeSlug,e.items??[]))}function N(n){const e=new Map;for(const{routeSlug:a,owner:o}of n){const i=a.split("#")[0],r=e.get(i)??new Set;r.add(o),e.set(i,r)}return[...e.entries()].filter(([,a])=>a.size>1).map(([a,o])=>({routeSlug:a,owners:[...o]}))}function O(n){return n.map(e=>({type:"group",label:e.label,labelTranslationKey:e.labelTranslationKey,link:e.link,routeSlug:e.routeSlug,fsPath:e.fsPath,version:e.version,isDefault:e.isDefault,versionFolderId:e.versionFolderId,items:e.menu??e.items??[],[c]:e[c],[p]:e[p]}))}function b(n){return typeof n?.tab=="string"}async function A(n,e,a,o,i,r,f){const t=S(await $({...n,group:n.label||n.page,items:[]},o,i,r,f));if(!t?.link)return;const l=t.routeSlug?i.getRouteBySlug(t.routeSlug):void 0,s=n.label||await l?.getNavText?.()||t.link,m=await w(n.items,o,i,r,f)??[],u=[...t.items??[],...m];return{type:"link",tabId:`${e}/${I(a,s)}`,label:s,labelTranslationKey:n.labelTranslationKey,link:t.link,routeSlug:t.routeSlug,fsPath:t.fsPath,metadata:t.metadata,icon:t.icon,srcSet:t.srcSet,badges:t.badges,items:u,...k(l?.versions,t),[c]:n.rbac??l?.[c]??t[c],[p]:t[p]}}function S(n){const e=Array.isArray(n)?n[0]:n;return e?.type==="group"?e:void 0}function k(n,e){const a=n?.find(o=>o.active);return a?{version:a.version,isDefault:a.default,versionFolderId:a.folderId}:e?.version===void 0?{}:{version:e.version,isDefault:e.isDefault,versionFolderId:e.versionFolderId}}function R(n,e,a){return[e,...v(a,"routeSlug")].filter(T).map(o=>({routeSlug:o,owner:n,items:a}))}export{M as collectTabRoutes,N as findDuplicateRoutes,F as hasTabs,I as makeTabId,C as resolveTabs,O as tabsAsGroups,x as validateTabs};
@@ -1,2 +1,2 @@
1
- import L from"node:dns";import{isIP as H}from"node:net";import{Agent as $}from"undici";import{withPathPrefix as M}from"@redocly/theme/core/utils";import{ServerRoutes as D}from"../../../constants/api.js";import{envConfig as y}from"../../config/env-config.js";import{getRequestOrigin as b}from"../utils/get-request-origin.js";import{SsrfBlockedError as N}from"../utils/ssrf-blocklist.js";import{createSsrfSafeLookup as W}from"../utils/ssrf-safe-lookup.js";const z=new Set(["connection","keep-alive","proxy-authenticate","proxy-connection","proxy-authorization","te","trailer","transfer-encoding","upgrade","host"]),U=new Set(["cookie","cookie2","accept-encoding"]),Y=new Set(["set-cookie","set-cookie2","content-encoding","content-length"]),S="x-redocly-proxy-streaming",x="x-http-method-override",P="x-redocly-cookie",v="Requests to private network addresses are not allowed",F=new $({connect:{lookup:W()}});function ue(n,e=M(D.CORS_PROXY)){return async t=>{const r=new URL(t.req.url).pathname;if(r===e||r===`${e}/`)return t.text(`Realm CORS proxy endpoint.
2
- Usage: ${e}/https://api.example.com/path`);const s=ne(t.req.url,e);if(!s)return t.text("Invalid proxied URL",400);const a=n?.getConfig().corsProxy?.allowedTargets;if(a&&a.length>0&&!a.some(f=>ee(s,f)))return t.text("Target URL is not in the allowed list for CORS proxy",403);const m=b(t),g=s.origin===m,w=s.pathname===e||s.pathname.startsWith(`${e}/`);if(g&&!w)return new Response("Please use a direct request",{status:308,headers:{Location:s.toString(),Vary:"origin","Cache-Control":"private"}});const u=await j(s.hostname),p=!y.isDevelopMode||y.isReunite;if(p&&u&&J(u))return t.text(v,403);const i=new Headers,A=T(t.req.raw.headers);for(const[o,f]of t.req.raw.headers)A.has(o.toLowerCase())||U.has(o.toLowerCase())||i.append(o,f);const R=i.get(P);if(R){const o=t.req.raw.headers.get("cookie")||"";i.set("cookie",o?`${o}; ${R}`:R),i.delete(P)}const O=t.req.raw.headers.get("origin")||"";X(O)&&i.delete("origin");let l=t.req.method;const E=i.get(x);E&&(l=E.toUpperCase(),i.delete(x));const h={method:l,headers:i,redirect:"manual"};p&&(h.dispatcher=F),l!=="GET"&&l!=="HEAD"&&t.req.raw.body&&(h.body=t.req.raw.body,h.duplex="half");let c;try{c=await fetch(s,h)}catch(o){if(o instanceof Error&&o.cause instanceof N)return t.text(v,403);const f=o instanceof Error?o.message:"unknown error",q=o instanceof Error&&o.cause instanceof Error?`: ${o.cause.message}`:"";return t.text(`Failed to proxy request: ${f}${q}`,502)}const _=c.headers.get("content-type")||"";if(te(_)&&t.req.raw.headers.get("sec-fetch-mode")==="navigate")return t.text("Direct browser navigation to proxied HTML or JavaScript content is not allowed",403);const d=new Headers(c.headers),k=T(c.headers);for(const o of k)d.delete(o);for(const o of Y)d.delete(o);return d.set(S,"1"),d.set("x-content-type-options","nosniff"),d.has("content-type")||d.set("content-type","text/plain"),new Response(c.body,{status:c.status,statusText:c.statusText,headers:d})}}function B(n){try{return decodeURIComponent(n)}catch{return n}}function G(n){return n.replace(/^(https?):\/(?!\/)/i,"$1://")}function V(n,e){return e?n.includes("?")?e==="?"?n:`${n.endsWith("?")||n.endsWith("&")?n:`${n}&`}${e.slice(1)}`:`${n}${e}`:n}function T(n){const e=new Set(z),t=n.get("connection");if(!t)return e;for(const r of t.split(",")){const s=r.trim().toLowerCase();s&&e.add(s)}return e}function X(n){const e=n.toLowerCase();return e.includes(".redocly.app")||e.includes("localhost")}async function j(n){const e=C(n);if(H(e))return e;try{const{address:t}=await L.promises.lookup(e);return t}catch{return null}}function J(n){const e=C(n).toLowerCase();if(!H(e))return!0;const t=K(e);return t?I(t):e.includes(":")?Q(e):I(e)}function C(n){return n.startsWith("[")&&n.endsWith("]")?n.slice(1,-1):n}function K(n){const e=n.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);if(e){const r=parseInt(e[1],16),s=parseInt(e[2],16);return[r>>8&255,r&255,s>>8&255,s&255].join(".")}return n.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i)?.[1]??null}function I(n){const e=n.split(".").map(Number);if(e.length!==4||e.some(s=>isNaN(s)))return!0;const[t,r]=e;return t===0||t===10||t===127||t===172&&r>=16&&r<=31||t===192&&r===168||t===169&&r===254||t===100&&r>=64&&r<=127}function Q(n){const e=Z(n);return n==="::1"||n==="::"||e!==null&&(e&65024)===64512||e!==null&&(e&65472)===65152}function Z(n){const[e]=n.split(":");if(!e)return null;const t=parseInt(e,16);return Number.isNaN(t)?null:t}function ee(n,e){let t;try{t=new URL(e)}catch{return!1}if(n.origin!==t.origin)return!1;const r=t.pathname.endsWith("/")?t.pathname:`${t.pathname}/`;return n.pathname.startsWith(r)||n.pathname===t.pathname}function te(n){const e=n.toLowerCase();return e.includes("text/html")||e.includes("javascript")||e.includes("application/xhtml")||e.includes("application/xml")||e.includes("image/svg")}function ne(n,e){const t=new URL(n),r=t.pathname===e,s=t.pathname.startsWith(`${e}/`);if(!r&&!s)return null;const a=t.pathname.slice(e.length).replace(/^\/+/,"");if(!a)return null;const m=[a,B(a)];for(const g of m){const w=G(g),u=V(w,t.search);try{const p=new URL(u);if(p.protocol==="http:"||p.protocol==="https:")return p}catch{continue}}return null}const le=S;export{le as CORS_PROXY_STREAM_HEADER,ue as corsProxyHandler,J as isPrivateIp,ne as resolveCorsProxyTarget};
1
+ import q from"node:dns";import{isIP as H}from"node:net";import{Agent as L}from"undici";import{withPathPrefix as $}from"@redocly/theme/core/utils";import{ServerRoutes as D}from"../../../constants/api.js";import{envConfig as y}from"../../config/env-config.js";import{getRequestOrigin as M}from"../utils/get-request-origin.js";import{SsrfBlockedError as N}from"../utils/ssrf-blocklist.js";import{createSsrfSafeLookup as b}from"../utils/ssrf-safe-lookup.js";const U=new Set(["connection","keep-alive","proxy-authenticate","proxy-connection","proxy-authorization","te","trailer","transfer-encoding","upgrade","host"]),W=new Set(["cookie","cookie2","accept-encoding"]),z=new Set(["set-cookie","set-cookie2","content-encoding","content-length"]),S="x-redocly-proxy-streaming",x="x-http-method-override",v="x-redocly-cookie",P="Requests to private network addresses are not allowed",Y=new L({connect:{lookup:b()}});function le(t,e=$(D.CORS_PROXY)){return async n=>{const r=new URL(n.req.url).pathname;if(r===e||r===`${e}/`)return n.text(`Realm CORS proxy endpoint.
2
+ Usage: ${e}/https://api.example.com/path`);const s=ne(n.req.url,e);if(!s)return n.text("Invalid proxied URL",400);const a=t?.getConfig().corsProxy?.allowedTargets;if(a&&a.length>0&&!a.some(f=>Z(s,f)))return n.text("Target URL is not in the allowed list for CORS proxy",403);const m=M(n),g=s.origin===m,w=s.pathname===e||s.pathname.startsWith(`${e}/`);if(g&&!w)return new Response("Please use a direct request",{status:308,headers:{Location:s.toString(),Vary:"origin","Cache-Control":"private"}});const p=await j(s.hostname),u=!y.isDevelopMode||y.isReunite;if(u&&p&&F(p))return n.text(P,403);const i=new Headers,A=T(n.req.raw.headers);for(const[o,f]of n.req.raw.headers)A.has(o.toLowerCase())||W.has(o.toLowerCase())||i.append(o,f);const R=i.get(v);if(R){const o=n.req.raw.headers.get("cookie")||"";i.set("cookie",o?`${o}; ${R}`:R),i.delete(v)}const O=n.req.raw.headers.get("origin")||"";X(O)&&i.delete("origin");let l=n.req.method;const E=i.get(x);E&&(l=E.toUpperCase(),i.delete(x));const h={method:l,headers:i,redirect:"manual"};u&&(h.dispatcher=Y),l!=="GET"&&l!=="HEAD"&&n.req.raw.body&&(h.body=n.req.raw.body,h.duplex="half");let c;try{c=await fetch(s,h)}catch(o){if(o instanceof Error&&o.cause instanceof N)return n.text(P,403);const f=o instanceof Error?o.message:"unknown error",k=o instanceof Error&&o.cause instanceof Error?`: ${o.cause.message}`:"";return n.text(`Failed to proxy request: ${f}${k}`,502)}if(n.req.raw.headers.get("sec-fetch-mode")==="navigate"&&ee(c))return n.text("Direct browser navigation to proxied HTML, JavaScript, or redirect responses is not allowed",403);const d=new Headers(c.headers),_=T(c.headers);for(const o of _)d.delete(o);for(const o of z)d.delete(o);return d.set(S,"1"),d.set("x-content-type-options","nosniff"),d.has("content-type")||d.set("content-type","text/plain"),new Response(c.body,{status:c.status,statusText:c.statusText,headers:d})}}function B(t){try{return decodeURIComponent(t)}catch{return t}}function G(t){return t.replace(/^(https?):\/(?!\/)/i,"$1://")}function V(t,e){return e?t.includes("?")?e==="?"?t:`${t.endsWith("?")||t.endsWith("&")?t:`${t}&`}${e.slice(1)}`:`${t}${e}`:t}function T(t){const e=new Set(U),n=t.get("connection");if(!n)return e;for(const r of n.split(",")){const s=r.trim().toLowerCase();s&&e.add(s)}return e}function X(t){const e=t.toLowerCase();return e.includes(".redocly.app")||e.includes("localhost")}async function j(t){const e=C(t);if(H(e))return e;try{const{address:n}=await q.promises.lookup(e);return n}catch{return null}}function F(t){const e=C(t).toLowerCase();if(!H(e))return!0;const n=J(e);return n?I(n):e.includes(":")?K(e):I(e)}function C(t){return t.startsWith("[")&&t.endsWith("]")?t.slice(1,-1):t}function J(t){const e=t.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);if(e){const r=parseInt(e[1],16),s=parseInt(e[2],16);return[r>>8&255,r&255,s>>8&255,s&255].join(".")}return t.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i)?.[1]??null}function I(t){const e=t.split(".").map(Number);if(e.length!==4||e.some(s=>isNaN(s)))return!0;const[n,r]=e;return n===0||n===10||n===127||n===172&&r>=16&&r<=31||n===192&&r===168||n===169&&r===254||n===100&&r>=64&&r<=127}function K(t){const e=Q(t);return t==="::1"||t==="::"||e!==null&&(e&65024)===64512||e!==null&&(e&65472)===65152}function Q(t){const[e]=t.split(":");if(!e)return null;const n=parseInt(e,16);return Number.isNaN(n)?null:n}function Z(t,e){let n;try{n=new URL(e)}catch{return!1}if(t.origin!==n.origin)return!1;const r=n.pathname.endsWith("/")?n.pathname:`${n.pathname}/`;return t.pathname.startsWith(r)||t.pathname===n.pathname}function ee(t){return t.status>=300&&t.status<400&&t.headers.has("location")||te(t.headers.get("content-type")||"")}function te(t){const e=t.toLowerCase();return e.includes("text/html")||e.includes("javascript")||e.includes("application/xhtml")||e.includes("application/xml")||e.includes("image/svg")}function ne(t,e){const n=new URL(t),r=n.pathname===e,s=n.pathname.startsWith(`${e}/`);if(!r&&!s)return null;const a=n.pathname.slice(e.length).replace(/^\/+/,"");if(!a)return null;const m=[a,B(a)];for(const g of m){const w=G(g),p=V(w,n.search);try{const u=new URL(p);if(u.protocol==="http:"||u.protocol==="https:")return u}catch{continue}}return null}const he=S;export{he as CORS_PROXY_STREAM_HEADER,le as corsProxyHandler,F as isPrivateIp,ne as resolveCorsProxyTarget};
@@ -1 +1 @@
1
- import{getCookie as U}from"hono/cookie";import{createHash as V,timingSafeEqual as Z}from"node:crypto";import{ulid as Y}from"ulid";import{isRedoclySso as W,isOidcProviderConfig as w,createMcpAuthorizationCode as X,verifyMcpAuthorizationCode as Q,createMcpRefreshToken as $,verifyMcpRefreshToken as x,createMcpSessionResource as S,oidcRefreshTokenGrant as ee,getOidcMetadata as re,getUserParamsFromCookies as te,idTokenIsForThisProject as ne}from"../../auth.js";import{isMcpContentProtected as P}from"../../../plugins/mcp/auth/auth-handlers.js";import{SUPPORTED_MCP_SCOPES as y}from"../../../plugins/mcp/constants.js";import{AUTH_URL as oe,JWT_SECRET_KEY as m,MCP_ACCESS_TOKEN_TTL_SEC as z,MCP_REFRESH_SESSION_LIFETIME_SEC as ie}from"../../../constants/common.js";import{ServerRoutes as T}from"../../../../constants/api.js";import{AuthCookieNames as v}from"../../../../constants/common.js";import{withPathPrefix as I}from"@redocly/theme/core/utils";import{telemetry as A}from"../../../telemetry/index.js";import{envConfig as D}from"../../../config/env-config.js";import*as M from"../../jwt/jwt.js";import{InternalTokenTypes as F,McpAccessTokenMarker as R}from"../../jwt/token-types.js";import{AlgorithmTypes as L,JwtAlgorithmNotImplemented as se,JwtTokenExpired as ce,JwtTokenInvalid as de,JwtTokenNotBefore as ae,JwtTokenSignatureMismatched as le}from"../../jwt/types.js";import{getRequestOrigin as N}from"../../utils/get-request-origin.js";import{encryptClaimValue as q}from"../../../utils/crypto/encrypt-claim-value.js";import{decryptAesGcm as pe}from"../../../utils/crypto/decrypt-aes-gcm.js";import{encryptAesGcm as G}from"../../../utils/crypto/encrypt-aes-gcm.js";import{fetchClientMetadataDocument as _e,isClientMetadataDocumentUrl as H,isRedirectUriRegistered as ue}from"./mcp-client-metadata.js";const s=(r,e,t=200,o)=>r.json(e,t,{"Content-Type":"application/json",...o??{}}),b=new Set(["S256"]),j=["authorization_code","refresh_token"],fe="https://claude.ai/oauth/claude-code-client-metadata";function he(r,e,t){if(!b.has(e||""))return!1;const o=V("sha256").update(t).digest("base64url"),c=Buffer.from(o),i=Buffer.from(r);return c.length!==i.length?!1:Z(c,i)}async function me(r){const e=Math.floor(Date.now()/1e3);return M.sign({type:F.MCP_CONTEXT,...r,iat:e,exp:e+600},m,L.HS256)}async function ge(r){await M.verify(r,m,L.HS256);const{payload:e}=M.decode(r);if(e.type!==F.MCP_CONTEXT)throw new Error("Invalid context token type");return e}function je(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=N(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 $e(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const t=N(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:[...j],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 ze(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:D.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:[...j],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 B(r){const e=r||{},t=Object.keys(e),o=t.find(i=>w(e[i])&&W(e[i])),c=t.find(i=>w(e[i]));return o||c||t[0]}function Fe(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"),p=o.get("code_challenge"),n=o.get("code_challenge_method"),l=Y();if(p&&!b.has(n||""))return s(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...b].join(", ")}`},400);if(H(i)&&!p)return s(e,{error:"invalid_request",error_description:"code_challenge is required for client_id_metadata_document clients"},400);if(H(i)&&c)try{const d=await _e(i),a=i===fe;if(!ue(c,d.redirect_uris,{ignorePort:a}))return s(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(d){const a=d instanceof Error?d.message:String(d);return s(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${a}`},400)}A.sendMcpAuthorizationStartedMessage([{...S(l),redirect_uri:c||null}]);const _=N(e),f={isMcpFlow:!0,originalRedirectUri:c,mcpClientId:i,mcpState:o.get("state"),mcpSessionId:l,mcpCodeChallenge:p,mcpCodeChallengeMethod:p?n:null,timestamp:Date.now()};try{const d=await me(f),a=new URL(I(T.IDP_LOGIN),_);a.searchParams.set("redirectTo",`${T.MCP_CALLBACK}/${d}`);const C=B(r.getConfig().ssoDirect);return C?(a.searchParams.set("idpId",C),e.redirect(a.toString())):s(e,{error:"not_found"},404)}catch(d){const a=d instanceof Error?d.message:String(d),C=d instanceof Error?d.stack:String(d);A.sendMcpAuthorizationFailedMessage([{...S(l),error:a,error_details:C}]);const g=new URL(I(`${oe}/oauth2/auth`));return g.search=o.toString(),e.redirect(g.toString())}}}async function K(r){const e=Math.floor(Date.now()/1e3),t=await M.sign({...r.claims,[R.CLAIM]:R.VALUE,...r.idpId?{idpId:r.idpId}:{},...r.idpAccessTokenEnc?{idp_access_token:r.idpAccessTokenEnc}:{},...r.ttlSec?{iat:e,exp:e+r.ttlSec}:{}},m,L.HS256,r.kid),o=r.ttlSec??(typeof r.claims.exp=="number"?Math.max(r.claims.exp-e,0):z);return{accessToken:t,expiresIn:o}}async function ke(r,e,t){const o=k=>s(r,{error:"invalid_grant",error_description:k},400),c=r.get("logger"),i=t.get("refresh_token"),p=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(k){return c?.warn("MCP refresh token rejected: %s",k instanceof Error?k.message:String(k)),o("Invalid refresh token")}if(p&&p!==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,a=n.idp_refresh_token_enc;if(n.idp_refresh_token_enc){let k;try{k=pe(n.idp_refresh_token_enc,m)}catch{return o("Invalid refresh token")}if(!n.idp_id||!w(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,k,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&&(a=G(u.refresh_token,m)),u.access_token&&(d=q(u.access_token,m)),u.id_token)try{const h=M.decode(u.id_token);_=h.payload,f=h.header.kid}catch{return o("Invalid token response from identity provider")}}const C=w(l)&&l.tokenExpirationTime||z,g=_??n.claims;if(!ne(g,l))return c?.warn("MCP refresh rejected: the id_token was issued for another project"),o("Invalid refresh token");const E=await K({claims:g,kid:f,idpId:n.idp_id,idpAccessTokenEnc:d,ttlSec:_?void 0:C}),O=await $({clientId:n.client_id,idpId:n.idp_id,claims:g,idpAccessTokenEnc:d,idpRefreshTokenEnc:a,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 Ge(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,p=t.get("code_verifier")||void 0;if(!j.includes(o))return s(e,{error:"invalid_request",error_description:"Invalid grant type"},400);if(o==="refresh_token")return await ke(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(!p)return s(e,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!he(n.code_challenge,n.code_challenge_method,p))return s(e,{error:"invalid_grant",error_description:"code_verifier mismatch"},400)}if(D.OAUTH_CLIENT_ID&&n.client_id&&!H(n.client_id)&&n.client_id!==D.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 _=await J(l,r.getConfig().ssoDirect);if(!_)return s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400);const f=n.idp_access_token_enc??(n.idp_access_token?q(n.idp_access_token,m):void 0),{accessToken:d,expiresIn:a}=await K({claims:_.payload,kid:_.kid,idpId:n.idp_id,idpAccessTokenEnc:f}),g=Math.floor(Date.now()/1e3)+ie,E=await $({clientId:n.client_id,idpId:n.idp_id,claims:_.payload,idpAccessTokenEnc:f,idpRefreshTokenEnc:n.idp_refresh_token_enc,sessionExp:g}),O={access_token:d,token_type:"Bearer",expires_in:a,refresh_token:E,scope:y.join(" ")};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 J(r,e){const t=await te(e||{},{[v.AUTHORIZATION]:r});if(t.isAuthenticated!==!0)return;const{payload:o,header:c}=M.decode(r);if(o[R.CLAIM]!==R.VALUE)return{payload:o,kid:c.kid,idpId:typeof t.idpId=="string"?t.idpId:void 0}}function Be(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 ge(o);if(c=i.mcpSessionId||null,!i.isMcpFlow||!i.originalRedirectUri)throw new Error("Invalid MCP context");const p=r.getConfig().ssoDirect,n=U(e,v.AUTHORIZATION),l=n?await J(n,p):void 0,_=U(e,v.IDP_ACCESS_TOKEN),f=U(e,v.IDP_REFRESH_TOKEN);if(!n||!l)return A.sendMcpAuthorizationFailedMessage([{...S(c),error:"Authentication required",error_details:null}]),e.text("Authentication required",401);const d=await X({idToken:n,idpId:l.idpId??B(p),idpAccessTokenEnc:_?q(_,m):void 0,idpRefreshTokenEnc:f?G(f,m):void 0,clientId:i.mcpClientId||"",redirectUri:i.originalRedirectUri,codeChallenge:i.mcpCodeChallenge||void 0,codeChallengeMethod:i.mcpCodeChallengeMethod||void 0,ttlSec:600}),a=new URL(i.originalRedirectUri);return a.searchParams.set("code",d),i.mcpState&&a.searchParams.set("state",i.mcpState),A.sendMcpAuthorizationCompletedMessage([{...S(c),redirect_uri:i.originalRedirectUri||null}]),e.redirect(a.toString())}catch(i){const p=Ce(i);return A.sendMcpAuthorizationFailedMessage([{...S(c),...p}]),e.text(`Invalid MCP callback: ${p.error}`,400)}}}function Ce(r){return r instanceof Error&&[se,ce,de,ae,le].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{j as SUPPORTED_MCP_GRANT_TYPES,me as createMcpContextToken,Fe as mcpAuthorizationHandler,Be as mcpCallbackHandler,ze as mcpDynamicClientRegistrationHandler,$e as mcpOAuthAuthorizationServerHandler,je as mcpOAuthProtectedResourceHandler,Ge as mcpTokenPortalHandler,B as resolveMcpIdpId,ge as verifyAndParseMcpContextToken,he as verifyPkce};
1
+ import{getCookie as U}from"hono/cookie";import{createHash as V,timingSafeEqual as Z}from"node:crypto";import{ulid as Y}from"ulid";import{isRedoclySso as W,isOidcProviderConfig as w,createMcpAuthorizationCode as X,verifyMcpAuthorizationCode as Q,createMcpRefreshToken as $,verifyMcpRefreshToken as x,createMcpSessionResource as S,oidcRefreshTokenGrant as ee,getOidcMetadata as re,getUserParamsFromCookies as te,idTokenIsForThisProject as ne}from"../../auth.js";import{isMcpContentProtected as P}from"../../../plugins/mcp/auth/auth-handlers.js";import{SUPPORTED_MCP_SCOPES as y}from"../../../plugins/mcp/constants.js";import{AUTH_URL as oe,JWT_SECRET_KEY as m,MCP_ACCESS_TOKEN_TTL_SEC as z,MCP_REFRESH_SESSION_LIFETIME_SEC as ie}from"../../../constants/common.js";import{ServerRoutes as T}from"../../../../constants/api.js";import{AuthCookieNames as v}from"../../../../constants/common.js";import{withPathPrefix as I}from"@redocly/theme/core/utils";import{telemetry as A}from"../../../telemetry/index.js";import{envConfig as D}from"../../../config/env-config.js";import*as M from"../../jwt/jwt.js";import{InternalTokenTypes as F,McpAccessTokenMarker as R}from"../../jwt/token-types.js";import{AlgorithmTypes as L,JwtAlgorithmNotImplemented as se,JwtTokenExpired as ce,JwtTokenInvalid as de,JwtTokenNotBefore as ae,JwtTokenSignatureMismatched as le}from"../../jwt/types.js";import{getRequestOrigin as N}from"../../utils/get-request-origin.js";import{encryptClaimValue as q}from"../../../utils/crypto/encrypt-claim-value.js";import{decryptAesGcm as pe}from"../../../utils/crypto/decrypt-aes-gcm.js";import{encryptAesGcm as G}from"../../../utils/crypto/encrypt-aes-gcm.js";import{fetchClientMetadataDocument as _e,isClientMetadataDocumentUrl as H,isRedirectUriRegistered as ue}from"./mcp-client-metadata.js";const s=(r,e,n=200,o)=>r.json(e,n,{"Content-Type":"application/json",...o??{}}),b=new Set(["S256"]),j=["authorization_code","refresh_token"],fe="https://claude.ai/oauth/claude-code-client-metadata";function he(r,e,n){if(!b.has(e||""))return!1;const o=V("sha256").update(n).digest("base64url"),c=Buffer.from(o),i=Buffer.from(r);return c.length!==i.length?!1:Z(c,i)}async function me(r){const e=Math.floor(Date.now()/1e3);return M.sign({type:F.MCP_CONTEXT,...r,iat:e,exp:e+600},m,L.HS256)}async function ge(r){await M.verify(r,m,L.HS256);const{payload:e}=M.decode(r);if(e.type!==F.MCP_CONTEXT)throw new Error("Invalid context token type");return e}function je(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 n=N(e);return s(e,{resource:`${n}${I("/mcp")}`,authorization_servers:[n],bearer_methods_supported:["header"],resource_documentation:`${n}${T.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:[...y],bearer_token_types_supported:["Bearer"]},200,{"Access-Control-Allow-Origin":"*"})}}function $e(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const n=N(e);return s(e,{issuer:n,authorization_endpoint:`${n}${I(T.MCP_AUTHORIZATION)}`,token_endpoint:`${n}${I(T.MCP_TOKEN_PORTAL)}`,scopes_supported:[...y],registration_endpoint:`${n}${I(T.MCP_DYNAMIC_CLIENT_REGISTRATION)}`,response_types_supported:["code"],grant_types_supported:[...j],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 ze(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:D.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:[...j],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(n){return s(e,{error:"invalid_request",error_description:n?.message||"Unable to register client"},500,{"Access-Control-Allow-Origin":"*"})}}}function B(r){const e=r||{},n=Object.keys(e),o=n.find(i=>w(e[i])&&W(e[i])),c=n.find(i=>w(e[i]));return o||c||n[0]}function Fe(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const n=new URL(e.req.url),{searchParams:o}=n,c=o.get("redirect_uri"),i=o.get("client_id"),p=o.get("code_challenge"),t=o.get("code_challenge_method"),l=Y();if(p&&!b.has(t||""))return s(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...b].join(", ")}`},400);if(H(i)&&!p)return s(e,{error:"invalid_request",error_description:"code_challenge is required for client_id_metadata_document clients"},400);if(H(i)&&c)try{const d=await _e(i),a=i===fe;if(!ue(c,d.redirect_uris,{ignorePort:a}))return s(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(d){const a=d instanceof Error?d.message:String(d);return s(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${a}`},400)}A.sendMcpAuthorizationStartedMessage([{...S(l),redirect_uri:c||null}]);const _=N(e),f={isMcpFlow:!0,originalRedirectUri:c,mcpClientId:i,mcpState:o.get("state"),mcpSessionId:l,mcpCodeChallenge:p,mcpCodeChallengeMethod:p?t:null,timestamp:Date.now()};try{const d=await me(f),a=new URL(I(T.IDP_LOGIN),_);a.searchParams.set("redirectTo",`${T.MCP_CALLBACK}/${d}`);const C=B(r.getConfig().ssoDirect);return C?(a.searchParams.set("idpId",C),e.redirect(a.toString())):s(e,{error:"not_found"},404)}catch(d){const a=d instanceof Error?d.message:String(d),C=d instanceof Error?d.stack:String(d);A.sendMcpAuthorizationFailedMessage([{...S(l),error:a,error_details:C}]);const g=new URL(I(`${oe}/oauth2/auth`));return g.search=o.toString(),e.redirect(g.toString())}}}async function K(r){const e=Math.floor(Date.now()/1e3),n=await M.sign({...r.claims,[R.CLAIM]:R.VALUE,...r.idpId?{idpId:r.idpId}:{},...r.idpAccessTokenEnc?{idp_access_token:r.idpAccessTokenEnc}:{},...r.ttlSec?{iat:e,exp:e+r.ttlSec}:{}},m,L.HS256,r.kid),o=r.ttlSec??(typeof r.claims.exp=="number"?Math.max(r.claims.exp-e,0):z);return{accessToken:n,expiresIn:o}}async function ke(r,e,n){const o=k=>s(r,{error:"invalid_grant",error_description:k},400),c=r.get("logger"),i=n.get("refresh_token"),p=n.get("client_id")||void 0;if(!i)return s(r,{error:"invalid_request",error_description:"refresh_token is required"},400);let t;try{t=await x(i)}catch(k){return c?.warn("MCP refresh token rejected: %s",k instanceof Error?k.message:String(k)),o("Invalid refresh token")}if(p&&p!==t.client_id)return o("Client mismatch");const l=t.idp_id?e.getConfig().ssoDirect?.[t.idp_id]:void 0;let _,f,d=t.idp_access_token_enc,a=t.idp_refresh_token_enc;if(t.idp_refresh_token_enc){let k;try{k=pe(t.idp_refresh_token_enc,m)}catch{return o("Invalid refresh token")}if(!t.idp_id||!w(l))return o("Unknown identity provider");let u;try{const h=await re(t.idp_id,l);if(!h?.token_endpoint)return o("Identity provider has no token endpoint");u=await ee(h.token_endpoint,k,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&&(a=G(u.refresh_token,m)),u.access_token&&(d=q(u.access_token,m)),u.id_token){try{const h=M.decode(u.id_token);_=h.payload,f=h.header.kid}catch{return o("Invalid token response from identity provider")}if(_.iss!==t.claims.iss||_.sub!==t.claims.sub)return c?.warn("MCP refresh rejected: the id_token names another issuer or subject"),o("Invalid refresh token")}}const C=w(l)&&l.tokenExpirationTime||z,g=_??t.claims;if(!ne(g,l))return c?.warn("MCP refresh rejected: the id_token was issued for another project"),o("Invalid refresh token");const E=await K({claims:g,kid:f,idpId:t.idp_id,idpAccessTokenEnc:d,ttlSec:_?void 0:C}),O=await $({clientId:t.client_id,idpId:t.idp_id,claims:g,idpAccessTokenEnc:d,idpRefreshTokenEnc:a,sessionExp:t.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 Ge(r){return async e=>{if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{const n=await e.req.formData(),o=n.get("grant_type"),c=n.get("code"),i=n.get("redirect_uri")||void 0,p=n.get("code_verifier")||void 0;if(!j.includes(o))return s(e,{error:"invalid_request",error_description:"Invalid grant type"},400);if(o==="refresh_token")return await ke(e,r,n);if(!c)return s(e,{error:"invalid_request",error_description:"Missing authorization code"},400);try{const t=await Q(c);if(i&&i!==t.redirect_uri)return s(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(t.code_challenge){if(!p)return s(e,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!he(t.code_challenge,t.code_challenge_method,p))return s(e,{error:"invalid_grant",error_description:"code_verifier mismatch"},400)}if(D.OAUTH_CLIENT_ID&&t.client_id&&!H(t.client_id)&&t.client_id!==D.OAUTH_CLIENT_ID)return s(e,{error:"invalid_client",error_description:"Client mismatch"},400);const l=t.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 _=await J(l,r.getConfig().ssoDirect);if(!_)return s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400);const f=t.idp_access_token_enc??(t.idp_access_token?q(t.idp_access_token,m):void 0),{accessToken:d,expiresIn:a}=await K({claims:_.payload,kid:_.kid,idpId:t.idp_id,idpAccessTokenEnc:f}),g=Math.floor(Date.now()/1e3)+ie,E=await $({clientId:t.client_id,idpId:t.idp_id,claims:_.payload,idpAccessTokenEnc:f,idpRefreshTokenEnc:t.idp_refresh_token_enc,sessionExp:g}),O={access_token:d,token_type:"Bearer",expires_in:a,refresh_token:E,scope:y.join(" ")};return s(e,O,200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch(t){return e.get("logger")?.warn("MCP token exchange failed: %s",t instanceof Error?t.message:String(t)),s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(n){const o=n instanceof Error?n.message:String(n);return s(e,{error:"server_error",error_description:"Failed to process token request",error_details:o},500)}}}async function J(r,e){const n=await te(e||{},{[v.AUTHORIZATION]:r});if(n.isAuthenticated!==!0)return;const{payload:o,header:c}=M.decode(r);if(o[R.CLAIM]!==R.VALUE)return{payload:o,kid:c.kid,idpId:typeof n.idpId=="string"?n.idpId:void 0}}function Be(r){return async e=>{const n=new URL(e.req.url);let o=n.searchParams.get("context");if(!o&&n.pathname.startsWith(I(`${T.MCP_CALLBACK}/`))){const i=n.pathname.split("/");o=i[i.length-1]}if(!o)return e.text("Missing context parameter",400);let c=null;try{const i=await ge(o);if(c=i.mcpSessionId||null,!i.isMcpFlow||!i.originalRedirectUri)throw new Error("Invalid MCP context");const p=r.getConfig().ssoDirect,t=U(e,v.AUTHORIZATION),l=t?await J(t,p):void 0,_=U(e,v.IDP_ACCESS_TOKEN),f=U(e,v.IDP_REFRESH_TOKEN);if(!t||!l)return A.sendMcpAuthorizationFailedMessage([{...S(c),error:"Authentication required",error_details:null}]),e.text("Authentication required",401);const d=await X({idToken:t,idpId:l.idpId??B(p),idpAccessTokenEnc:_?q(_,m):void 0,idpRefreshTokenEnc:f?G(f,m):void 0,clientId:i.mcpClientId||"",redirectUri:i.originalRedirectUri,codeChallenge:i.mcpCodeChallenge||void 0,codeChallengeMethod:i.mcpCodeChallengeMethod||void 0,ttlSec:600}),a=new URL(i.originalRedirectUri);return a.searchParams.set("code",d),i.mcpState&&a.searchParams.set("state",i.mcpState),A.sendMcpAuthorizationCompletedMessage([{...S(c),redirect_uri:i.originalRedirectUri||null}]),e.redirect(a.toString())}catch(i){const p=Ce(i);return A.sendMcpAuthorizationFailedMessage([{...S(c),...p}]),e.text(`Invalid MCP callback: ${p.error}`,400)}}}function Ce(r){return r instanceof Error&&[se,ce,de,ae,le].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{j as SUPPORTED_MCP_GRANT_TYPES,me as createMcpContextToken,Fe as mcpAuthorizationHandler,Be as mcpCallbackHandler,ze as mcpDynamicClientRegistrationHandler,$e as mcpOAuthAuthorizationServerHandler,je as mcpOAuthProtectedResourceHandler,Ge as mcpTokenPortalHandler,B as resolveMcpIdpId,ge as verifyAndParseMcpContextToken,he as verifyPkce};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/realm",
3
- "version": "0.138.0-next.7",
3
+ "version": "0.138.0-next.9",
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.3.0-next.5",
37
- "@redocly/config": "0.61.1",
36
+ "@redocly/api-docs": "0.3.0-next.6",
37
+ "@redocly/config": "0.62.0",
38
38
  "@redocly/openapi-core": "2.55.0",
39
39
  "@redocly/portal-legacy-ui": "0.19.0",
40
40
  "@redocly/portal-plugin-mock-server": "0.23.0-next.0",
41
41
  "@redocly/realm-asyncapi-sdk": "0.16.0-next.2",
42
- "@redocly/theme": "0.70.0-next.5",
42
+ "@redocly/theme": "0.70.0-next.6",
43
43
  "@shikijs/transformers": "3.21.0",
44
44
  "@tanstack/react-query": "5.62.3",
45
45
  "@tanstack/react-table": "8.21.3",
@@ -91,7 +91,7 @@
91
91
  "styled-components": "^6.4.2",
92
92
  "tty-browserify": "0.0.1",
93
93
  "typesense": "2.1.0",
94
- "ulid": "^2.3.0",
94
+ "ulid": "^3.0.2",
95
95
  "undici": "^6.28.0",
96
96
  "web-vitals": "3.3.1",
97
97
  "workerpool": "9.2.0",