@redocly/realm 0.138.0-next.3 → 0.138.0-next.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -0
- package/dist/cli/build/analytics/collect-analytics.js +1 -1
- package/dist/cli/build/analytics/collectors/get-sidebars-usage.d.ts +12 -0
- package/dist/cli/build/analytics/collectors/get-sidebars-usage.js +1 -0
- package/dist/cli/build/index.js +1 -1
- package/dist/cli/eject/resolveEjectParams.js +1 -1
- package/dist/cli/generate/string-utils.js +1 -1
- package/dist/cli/stats/collectors/extension-sampling.js +1 -1
- package/dist/cli/translations/collect-config-translations.js +1 -1
- package/dist/client/TestProvider.js +1 -1
- package/dist/client/app/Link.js +1 -1
- package/dist/client/app/Sidebar/useSidebarItems.js +1 -1
- package/dist/client/app/hooks/codeHighlight/treeview-transformer.js +1 -1
- package/dist/client/app/hooks/markdown/useMarkdownText.js +1 -1
- package/dist/client/app/search/index.d.ts +1 -1
- package/dist/client/app/search/index.js +1 -1
- package/dist/client/app/search/useSearch.d.ts +3 -0
- package/dist/client/app/search/useSearch.js +1 -1
- package/dist/client/providers/page-data/hooks.d.ts +2 -0
- package/dist/client/providers/page-data/hooks.js +1 -1
- package/dist/client/providers/theme/ThemeDataProvider.js +1 -1
- package/dist/client/runtime/loader.js +1 -1
- package/dist/client/templates/api-docs/helpers.js +1 -1
- package/dist/client/templates/api-docs/template.js +7 -3
- package/dist/client/utils/catalog/parse-filter-query.js +1 -1
- package/dist/client/{app/Sidebar/helpers → utils}/filter-out-versioned-items.d.ts +12 -2
- package/dist/client/utils/filter-out-versioned-items.js +1 -0
- package/dist/client/utils/navigation-tabs.d.ts +14 -0
- package/dist/client/utils/navigation-tabs.js +1 -0
- package/dist/constants/l10n/langs/ar.js +1 -1
- package/dist/constants/l10n/langs/de.js +1 -1
- package/dist/constants/l10n/langs/en.js +1 -1
- package/dist/constants/l10n/langs/es.js +1 -1
- package/dist/constants/l10n/langs/fr.js +1 -1
- package/dist/constants/l10n/langs/hi.js +1 -1
- package/dist/constants/l10n/langs/it.js +1 -1
- package/dist/constants/l10n/langs/ja.js +1 -1
- package/dist/constants/l10n/langs/ko.js +1 -1
- package/dist/constants/l10n/langs/pl.js +1 -1
- package/dist/constants/l10n/langs/pt-BR.js +1 -1
- package/dist/constants/l10n/langs/pt.js +1 -1
- package/dist/constants/l10n/langs/ru.js +1 -1
- package/dist/constants/l10n/langs/uk.js +1 -1
- package/dist/constants/l10n/langs/zh.js +1 -1
- package/dist/server/config/env-config.d.ts +0 -2
- package/dist/server/config/env-schema.d.ts +0 -5
- package/dist/server/config/env-schemas/feature-flags.d.ts +0 -4
- package/dist/server/config/env-schemas/feature-flags.js +1 -1
- package/dist/server/constants/common.d.ts +1 -1
- package/dist/server/constants/common.js +1 -1
- package/dist/server/esbuild/esbuild-logger.js +3 -3
- package/dist/server/plugins/api-docs/index.js +1 -1
- package/dist/server/plugins/api-docs/utils/apply-nav-item-to-root.d.ts +9 -0
- package/dist/server/plugins/api-docs/utils/apply-nav-item-to-root.js +1 -0
- package/dist/server/plugins/api-docs/utils/decorators.js +1 -1
- package/dist/server/plugins/catalog-entities/utils/ajv-validator.js +1 -1
- package/dist/server/plugins/catalog-entities/utils/extract-parts-from-components-ref.js +1 -1
- package/dist/server/plugins/catalog-entities/utils/is-valid-tag-name.js +1 -1
- package/dist/server/plugins/catalog-entities/utils/openapi-component-content-extractor.js +1 -1
- package/dist/server/plugins/default-theme/extract-team-names.js +1 -1
- package/dist/server/plugins/get-reserved-routes.d.ts +4 -2
- package/dist/server/plugins/get-reserved-routes.js +1 -1
- package/dist/server/plugins/l10n/index.js +1 -1
- package/dist/server/plugins/markdown/search/to-markdown.js +1 -1
- package/dist/server/plugins/markdown/utils/sanitize-malformed-md-content.js +1 -1
- package/dist/server/plugins/mcp/audit/secret-patterns.js +1 -1
- package/dist/server/plugins/mcp/auth/assistant-anonymous-token.d.ts +0 -1
- package/dist/server/plugins/mcp/auth/assistant-anonymous-token.js +1 -1
- package/dist/server/plugins/mcp/auth/auth-handlers.js +1 -1
- package/dist/server/plugins/mcp/codemode/build-tool-list.d.ts +6 -1
- package/dist/server/plugins/mcp/codemode/build-tool-list.js +2 -2
- package/dist/server/plugins/mcp/codemode/code-execution-audit.d.ts +3 -2
- package/dist/server/plugins/mcp/codemode/code-execution-audit.js +1 -1
- package/dist/server/plugins/mcp/codemode/code-execution-telemetry.d.ts +3 -4
- package/dist/server/plugins/mcp/codemode/code-execution-telemetry.js +1 -1
- package/dist/server/plugins/mcp/codemode/prompts.js +1 -1
- package/dist/server/plugins/mcp/codemode/tools/describe-tools.js +2 -2
- package/dist/server/plugins/mcp/codemode/tools/execute.js +1 -1
- package/dist/server/plugins/mcp/codemode/tools/manage-credentials.js +2 -2
- package/dist/server/plugins/mcp/custom-tools/scan-custom-tools.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/tool-schemas.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/tools/openapi/endpoint-suggestions.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/tools/openapi/get-endpoint-info.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/tools/openapi/get-security-schemes.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/tools/openapi/utils.d.ts +3 -4
- package/dist/server/plugins/mcp/docs-mcp/tools/openapi/utils.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/utils/resolve-refs.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/elicitation.d.ts +15 -3
- package/dist/server/plugins/mcp/gateway-mcp/elicitation.js +6 -5
- package/dist/server/plugins/mcp/gateway-mcp/fetch/allowed-hosts.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/fetch/url-placeholders.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/upstream-auth-scheme.js +2 -2
- package/dist/server/plugins/mcp/handlers/docs-mcp-handler.js +1 -1
- package/dist/server/plugins/mcp/handlers/mcp-credentials-page.js +35 -35
- package/dist/server/plugins/mcp/index.js +1 -1
- package/dist/server/plugins/mcp/servers/mcp-server.js +3 -3
- package/dist/server/plugins/mcp/types.d.ts +5 -11
- package/dist/server/plugins/mcp/utils/json-schema-validator.d.ts +2 -2
- package/dist/server/plugins/mcp/utils/json-schema-validator.js +1 -1
- package/dist/server/plugins/mcp/utils/mcp-client.js +1 -1
- package/dist/server/plugins/mcp/utils/public-endpoint.d.ts +9 -0
- package/dist/server/plugins/mcp/utils/public-endpoint.js +1 -0
- package/dist/server/plugins/mcp/utils/telemetry-attributes.d.ts +2 -1
- package/dist/server/plugins/mcp/utils/telemetry-attributes.js +1 -1
- package/dist/server/plugins/mcp/utils/xmcp-utils.d.ts +0 -7
- package/dist/server/plugins/mcp/utils/xmcp-utils.js +1 -1
- package/dist/server/plugins/mcp/workers/execute-mcp-tool.js +1 -1
- package/dist/server/plugins/mcp/workers/mcp-tool-telemetry.d.ts +5 -7
- package/dist/server/plugins/mcp/workers/mcp-tool-telemetry.js +1 -1
- package/dist/server/plugins/nav-utils.js +1 -1
- package/dist/server/plugins/scorecard-classic/index.js +1 -1
- package/dist/server/plugins/scorecard-classic/template/useData.js +1 -1
- package/dist/server/plugins/search/llmstxt/index.js +1 -1
- package/dist/server/plugins/sidebars/index.js +7 -3
- package/dist/server/plugins/sidebars/tabs.d.ts +19 -0
- package/dist/server/plugins/sidebars/tabs.js +1 -0
- package/dist/server/plugins/sso/index.js +1 -1
- package/dist/server/store.js +1 -1
- package/dist/server/tools/notifiers/helpers/privacy/mask-subject.js +1 -1
- package/dist/server/tools/notifiers/helpers/privacy/remove-pii.js +1 -1
- package/dist/server/utils/catalog-classic/get-catalog-shared-data-id.d.ts +3 -0
- package/dist/server/utils/catalog-classic/get-catalog-shared-data-id.js +1 -0
- package/dist/server/utils/envs/interpolate-env-variables.js +1 -1
- package/dist/server/utils/rbac.js +1 -1
- package/dist/server/utils/report-all-errors.js +1 -1
- package/dist/server/utils/sandbox-context.js +1 -1
- package/dist/server/utils/static-data.js +1 -1
- package/dist/server/utils/write-problems-report.d.ts +3 -0
- package/dist/server/utils/write-problems-report.js +1 -0
- package/dist/server/web-server/auth.d.ts +16 -2
- package/dist/server/web-server/auth.js +4 -4
- package/dist/server/web-server/jwt/jwt.js +1 -1
- package/dist/server/web-server/jwt/token-types.d.ts +11 -0
- package/dist/server/web-server/jwt/token-types.js +1 -0
- package/dist/server/web-server/routes/auth.js +1 -1
- package/dist/server/web-server/routes/dynamic-route.js +1 -1
- package/dist/server/web-server/routes/mcp-routes/mcp-oauth.js +1 -1
- package/dist/server/web-server/routes/semantic-search.d.ts +1 -1
- package/dist/server/web-server/routes/semantic-search.js +1 -1
- package/dist/server/web-server/routes/static-content.js +1 -1
- package/dist/utils/path/parse-path-versions.js +1 -1
- package/dist/utils/string/to-kebab-case.js +1 -1
- package/package.json +8 -8
- package/dist/client/app/Sidebar/helpers/filter-out-versioned-items.js +0 -1
- package/dist/server/plugins/mcp/auth/token-audience-telemetry.d.ts +0 -7
- package/dist/server/plugins/mcp/auth/token-audience-telemetry.js +0 -1
- package/dist/server/plugins/mcp/auth/token-audience.d.ts +0 -6
- package/dist/server/plugins/mcp/auth/token-audience.js +0 -1
|
@@ -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>"},A={codemodeType:"OpenApiDocument"},P={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}}},O={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}}},T={type:"object",required:[],additionalProperties:!1,properties:{}},q={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",properties:{items:{type:"array",items:{type:"object",properties:{name:{type:"string"},description:i,version:i,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:["filePath"],properties:{api:{type:"string"},version:{type:"string"},...n,servers:a,endpoints:{type:"array",items:{type:"object",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:O,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",required:["filePath"],properties:{api:{type:"string"},version:{type:"string"},...n,servers:a,endpoint:P,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:["filePath"],properties:{name:i,version:i,...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:["filePath"],properties:{api:{type:"string"},version:{type:"string"},...n,definition:A}}},...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:T,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:q,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",properties:{items:{type:"array",items:{type:"object",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",properties:{name:{type:"string"},items:{type:"array",items:{type:"object",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",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 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{formatCandidates as r}from"../../utils/api-name-matching.js";import{visibleOperations as l}from"./utils.js";function i(t,e){return l(t.paths?.[e]).map(([o])=>o.toUpperCase()).sort()}function h(t){return Object.keys(t.paths??{}).filter(e=>i(t,e).length>0).sort()}function c(t){return t==="code"?{endpoints:"tools.getEndpoints",search:"tools.search",listApis:"tools.listApis"}:{endpoints:"get-endpoints",search:"search",listApis:"list-apis"}}function f(t,e,o,n){const s=h(t);if(s.length===0)return"Endpoint not found";const{endpoints:p,search:a}=c(n);return`No path "${o}" in "${e}". Documented paths: ${r(s)}. Call ${p} for the full list, or ${a} with a keyword from the request.`}function m(t,e,o,n){const s=i(t,o);return s.length===0?"Endpoint not found":`"${o}" in "${e}" documents no ${n.toUpperCase()}. Available methods: ${r(s)}.`}export{m as describeMethodMiss,f as describePathMiss,c as recoveryToolNames,i as visibleMethodsAt,h as visiblePaths};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{inlineOperation as
|
|
1
|
+
import{inlineOperation as N,resolveOperation as O}from"../../utils/resolve-refs.js";import{matchDocumentedPath as b,visibleOperations as I}from"./utils.js";import{MAX_ENDPOINT_INFO_BYTES as x,MAX_INLINED_ENDPOINT_BYTES as T}from"./constants.js";import{describeMethodMiss as _,describePathMiss as S}from"./endpoint-suggestions.js";import{loadApiDescription as $}from"./load-api-description.js";const D=async(c,n)=>{const{path:h,method:e}=c;if(!h||!e)return{content:[{type:"text",text:'Both "path" and "method" are required (e.g. path: "/tickets", method: "GET").'}],isError:!0};let m;try{m=await $(c,n)}catch(r){return{content:[{type:"text",text:r.message}],isError:!0}}const{description:t,filePath:E,otherMatches:i}=m,d=i?` Other definitions share this title: ${i.map(r=>`"${r.filePath}"`).join(", ")}. Pass "filePath" to check them.`:"",o=b(t,h),{title:s=""}=t.info||{},a=t.paths?.[o],l=I(a);if(!a||l.length===0)return{content:[{type:"text",text:S(t,s,o,n.mcpMode)+d}],isError:!0};const f=l.find(([r])=>r.toLowerCase()===e.toLowerCase())?.[1];if(!f)return{content:[{type:"text",text:_(t,s,o,e)+d}],isError:!0};const{"x-mcp":C,...P}=f,v=a?.parameters||[],u={operation:P,pathParams:v,definition:t},M=n.mcpMode==="code"?N(u,T):void 0,{operation:g,components:y}=M??O(u),p=JSON.stringify({api:s,version:t.info?.version||"",filePath:E,otherMatches:i,servers:t.servers||[],endpoint:{path:o,method:e.toUpperCase(),...g},globalSecurity:t.security||[],securitySchemes:t.components?.securitySchemes||{},...y?{components:y}:{}},null,2);return p.length>x?{content:[{type:"text",text:`${e.toUpperCase()} ${o} is ${p.length} bytes, over the ${x} byte limit.`}],isError:!0}:{content:[{type:"text",text:p}]}};var U={"get-endpoint-info":D};export{U as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{loadApiDescription as
|
|
1
|
+
import{loadApiDescription as n}from"./load-api-description.js";const a=async(r,i)=>{let t;try{t=await n(r,i)}catch(o){return{content:[{type:"text",text:o.message}],isError:!0}}const{description:e,filePath:s,otherMatches:c}=t;return{content:[{type:"text",text:JSON.stringify({api:e.info?.title||"",version:e.info?.version||"",filePath:s,otherMatches:c,securitySchemes:e.components?.securitySchemes||{},security:e.security||[]},null,2)}]}};var y={"get-security-schemes":a};export{y as default};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { OpenAPIDefinition, OpenAPIOperation } from '@redocly/api-docs';
|
|
2
2
|
import type { ApiFunctionsUser, RbacConfig } from '@redocly/config';
|
|
3
|
-
import type { McpEndpoint } from '../../../types';
|
|
4
|
-
|
|
3
|
+
import type { McpEndpoint, McpPath } from '../../../types';
|
|
4
|
+
/** The operations of `pathItem` that `x-mcp` does not hide, keyed as the definition keys them. */
|
|
5
|
+
export declare function visibleOperations(pathItem: McpPath | undefined): [string, McpEndpoint][];
|
|
5
6
|
type EndpointInfo = Pick<OpenAPIOperation, 'summary' | 'description' | 'security'> & {
|
|
6
7
|
method: string;
|
|
7
8
|
path: string;
|
|
@@ -17,7 +18,5 @@ export declare function getApiDescriptionFromFs({ relativePath, outdir, user, rb
|
|
|
17
18
|
export declare function absolutePath(requestedPath: string): string;
|
|
18
19
|
/** Models copy a path and lose its casing; the definition's own spelling still answers. */
|
|
19
20
|
export declare function matchDocumentedPath(description: OpenAPIDefinition, requestedPath: string): string;
|
|
20
|
-
/** Definitions sometimes key an operation in upper case; the documented key still answers. */
|
|
21
|
-
export declare function matchDocumentedMethod(pathItem: object, requestedMethod: string): string;
|
|
22
21
|
export {};
|
|
23
22
|
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import m from"node:path";import{existsSync as f}from"node:fs";import{readFile as
|
|
1
|
+
import m from"node:path";import{existsSync as f}from"node:fs";import{readFile as u}from"node:fs/promises";import{replaceFileExtension as d}from"../../../../../plugins/api-docs/utils/replace-file-extension.js";import{PUBLIC_API_DEFINITIONS_FOLDER as h}from"../../../../../constants/common.js";import{filterDataByAccessDeep as l}from"../../../../../utils/rbac.js";import{isEndpointHidden as c}from"../../../utils/xmcp-utils.js";import{HTTP_METHODS as x}from"./constants.js";function y(t){return!t||c(t,"docs")?[]:Object.entries(t).filter(r=>{const[e,o]=r;return x.has(e.toLowerCase())&&typeof o=="object"&&o!==null&&!c(o,"docs")})}function P(t){const{paths:r={}}=t,e=[];for(const[o,i]of Object.entries(r))for(const[n,s]of y(i))e.push({path:o,method:n.toUpperCase(),summary:s.summary,description:s.description,security:s.security});return e}async function I({relativePath:t,outdir:r,user:e,rbac:o={},requiresLogin:i=!1}){const n=m.join(r||"",h,d(t,".json"));if(!f(n))return;const a=await u(n,"utf-8"),p=JSON.parse(a);return l(p,{isAuthenticated:e?.isAuthenticated,email:e?.email,teams:e?.teams},o,i)}function D(t){return t.startsWith("/")?t:`/${t}`}function S(t,r){const e=D(r);if(t.paths?.[e])return e;const o=e.toLowerCase();return Object.keys(t.paths??{}).find(n=>n.toLowerCase()===o)??e}export{D as absolutePath,I as getApiDescriptionFromFs,P as getEndpointsFromPaths,S as matchDocumentedPath,y as visibleOperations};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const S=/^#\/components\/([^/]+)\/([^/]+)(\/.*)?$/;function w(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")}function d(e,s){if(!s||!e?.startsWith("#/"))return;const n=e.slice(2).split("/").map(w);let t=s;for(const r of n)if(typeof t=="object"&&t!==null&&r in t)t=t[r];else return;return t}function h(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function O(e,s){const n={},t=new Set,r=new Set,o=[e];for(;o.length>0;){const c=o.pop();if(!(c===null||typeof c!="object")){if(h(c)&&typeof c.$ref=="string"&&!t.has(c.$ref)){t.add(c.$ref);const[,i,f]=S.exec(c.$ref)??[],u=f&&w(f);if(i&&u&&!r.has(`${i}/${u}`)){const l=d(`#/components/${i}/${f}`,s);l!==void 0&&(r.add(`${i}/${u}`),(n[i]??={})[u]=l,o.push(l))}}for(const i of Object.values(c))o.push(i)}}return Object.keys(n).length>0?n:void 0}function R(e,s){const n=h(e)&&typeof e.$ref=="string"?d(e.$ref,s)??e:e,{name:t,in:r}=h(n)?n:{};return typeof t=="string"&&typeof r=="string"?`${r}:${t}`:JSON.stringify(e)}function g(e,s,n){const t=new Map;for(const r of[...e,...s])r&&t.set(R(r,n),r);return[...t.values()]}function C({operation:e,pathParams:s,definition:n}){const t={...e,parameters:g(s,e.parameters??[],n)};return{operation:t,components:O(t,n)}}class E extends Error{}function j(e,s){if(e.remainingBytes-=s,e.remainingBytes<0)throw new E}function $(e,s){return j(e,JSON.stringify(s).length),s}function m(e,s,n){const t=d(e,s);if(t===void 0)return;const r=n.bytesByRef.get(e)??JSON.stringify(t).length;return n.bytesByRef.set(e,r),j(n,r),t}function y(e,s,n){const t=$(n,Object.fromEntries(Object.entries(e).filter(([r,o])=>r!=="$ref"&&o!==void 0)));return{...s,...t}}function a({schema:e,definition:s,budget:n,visitedRefs:t=new Set,maxDepth:r=50,currentDepth:o=0}){if(!e)return;if(o>=r)return $(n,{type:"object",description:`Maximum resolution depth exceeded (${r})`});if("$ref"in e&&e.$ref){if(t.has(e.$ref))return $(n,{type:"object",description:`Circular reference detected: ${e.$ref}`,"x-circular-ref":e.$ref});const f=new Set(t);f.add(e.$ref);const u=m(e.$ref,s,n);if(!u)return $(n,{type:"object",description:`Could not resolve reference: ${e.$ref}`,"x-unresolved-ref":e.$ref});const l=a({schema:u,definition:s,budget:n,visitedRefs:f,maxDepth:r,currentDepth:o+1});return l&&y(e,l,n)}const c={...e};if(c.properties){const f={};for(const[u,l]of Object.entries(c.properties)){const p=a({schema:l,definition:s,budget:n,visitedRefs:new Set(t),maxDepth:r,currentDepth:o+1});f[u]=p||l}c.properties=f}if(c.items&&typeof c.items=="object")if(Array.isArray(c.items))c.items=c.items.map(f=>a({schema:f,definition:s,budget:n,visitedRefs:new Set(t),maxDepth:r,currentDepth:o+1})||f);else{const f=a({schema:c.items,definition:s,budget:n,visitedRefs:new Set(t),maxDepth:r,currentDepth:o+1});f&&(c.items=f)}return["allOf","anyOf","oneOf"].forEach(f=>{const u=c[f];Array.isArray(u)&&(c[f]=u.reduce((l,p)=>{const v=a({schema:p,definition:s,budget:n,visitedRefs:new Set(t),maxDepth:r,currentDepth:o+1});return v&&l.push(v),l},[]))}),c}function P({pathParams:e,opParams:s,definition:n,budget:t}){return[...e,...s].reduce((r,o)=>{if(!o)return r;if("$ref"in o&&o.$ref){const c=m(o.$ref,n,t);if(!c)return r;const i=y(o,c,t);return r.push({...i,schema:"schema"in i&&i.schema?a({schema:i.schema,definition:n,budget:t}):void 0}),r}return r.push({...o,schema:"schema"in o&&o.schema?a({schema:o.schema,definition:n,budget:t}):void 0}),r},[])}function x(e,s,n){const t={...e};if(e.example!==void 0&&(t.example=e.example),!e.examples)return t;const r={};for(const[o,c]of Object.entries(e.examples))if("$ref"in c){const i=m(c.$ref,s,n);i&&(r[o]=i)}else r[o]=c;return t.examples=r,t}function A(e,s,n){if(!e)return;const t="$ref"in e&&e.$ref?m(e.$ref,s,n):e;if(!t)return;const r=y(e,t,n),o={...r};if("content"in r&&r.content){const c={};for(const[i,f]of Object.entries(r.content))c[i]={...x(f,s,n),schema:f.schema?a({schema:f.schema,definition:s,budget:n}):void 0};o.content=c}return o}function B(e,s,n){return Object.entries(e).reduce((t,[r,o])=>{const c="$ref"in o&&o.$ref?m(o.$ref,s,n):o;if(!c)return t;const i=y(o,c,n),f={...i};if("content"in i&&i.content){const u={};for(const[l,p]of Object.entries(i.content))u[l]={...x(p,s,n),schema:p.schema?a({schema:p.schema,definition:s,budget:n}):void 0};f.content=u}return t[r]=f,t},{})}function N({operation:e,pathParams:s,definition:n},t){const r={remainingBytes:t,bytesByRef:new Map};try{const o={...e,parameters:P({pathParams:s,opParams:e.parameters||[],definition:n,budget:r}),requestBody:A(e.requestBody,n,r),responses:B(e.responses,n,r)};return{operation:o,components:O(o,n)}}catch{return}}export{N as inlineOperation,C as resolveOperation,d as resolveRef};
|
|
1
|
+
const S=/^#\/components\/([^/]+)\/([^/]+)(\/.*)?$/;function w(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")}function d(e,s){if(!s||!e?.startsWith("#/"))return;const n=e.slice(2).split("/").map(w);let t=s;for(const r of n)if(typeof t=="object"&&t!==null&&r in t)t=t[r];else return;return t}function h(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function O(e,s){const n={},t=new Set,r=new Set,o=[e];for(;o.length>0;){const c=o.pop();if(!(c===null||typeof c!="object")){if(h(c)&&typeof c.$ref=="string"&&!t.has(c.$ref)){t.add(c.$ref);const[,i,f]=S.exec(c.$ref)??[],u=f&&w(f);if(i&&u&&!r.has(`${i}/${u}`)){const l=d(`#/components/${i}/${f}`,s);l!==void 0&&(r.add(`${i}/${u}`),(n[i]??={})[u]=l,o.push(l))}}for(const i of Object.values(c))o.push(i)}}return Object.keys(n).length>0?n:void 0}function R(e,s){const n=h(e)&&typeof e.$ref=="string"?d(e.$ref,s)??e:e,{name:t,in:r}=h(n)?n:{};return typeof t=="string"&&typeof r=="string"?`${r}:${t}`:JSON.stringify(e)}function g(e,s,n){const t=new Map;for(const r of[...e,...s])r&&t.set(R(r,n),r);return[...t.values()]}function C({operation:e,pathParams:s,definition:n}){const t={...e,parameters:g(s,e.parameters??[],n)};return{operation:t,components:O(t,n)}}class E extends Error{}function j(e,s){if(e.remainingBytes-=s,e.remainingBytes<0)throw new E}function $(e,s){return j(e,JSON.stringify(s).length),s}function m(e,s,n){const t=d(e,s);if(t===void 0)return;const r=n.bytesByRef.get(e)??JSON.stringify(t).length;return n.bytesByRef.set(e,r),j(n,r),t}function y(e,s,n){const t=$(n,Object.fromEntries(Object.entries(e).filter(([r,o])=>r!=="$ref"&&o!==void 0)));return{...s,...t}}function a({schema:e,definition:s,budget:n,visitedRefs:t=new Set,maxDepth:r=50,currentDepth:o=0}){if(!e)return;if(o>=r)return $(n,{type:"object",description:`Maximum resolution depth exceeded (${r})`});if("$ref"in e&&e.$ref){if(t.has(e.$ref))return $(n,{type:"object",description:`Circular reference detected: ${e.$ref}`,"x-circular-ref":e.$ref});const f=new Set(t);f.add(e.$ref);const u=m(e.$ref,s,n);if(!u)return $(n,{type:"object",description:`Could not resolve reference: ${e.$ref}`,"x-unresolved-ref":e.$ref});const l=a({schema:u,definition:s,budget:n,visitedRefs:f,maxDepth:r,currentDepth:o+1});return l&&y(e,l,n)}const c={...e};if(c.properties){const f={};for(const[u,l]of Object.entries(c.properties)){const p=a({schema:l,definition:s,budget:n,visitedRefs:new Set(t),maxDepth:r,currentDepth:o+1});f[u]=p||l}c.properties=f}if(c.items&&typeof c.items=="object")if(Array.isArray(c.items))c.items=c.items.map(f=>a({schema:f,definition:s,budget:n,visitedRefs:new Set(t),maxDepth:r,currentDepth:o+1})||f);else{const f=a({schema:c.items,definition:s,budget:n,visitedRefs:new Set(t),maxDepth:r,currentDepth:o+1});f&&(c.items=f)}return["allOf","anyOf","oneOf"].forEach(f=>{const u=c[f];Array.isArray(u)&&(c[f]=u.reduce((l,p)=>{const v=a({schema:p,definition:s,budget:n,visitedRefs:new Set(t),maxDepth:r,currentDepth:o+1});return v&&l.push(v),l},[]))}),c}function P({pathParams:e,opParams:s,definition:n,budget:t}){return[...e,...s].reduce((r,o)=>{if(!o)return r;if("$ref"in o&&o.$ref){const c=m(o.$ref,n,t);if(!c)return r;const i=y(o,c,t);return r.push({...i,schema:"schema"in i&&i.schema?a({schema:i.schema,definition:n,budget:t}):void 0}),r}return r.push({...o,schema:"schema"in o&&o.schema?a({schema:o.schema,definition:n,budget:t}):void 0}),r},[])}function x(e,s,n){const t={...e};if(e.example!==void 0&&(t.example=e.example),!e.examples)return t;const r={};for(const[o,c]of Object.entries(e.examples))if("$ref"in c){const i=m(c.$ref,s,n);i&&(r[o]=i)}else r[o]=c;return t.examples=r,t}function A(e,s,n){if(!e)return;const t="$ref"in e&&e.$ref?m(e.$ref,s,n):e;if(!t)return;const r=y(e,t,n),o={...r};if("content"in r&&r.content){const c={};for(const[i,f]of Object.entries(r.content))c[i]={...x(f,s,n),schema:f.schema?a({schema:f.schema,definition:s,budget:n}):void 0};o.content=c}return o}function B(e,s,n){if(e)return Object.entries(e).reduce((t,[r,o])=>{const c="$ref"in o&&o.$ref?m(o.$ref,s,n):o;if(!c)return t;const i=y(o,c,n),f={...i};if("content"in i&&i.content){const u={};for(const[l,p]of Object.entries(i.content))u[l]={...x(p,s,n),schema:p.schema?a({schema:p.schema,definition:s,budget:n}):void 0};f.content=u}return t[r]=f,t},{})}function N({operation:e,pathParams:s,definition:n},t){const r={remainingBytes:t,bytesByRef:new Map};try{const o={...e,parameters:P({pathParams:s,opParams:e.parameters||[],definition:n,budget:r}),requestBody:A(e.requestBody,n,r),responses:B(e.responses,n,r)};return{operation:o,components:O(o,n)}}catch{return}}export{N as inlineOperation,C as resolveOperation,d as resolveRef};
|
|
@@ -31,6 +31,16 @@ type CredentialElicitationRequest = {
|
|
|
31
31
|
hasExistingSecret: boolean;
|
|
32
32
|
reason: CredentialElicitationReason;
|
|
33
33
|
};
|
|
34
|
+
type AcceptedLinkScope = {
|
|
35
|
+
kv: KvService;
|
|
36
|
+
subject: string;
|
|
37
|
+
baseUrl: string;
|
|
38
|
+
apis: readonly string[];
|
|
39
|
+
};
|
|
40
|
+
export type AcceptedCredentialsLink = {
|
|
41
|
+
apiName: string;
|
|
42
|
+
url: string;
|
|
43
|
+
};
|
|
34
44
|
/** Concurrent prompts for one subject and scheme share the link the first one mints. */
|
|
35
45
|
export declare function dedupeElicitation(key: string, run: () => Promise<string>): Promise<string>;
|
|
36
46
|
export declare function supportsUrlElicitation(capabilities: {
|
|
@@ -42,13 +52,15 @@ export declare function supportsUrlElicitation(capabilities: {
|
|
|
42
52
|
export declare function isStatelessRequest(mcpReq: {
|
|
43
53
|
envelope?: unknown;
|
|
44
54
|
}): boolean;
|
|
45
|
-
|
|
55
|
+
/** How the user answered the credentials prompt, read from a retried request. */
|
|
56
|
+
export declare function credentialElicitationAnswer(inputResponses: Record<string, unknown> | undefined): 'accepted' | 'declined' | undefined;
|
|
57
|
+
export declare function buildCredentialsAcceptedHint(apiName: string, url?: string): string;
|
|
58
|
+
export declare function acceptedCredentialsLink(requestState: unknown, { kv, subject, baseUrl, apis }: AcceptedLinkScope): Promise<AcceptedCredentialsLink | undefined>;
|
|
46
59
|
export declare function schemeLabelOf(schemes: Record<string, unknown>, scheme: string): string | undefined;
|
|
47
60
|
/**
|
|
48
61
|
* The 2026-07-28-era credentials prompt: the tool call returns an input-required result the
|
|
49
62
|
* client fulfils and retries, with no push `elicitation/create` and no session behind it.
|
|
50
|
-
*
|
|
51
|
-
* recomputed from the caller's identity on every request.
|
|
63
|
+
* `requestState` carries the link token, so the retry after an accepted prompt can repeat the link.
|
|
52
64
|
*/
|
|
53
65
|
export declare function buildUpstreamCredentialInputRequired(request: CredentialElicitationRequest): Promise<InputRequiredResult>;
|
|
54
66
|
/** Fallback text for a client without URL-elicitation support. */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import{inputRequired as
|
|
2
|
-
`)}const
|
|
3
|
-
`)}
|
|
4
|
-
`)}
|
|
5
|
-
`)}
|
|
1
|
+
import{inputRequired as u,inputResponse as A}from"@modelcontextprotocol/server";import{withPathPrefix as I}from"@redocly/theme/core/utils";import{MCP_CREDENTIALS_ROUTE as P}from"../../../constants/mcp.js";import{MANAGE_API_CREDENTIALS_TOOL_NAME as h}from"../constants.js";import{secretStore as d,ELICITATION_TTL_MS as g}from"./secret-store.js";import{operationsForScheme as k,orderedSchemesOf as E}from"./upstream-auth-scheme.js";import{ANONYMOUS_SUBJECT_PREFIX as p}from"./subject.js";const m="credentials",l=new Map;function F(e,t){const r=l.get(e);if(r)return r;const n=t().finally(()=>{l.delete(e)});return l.set(e,n),n}function H(e){return!!e?.elicitation?.url}function D(e){return e.envelope!==void 0}function q(e){const t=A(e,m);if(t.kind==="elicit")return t.action==="accept"?"accepted":"declined"}function B(e,t){return[`The user accepted the prompt to add credentials for the "${e}" API.`,...t?["The credentials page:",t]:[],"",`Wait until the user says they saved the credentials, then retry the call that needed them. Do not call \`${h}\` again unless the user asks for a new link.`].join(`
|
|
2
|
+
`)}async function Y(e,{kv:t,subject:r,baseUrl:n,apis:a}){if(typeof e!="string")return;const i=await d.peekElicitation(t,e);if(!(i?.subject!==r||!a.includes(i.api)))return{apiName:i.name,url:w(n,e)}}function W(e,t){return Object.keys(e).length>1?t:void 0}async function z(e){const{token:t,url:r}=await $(e);return u({inputRequests:{[m]:u.elicitUrl({message:C(e),url:r})},requestState:t})}async function G(e){const{url:t}=await $(e);return O(e,t,S(e.subject))}function J(e,t){const r=f(e);if(e.status===401&&!e.sentScheme){const[i,o]=e.callerHeader?[`Your code set the "${e.callerHeader}" header itself, so the stored credential was not sent.`,"Remove that header from the request and retry. Do not replace the stored credentials."]:["No stored credential was sent with this request, so this result says nothing about the stored credentials.","Do not replace them. Check whether this operation needs a different kind of credential or a different route."];return[`The "${e.name}" API returned ${r}. ${i}`,"",o].join(`
|
|
3
|
+
`)}const n=e.status===401?`The "${e.name}" API rejected the stored ${t?`"${t}" `:""}credential (${r}).`:`The "${e.name}" API rejected the request with ${r}.`,a=t?`api: "${e.api}" and scheme: "${t}"`:`api: "${e.api}"`;return[n,"",`Do not retry the same call. Call \`${h}\` with ${a} to get a link the user has to open in a browser to replace or remove the stored credentials, then retry once they confirm they saved them.`].join(`
|
|
4
|
+
`)}function f({status:e,detail:t}){return t?`HTTP ${e}: ${t}`:`HTTP ${e}`}function K(e,t){const r=t.map(({name:n,label:a,description:i})=>`- ${n} \u2014 ${a}${i?`: ${i}`:""}`);return[`The "${e.name}" API rejected the request with ${f(e)} and this operation accepts more than one kind of credential:`,"",...r,"","Ask the user which one of these they have, and wait for the answer. Never guess and never reuse a value meant for another kind.",`Then call \`${h}\` with api: "${e.api}" and scheme set to the name they picked to get a link where they enter it.`].join(`
|
|
5
|
+
`)}async function $({kv:e,subject:t,baseUrl:r,target:n,auth:a,apiAuth:i,hasExistingSecret:o}){const s=i?await b(e,t,n.api,i):[],c=await d.createElicitation(e,{subject:t,api:n.api,name:n.name,scheme:n.scheme,...n.schemeLabel?{schemeLabel:n.schemeLabel}:{},auth:a,hasExistingSecret:o,...s.length>1?{schemes:s}:{}});return{token:c,url:w(r,c)}}async function b(e,t,r,n){const i=(await d.getSecretsForSubject(e,t))[r]??{};return E(n.schemes).map(o=>{const s=k(n,o),c=n.descriptions?.[o];return{name:o,...c?{label:c}:{},auth:n.schemes[o],hasExistingSecret:!!i[o],...s.length>0?{operations:s}:{}}})}function S(e){return e.startsWith(p)?e.slice(p.length):void 0}function C({target:e,hasExistingSecret:t,reason:r}){const n=`The link works for ${T()} minutes.`,a=x(e),i=r.kind==="authFailure"&&r.detail?` The API answered: ${r.detail}.`:"";return t?`Open this page in a browser to replace or remove the stored credentials for ${a}. They go straight to the API and never enter the chat. ${n}`:`${v(a)} needs credentials.${i} Open this page in a browser to paste them - they go straight to the API and never enter the chat. ${n}`}function x({name:e,schemeLabel:t}){return t?`the "${t}" scheme of the "${e}" API`:`the "${e}" API`}function v(e){return e.charAt(0).toUpperCase()+e.slice(1)}function T(){return Math.round(g/6e4)}function w(e,t){const r=I(P);if(!e)return`${r}?token=${t}`;try{const n=new URL(r,e);return n.searchParams.set("token",t),n.toString()}catch{return`${e}${r}?token=${t}`}}function O({target:e,reason:t,hasExistingSecret:r},n,a){const o=[`**Stop and show the user this link. They have to open it in a browser to ${r?`replace or remove the stored credentials for the "${e.name}" API`:`add credentials for the "${e.name}" API`} - nothing continues until they do.**`,"",n,"",`The link is valid ${T()} minutes and opens a form on this site. Credentials go straight to the API from there: never ask for them in the conversation and never pass them as a tool argument.`,"",t.kind==="authFailure"?`The upstream API "${e.name}" returned ${t.status}${t.detail?` (${t.detail})`:""} and needs credentials.`:r?`Credentials are already stored for the "${e.name}" API; the link lets the user replace or remove them.`:`The "${e.name}" API needs credentials.`,...e.schemeLabel?[`The operation authenticates with its "${e.schemeLabel}" security scheme.`]:[],a?`Once the user confirms they saved them, call this tool again with sessionHandle: "${a}" - the credentials are stored against that handle, applied server-side, and never appear in the conversation.`:"Once the user confirms they saved them, call this tool again - the credentials are applied server-side and never appear in the conversation."],s=t.kind==="authFailure"?t.otherApiNames??[]:[];if(s.length>0){const c=s.map(y=>`"${y}"`).join(", ");o.push("",`The remaining APIs (${c}) will prompt for credentials on subsequent calls.`)}return o.join(`
|
|
6
|
+
`)}export{m as CREDENTIALS_INPUT_REQUEST_KEY,Y as acceptedCredentialsLink,B as buildCredentialsAcceptedHint,J as buildCredentialsToolHint,K as buildSchemeChoiceHint,G as buildUpstreamCredentialFallback,z as buildUpstreamCredentialInputRequired,q as credentialElicitationAnswer,F as dedupeElicitation,D as isStatelessRequest,W as schemeLabelOf,H as supportsUrlElicitation};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const h=/\{([^}]+)\}/g;function g(n=[]){const e=[];for(const t of n){const r=p(t);r&&e.push(r)}return e}function x(n,e){return e.some(t=>t.test(n))}function p(n){const e=m(n.url);if(!e)return null;let t="",r=0,o=!1;for(const l of e.matchAll(h)){const[i,f]=l,c=e.slice(r,l.index);c.includes(".")&&(o=!0),t+=s(c);const u=d(n.variables?.[f]);u.guaranteesDot&&(o=!0),t+=u.pattern,r=l.index+i.length}const a=e.slice(r);return a.includes(".")&&(o=!0),t+=s(a),o?new RegExp(`^${t}$`,"i"):null}function d(n){const e=n?.enum?.filter(Boolean);if(e&&e.length>0)return{pattern:`(?:${e.map(s).join("|")})`,guaranteesDot:e.every(r=>r.includes("."))};const t=n?.default?.split(".").slice(1).join(".");return t?{pattern:`[^.]+\\.${s(t)}`,guaranteesDot:!0}:{pattern:"[^.]+",guaranteesDot:!1}}function m(n){if(!n)return null;const t=n.replace(/^[a-z][a-z0-9+.-]*:\/\//i,"").split(/[/?#]/,1)[0];return t&&(t.includes("@")?t.slice(t.lastIndexOf("@")+1):t).replace(/:(?:\d+|\{[^}]+\})$/,"")||null}function s(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}export{g as buildAllowedHostMatchers,x as isHostAllowed};
|
|
1
|
+
const h=/\{(?<!\{[^}]*?\{)([^}]+)\}/g;function g(n=[]){const e=[];for(const t of n){const r=p(t);r&&e.push(r)}return e}function x(n,e){return e.some(t=>t.test(n))}function p(n){const e=m(n.url);if(!e)return null;let t="",r=0,o=!1;for(const l of e.matchAll(h)){const[i,f]=l,c=e.slice(r,l.index);c.includes(".")&&(o=!0),t+=s(c);const u=d(n.variables?.[f]);u.guaranteesDot&&(o=!0),t+=u.pattern,r=l.index+i.length}const a=e.slice(r);return a.includes(".")&&(o=!0),t+=s(a),o?new RegExp(`^${t}$`,"i"):null}function d(n){const e=n?.enum?.filter(Boolean);if(e&&e.length>0)return{pattern:`(?:${e.map(s).join("|")})`,guaranteesDot:e.every(r=>r.includes("."))};const t=n?.default?.split(".").slice(1).join(".");return t?{pattern:`[^.]+\\.${s(t)}`,guaranteesDot:!0}:{pattern:"[^.]+",guaranteesDot:!1}}function m(n){if(!n)return null;const t=n.replace(/^[a-z][a-z0-9+.-]*:\/\//i,"").split(/[/?#]/,1)[0];return t&&(t.includes("@")?t.slice(t.lastIndexOf("@")+1):t).replace(/:(?:\d+|\{[^}]+\})$/,"")||null}function s(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}export{g as buildAllowedHostMatchers,x as isHostAllowed};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const o=/\{([^}/]+)\}/g,t=/^[A-Za-z_][A-Za-z0-9_]*$/;function n(e){const a=e.replace(/%7B/gi,"{").replace(/%7D/gi,"}"),l=new Set;for(const[,r]of a.matchAll(o))t.test(r)&&l.add(r);return[...l]}function s(e){const a=e.length>1?"placeholders":"placeholder",l=e.map(r=>`"${r}"`).join(", ");return`Unresolved ${a} ${l} in the URL. This is not a credentials problem. Replace every placeholder with a real value. Use the server variable default, or ask the user.`}export{n as findUnresolvedPlaceholders,s as unresolvedPlaceholderMessage};
|
|
1
|
+
const o=/\{(?<!\{[^}/]*?\{)([^}/]+)\}/g,t=/^[A-Za-z_][A-Za-z0-9_]*$/;function n(e){const a=e.replace(/%7B/gi,"{").replace(/%7D/gi,"}"),l=new Set;for(const[,r]of a.matchAll(o))t.test(r)&&l.add(r);return[...l]}function s(e){const a=e.length>1?"placeholders":"placeholder",l=e.map(r=>`"${r}"`).join(", ");return`Unresolved ${a} ${l} in the URL. This is not a credentials problem. Replace every placeholder with a real value. Use the server variable default, or ask the user.`}export{n as findUnresolvedPlaceholders,s as unresolvedPlaceholderMessage};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{getApiDescriptionFromFs as O}from"../docs-mcp/tools/openapi/utils.js";import{OPERATION_METHODS as S}from"../docs-mcp/tools/openapi/constants.js";const A=120,l="default",g={kind:"raw"},h=["oauth2ClientCredentials","bearer","apiKey","cookie","raw"],w=new Set(S),C=/\{[^}]*\}/g;function c(){return{schemes:{[l]:g},defaultScheme:l,routes:[]}}async function L(e,t,n=O){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?E(i,r.servers??[]):c()}catch{return c()}}function E(e,t){const n=$(e),r=Object.keys(n);if(r.length===0)return c();const i=m(e.security,n)[0]??a(r,n)[0],o=x(e,r);return{schemes:n,defaultScheme:i,routes:v(e,n,t),...Object.keys(o).length>0?{descriptions:o}:{}}}function N(e,t){return t.flatMap(n=>{const r=e.schemes[n];if(!r)return[];const i=e.descriptions?.[n];return[{name:n,label:j(r),...i?{description:i}:{}}]})}function U(e){return[...new Set(Object.values(e).map(P))]}function q(e){return a(Object.keys(e),e)}function z(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 j(e){switch(e.kind){case"bearer":return"API key or bearer token";case"cookie":return`browser session cookie (${e.name})`;case"apiKey":return`API key in the ${e.header} header`;case"oauth2ClientCredentials":return"OAuth2 client credentials";default:return"Authorization header value"}}function x(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 $(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==="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 v(e,t,n){const r=I(n),i=[];for(const[o,s]of Object.entries(e.paths??{})){if(!s||typeof s!="object")continue;const f=H(r,o);if(f)for(const[p,b]of Object.entries(s)){if(!w.has(p.toLowerCase()))continue;const u=b?.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 H(e,t){if(t.startsWith("/"))return new RegExp(`^${e}${y(t)}/?$`)}function y(e){return e.split(C).map(_).join("[^/]+").replace(
|
|
1
|
+
import{getApiDescriptionFromFs as O}from"../docs-mcp/tools/openapi/utils.js";import{OPERATION_METHODS as S}from"../docs-mcp/tools/openapi/constants.js";const A=120,l="default",g={kind:"raw"},h=["oauth2ClientCredentials","bearer","apiKey","cookie","raw"],w=new Set(S),C=/\{(?<!\{[^}]*?\{)[^}]*\}/g;function c(){return{schemes:{[l]:g},defaultScheme:l,routes:[]}}async function L(e,t,n=O){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?E(i,r.servers??[]):c()}catch{return c()}}function E(e,t){const n=$(e),r=Object.keys(n);if(r.length===0)return c();const i=m(e.security,n)[0]??a(r,n)[0],o=x(e,r);return{schemes:n,defaultScheme:i,routes:v(e,n,t),...Object.keys(o).length>0?{descriptions:o}:{}}}function N(e,t){return t.flatMap(n=>{const r=e.schemes[n];if(!r)return[];const i=e.descriptions?.[n];return[{name:n,label:j(r),...i?{description:i}:{}}]})}function U(e){return[...new Set(Object.values(e).map(P))]}function q(e){return a(Object.keys(e),e)}function z(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 j(e){switch(e.kind){case"bearer":return"API key or bearer token";case"cookie":return`browser session cookie (${e.name})`;case"apiKey":return`API key in the ${e.header} header`;case"oauth2ClientCredentials":return"OAuth2 client credentials";default:return"Authorization header value"}}function x(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 $(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==="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 v(e,t,n){const r=I(n),i=[];for(const[o,s]of Object.entries(e.paths??{})){if(!s||typeof s!="object")continue;const f=H(r,o);if(f)for(const[p,b]of Object.entries(s)){if(!w.has(p.toLowerCase()))continue;const u=b?.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 H(e,t){if(t.startsWith("/"))return new RegExp(`^${e}${y(t)}/?$`)}function y(e){return e.split(C).map(_).join("[^/]+").replace(/(?<!\/)\/+$/,"")}function I(e){const t=[...new Set(e.map(n=>R(n.url)).filter(Boolean).map(y))];return t.length>0?`(?:${t.join("|")})?`:""}function R(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 _(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function k(e){return typeof e=="object"&&e!==null&&"type"in e}export{l as DEFAULT_SCHEME_NAME,U as credentialHeadersOf,z as operationsForScheme,q as orderedSchemesOf,L as resolveUpstreamApiAuth,N as schemeChoicesOf};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{logger as
|
|
1
|
+
import{logger as h}from"../../../tools/notifiers/logger.js";import{getFeedbackClientIp as N}from"../../../web-server/utils/feedback-ip.js";import{getForwardedOrigin as A,getInternalOrigin as v}from"../../../web-server/utils/get-request-origin.js";import{GATEWAY_FETCH_LIMIT_DEFAULTS as F,DEFAULT_ANONYMOUS_VISITOR_TEAM as B,RbacFeatures as L}from"../../../../constants/common.js";import{isObject as H}from"../../../../utils/guards/is-object.js";import{getAccessibleSkills as V}from"../../../utils/skills/get-accessible-skills.js";import{getSkillUrl as $}from"../../../utils/skills/get-skill-url.js";import{createMcpHandler as j}from"@modelcontextprotocol/server";import{createDocsMcpServer as G}from"../servers/docs-server.js";import{filterApiDescriptionsByRbac as J,filterGraphqlDescriptionsByRbac as K}from"../utils.js";import{McpClientInputError as Y,createMethodNotAllowedError as z,withErrorHandling as W}from"./errors.js";import{constructForbiddenResponse as X,constructInvalidTokenResponse as Q,constructUnauthorizedResponse as Z,handleMcpAuth as q}from"../auth/auth-handlers.js";import{isAssistantAnonymousToken as x}from"../auth/assistant-anonymous-token.js";import{shouldHandleMcpAuth as ee}from"../../../utils/rbac/should-handle-mcp-auth.js";import{extractTokenFromAuthHeader as re}from"../utils/jwt.js";import{resolveMcpClient as oe}from"../utils/mcp-client.js";import{randomUUID as se}from"../../../utils/crypto/random-uuid.js";import{getPublicMcpEndpointStatus as ne}from"../utils/public-endpoint.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as ie,SSE_KEEPALIVE_INTERVAL_MS as te}from"../constants.js";import{withPathPrefix as ae}from"@redocly/theme/core/utils";import{PUBLIC_MCP_ROUTE as ce}from"@redocly/theme/core/constants/mcp";import{canAccessFeature as pe}from"../../../utils/rbac.js";import{ANONYMOUS_AUTH_DETAILS as de}from"../../../constants/rbac.js";const M="X-Redocly-AI-Metadata";function le(r){if(r)try{const e=JSON.parse(r);if(H(e))return e;h.warn(`Ignoring ${M} header: not a JSON object.`)}catch{h.warn(`Ignoring ${M} header: invalid JSON.`)}}function b(){return{...de,email:"",idpAccessToken:void 0,idpId:void 0}}const me=async(r,e,i)=>{const t=!!e?.config?.access?.requiresLogin,a=e?.config?.access?.rbac||{},E=i?.props?.config?.mcpMode??"tools",y=i?.props?.config?.gatewayFetchBudgetPerExec??F.budgetPerExec,S=i?.props?.config?.searchIgnoredRoutes??[],s=i,U=!!s?.props?.config?.isPublicEndpoint;let m;const T=r.headers.get("Authorization");if(U)e.user=b();else if(ee(t,a)){const{isAuthenticated:n,isTokenValid:p,isFirstPartyAnonymous:u,currentUser:d,accessToken:f}=await q(r,e);if(u)e.user=b();else if(n)d&&(e.user=d),m=f;else{const c=A(r),l=ne(e.config)==="served"?`${c}${ae(ce)}`:void 0;return p===!1?Q(c,l):Z(c,l)}}else if(T){const n=re(T)??void 0;m=(n?await x(n):!1)?void 0:n}const w={isAuthenticated:!!e.user?.isAuthenticated,teams:e.user?.teams||[B],claims:e.user?.claims??{}};return pe(L.MCP,w,a,t)?await W(async()=>{const n=i?.props?.config?.elicitationEnabled??!1;if(r.method==="GET")return new Response(JSON.stringify({error:"Method Not Allowed",message:`In order to use this MCP server, you need to register it in your MCP client (VS Code, Cursor, Claude Code, etc.) using this URL: ${r.url}`}),{status:405,headers:{"Content-Type":"application/json"}});if(r.method!=="POST")return z();const p=s?.props?.config?.apiDescriptionsMap||{},u=s?.props?.config?.graphqlDescriptions||[],d=s?.props?.config?.mcpDocsServerName||ie,{config:{mcp:f={}}}=e,c=J(p,e.user,a,t),l=K(u,e.user,a,t),C=e.config.products?Object.values(e.config.products).map(o=>o?.name):[],D=f.docs?.name||d,g=le(r.headers.get(M)),P=s?.props?.tools||[],R=s?.props?.skillsBaseUrl||A(r),_=V(s?.props?.skills||[],e.user,a,t).map(o=>({slug:o.slug,name:o.name,description:o.description,content:o.content,uri:$(R,o.slug)})),I=await ue(r),O={name:D,tools:P,skills:_,context:{...e,accessToken:m,outdir:e.outdir||"",baseUrl:e.baseUrl||A(r),internalBaseUrl:v(r),apiDescriptionsMap:c,graphqlDescriptions:l,products:C,metadata:g,mcpMode:E,elicitationEnabled:n,gatewayFetchBudgetPerExec:y,searchIgnoredRoutes:S,clientIp:N(r),...oe(r),requestId:se(),messageId:typeof g?.messageId=="string"?g.messageId:void 0}},k=await G({...O,getKv:e.getKv});return await j(async()=>k,{keepAliveMs:te,onerror:o=>h.warn(`MCP handler: ${o.message}`)}).fetch(r,I!==void 0?{parsedBody:I}:void 0)},E):X()};async function ue(r){try{return await r.json()}catch(e){throw new Y(e instanceof Error?e.message:String(e),{cause:e})}}var Le=me;export{Le as default};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{ELICITATION_TTL_MS as
|
|
1
|
+
import{withPathPrefix as g}from"@redocly/theme/core/utils";import{MCP_CREDENTIALS_ROUTE as C}from"../../../constants/mcp.js";import{ELICITATION_TTL_MS as f}from"../gateway-mcp/secret-store.js";const b=6,v=`<svg class="logo" width="193" height="56" viewBox="0 0 193 56" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Redocly">
|
|
2
2
|
<g clip-path="url(#redocly-logo-clip)">
|
|
3
3
|
<path d="M84.3634 40.7533H80.0357L73.199 30.9849H69.8119V40.8155H66.1738V14.5592H76.3353C78.9067 14.5592 80.9139 15.368 82.2308 16.9857C83.5483 18.5412 84.1756 20.5322 84.1756 22.8343C84.1756 25.0119 83.6107 26.7541 82.4193 28.3095C81.2902 29.865 79.5339 30.7361 77.2134 30.8605L84.3634 40.7533ZM69.8743 17.8568V27.6251H76.2098C79.22 27.6251 80.5999 25.4475 80.5999 22.7098C80.5999 20.0344 79.22 17.7945 76.3353 17.7945H69.8743V17.8568Z" fill="currentColor"/>
|
|
4
4
|
<path d="M103.117 33.225H88.5654C89.0042 36.1492 91.2623 38.078 94.3355 38.078C96.5312 38.078 98.5384 37.0203 99.4789 35.3403C99.8553 35.4648 100.357 35.7137 101.047 35.9626L102.553 36.4603C101.047 39.5091 97.9111 41.1889 94.2731 41.1889C91.701 41.1889 89.506 40.3179 87.6873 38.638C85.931 36.8959 85.1152 34.7182 85.1152 31.9184C85.1152 29.1185 85.9934 26.9409 87.6873 25.2609C89.4436 23.5188 91.6386 22.71 94.2731 22.71C96.8445 22.71 98.9771 23.581 100.608 25.2609C102.302 26.9409 103.18 29.1807 103.18 31.9184C103.117 32.5405 103.117 32.9139 103.117 33.225ZM99.6674 30.4873C99.228 27.563 97.0954 25.7587 94.21 25.7587C91.1999 25.7587 88.9418 27.6252 88.5654 30.4873H99.6674Z" fill="currentColor"/>
|
|
@@ -13,7 +13,7 @@ import{ELICITATION_TTL_MS as m}from"../gateway-mcp/secret-store.js";const f=6,b=
|
|
|
13
13
|
<path d="M28.5385 16.6127H44.4703C45.3484 16.6127 46.0383 15.9282 46.0383 15.0572C46.0383 14.1861 45.3484 13.5017 44.4703 13.5017H28.5385C27.6604 13.5017 26.9705 14.1861 26.9705 15.0572C26.9077 15.8661 27.6604 16.6127 28.5385 16.6127Z" fill="currentColor"/>
|
|
14
14
|
</g>
|
|
15
15
|
<defs><clipPath id="redocly-logo-clip"><rect width="193" height="56" fill="white"/></clipPath></defs>
|
|
16
|
-
</svg>`;function r(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""")}function s(e){return`<!doctype html>
|
|
16
|
+
</svg>`;function r(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""")}function c(){return r(g(C))}function s(e){return`<!doctype html>
|
|
17
17
|
<html lang="en">
|
|
18
18
|
<head>
|
|
19
19
|
<meta charset="utf-8">
|
|
@@ -88,21 +88,21 @@ label.check input{width:auto;margin:0}
|
|
|
88
88
|
footer{margin-top:1.5rem;padding-top:1rem;border-top:1px solid var(--border);font-size:.75rem;color:var(--text-muted);text-align:center;line-height:1.45}
|
|
89
89
|
</style>
|
|
90
90
|
</head>
|
|
91
|
-
<body><main class="card">${
|
|
92
|
-
</html>`}function l(e){return`<h1>\u{1F512} ${r(e)}</h1>`}function d(){return"<footer>Secured by Redocly · Credentials never enter the AI conversation</footer>"}function
|
|
91
|
+
<body><main class="card">${v}${e}</main></body>
|
|
92
|
+
</html>`}function l(e){return`<h1>\u{1F512} ${r(e)}</h1>`}function d(){return"<footer>Secured by Redocly · Credentials never enter the AI conversation</footer>"}function u(){return`<p class="hint">This link expires ${Math.round(f/6e4)} minutes after it was issued. If it stops working, request a new one in your AI client.</p>`}function h({name:e,schemeLabel:t,schemes:o}){return o&&o.length>1?e:t?`${e} (${t})`:e}function y(e){return`
|
|
93
93
|
<div class="section">
|
|
94
94
|
<h2>Remove stored credentials</h2>
|
|
95
95
|
<p class="hint">This API already has credentials stored. Enter new credentials to replace them, or remove them to make calls anonymous again.</p>
|
|
96
|
-
<form method="post" action="
|
|
96
|
+
<form method="post" action="${c()}" autocomplete="off">
|
|
97
97
|
<input type="hidden" name="token" value="${r(e)}">
|
|
98
98
|
<input type="hidden" name="mode" value="clear">
|
|
99
99
|
<button class="danger" type="submit">Remove credentials</button>
|
|
100
100
|
</form>
|
|
101
|
-
</div>`}function
|
|
101
|
+
</div>`}function Z(e,t,o){const n=o?`<p class="error">${r(o)}</p>`:"";if(e.schemes&&e.schemes.length>1)return I(e,e.schemes,t,n);const a=e.hasExistingSecret?y(t):"",i=h(e);switch(e.auth.kind){case"bearer":return $(i,t,a,n);case"apiKey":return x(i,t,e.auth.header,a,n);case"cookie":return k(i,t,e.auth.name,a,n);case"oauth2ClientCredentials":return w(i,t,e.auth.scopes,a,n);default:return A(i,t,a,n)}}function $(e,t,o,n){return s(`
|
|
102
102
|
${l(`Credentials for ${e}`)}
|
|
103
103
|
${n}
|
|
104
104
|
<p class="hint">Encrypted and visible only to you for 24 hours (anonymous) or 30 days (signed in).<br />Sent to the API as the <code>Authorization header</code>. It never passes through the AI conversation.</p>
|
|
105
|
-
<form method="post" action="
|
|
105
|
+
<form method="post" action="${c()}" autocomplete="off">
|
|
106
106
|
<input type="hidden" name="token" value="${r(t)}">
|
|
107
107
|
<input type="hidden" name="mode" value="token">
|
|
108
108
|
<label for="secret">API token</label>
|
|
@@ -111,12 +111,12 @@ ${n}
|
|
|
111
111
|
<button type="submit">Save credentials</button>
|
|
112
112
|
</form>
|
|
113
113
|
${o}
|
|
114
|
-
${
|
|
115
|
-
${d()}`)}function
|
|
114
|
+
${u()}
|
|
115
|
+
${d()}`)}function x(e,t,o,n,a){return s(`
|
|
116
116
|
${l(`Credentials for ${e}`)}
|
|
117
117
|
${a}
|
|
118
118
|
<p class="hint">Encrypted and visible only to you for 24 hours (anonymous) or 30 days (signed in).<br />Sent to the API as the <code>${r(o)}</code> header. It never passes through the AI conversation.</p>
|
|
119
|
-
<form method="post" action="
|
|
119
|
+
<form method="post" action="${c()}" autocomplete="off">
|
|
120
120
|
<input type="hidden" name="token" value="${r(t)}">
|
|
121
121
|
<input type="hidden" name="mode" value="token">
|
|
122
122
|
<label for="secret">${r(o)} value</label>
|
|
@@ -124,12 +124,12 @@ ${a}
|
|
|
124
124
|
<button type="submit">Save credentials</button>
|
|
125
125
|
</form>
|
|
126
126
|
${n}
|
|
127
|
-
${
|
|
128
|
-
${d()}`)}function
|
|
127
|
+
${u()}
|
|
128
|
+
${d()}`)}function k(e,t,o,n,a){return s(`
|
|
129
129
|
${l(`Credentials for ${e}`)}
|
|
130
130
|
${a}
|
|
131
131
|
<p class="hint">Encrypted and visible only to you for 24 hours (anonymous) or 30 days (signed in).<br />Sent to the API as the <code>${r(o)}</code> cookie. It never passes through the AI conversation.</p>
|
|
132
|
-
<form method="post" action="
|
|
132
|
+
<form method="post" action="${c()}" autocomplete="off">
|
|
133
133
|
<input type="hidden" name="token" value="${r(t)}">
|
|
134
134
|
<input type="hidden" name="mode" value="token">
|
|
135
135
|
<label for="secret">${r(o)} cookie value</label>
|
|
@@ -138,14 +138,14 @@ ${a}
|
|
|
138
138
|
<button type="submit">Save credentials</button>
|
|
139
139
|
</form>
|
|
140
140
|
${n}
|
|
141
|
-
${
|
|
142
|
-
${d()}`)}function
|
|
141
|
+
${u()}
|
|
142
|
+
${d()}`)}function w(e,t,o,n,a){const i=o.length>0?o.map(r).join(", "):"default scopes";return s(`
|
|
143
143
|
${l(`Credentials for ${e}`)}
|
|
144
144
|
${a}
|
|
145
145
|
<p class="hint">Paste your token, or provide your OAuth2 client credentials.</p>
|
|
146
146
|
<div class="section">
|
|
147
147
|
<h2>I have a token</h2>
|
|
148
|
-
<form method="post" action="
|
|
148
|
+
<form method="post" action="${c()}" autocomplete="off">
|
|
149
149
|
<input type="hidden" name="token" value="${r(t)}">
|
|
150
150
|
<input type="hidden" name="mode" value="token">
|
|
151
151
|
<label for="secret">API token</label>
|
|
@@ -157,7 +157,7 @@ ${a}
|
|
|
157
157
|
<div class="section">
|
|
158
158
|
<h2>Exchange client credentials</h2>
|
|
159
159
|
<p class="hint">We request a token from the API on your behalf. Scopes: ${i}.</p>
|
|
160
|
-
<form method="post" action="
|
|
160
|
+
<form method="post" action="${c()}" autocomplete="off">
|
|
161
161
|
<input type="hidden" name="token" value="${r(t)}">
|
|
162
162
|
<input type="hidden" name="mode" value="client-credentials">
|
|
163
163
|
<label for="client_id">Client ID</label>
|
|
@@ -168,12 +168,12 @@ ${a}
|
|
|
168
168
|
</form>
|
|
169
169
|
</div>
|
|
170
170
|
${n}
|
|
171
|
-
${
|
|
172
|
-
${d()}`)}function
|
|
171
|
+
${u()}
|
|
172
|
+
${d()}`)}function A(e,t,o,n){return s(`
|
|
173
173
|
${l(`Credentials for ${e}`)}
|
|
174
174
|
${n}
|
|
175
175
|
<p class="hint">The value is stored and encrypted only for you for 24 hours (anonymous) or 30 days (signed in).<br />It is sent to the API as the <code>Authorization</code> header. It never passes through the AI conversation.</p>
|
|
176
|
-
<form method="post" action="
|
|
176
|
+
<form method="post" action="${c()}" autocomplete="off">
|
|
177
177
|
<input type="hidden" name="token" value="${r(t)}">
|
|
178
178
|
<input type="hidden" name="mode" value="token">
|
|
179
179
|
<label for="secret">Authorization header value</label>
|
|
@@ -181,45 +181,45 @@ ${n}
|
|
|
181
181
|
<button type="submit">Save credentials</button>
|
|
182
182
|
</form>
|
|
183
183
|
${o}
|
|
184
|
-
${
|
|
185
|
-
${d()}`)}function
|
|
184
|
+
${u()}
|
|
185
|
+
${d()}`)}function I(e,t,o,n){const a=t.map((i,p)=>P(i,p)).join(`
|
|
186
186
|
`);return s(`
|
|
187
187
|
${l(`Credentials for ${e.name}`)}
|
|
188
188
|
${n}
|
|
189
189
|
<p class="hint">This API accepts more than one kind of credential. Fill in the one(s) you have and leave the rest blank - each value is stored encrypted for you only (24 hours for anonymous callers, 30 days when signed in) and sent straight to the API. It never passes through the AI conversation.</p>
|
|
190
|
-
<form method="post" action="
|
|
190
|
+
<form method="post" action="${c()}" autocomplete="off">
|
|
191
191
|
<input type="hidden" name="token" value="${r(o)}">
|
|
192
192
|
<input type="hidden" name="mode" value="multi">
|
|
193
193
|
${a}
|
|
194
194
|
<button type="submit">Save credentials</button>
|
|
195
195
|
</form>
|
|
196
|
-
${
|
|
197
|
-
${d()}`)}function
|
|
196
|
+
${u()}
|
|
197
|
+
${d()}`)}function P(e,t){const o=M(e.auth),n=e.label?`<p class="hint">${r(e.label)}</p>`:"",a=S(e.operations),i=e.hasExistingSecret?`<label class="check"><input type="checkbox" name="remove__${t}" value="1"> Remove the stored credential</label>`:"",p=e.hasExistingSecret?'<span class="badge stored">Stored</span>':'<span class="badge">Not set</span>';return`
|
|
198
198
|
<div class="section">
|
|
199
|
-
<h2>${r(e.name)} ${
|
|
199
|
+
<h2>${r(e.name)} ${p}</h2>
|
|
200
200
|
<p class="hint">Sent to the API as the <code>${r(o.code)}</code> ${o.suffix}.</p>
|
|
201
201
|
${n}
|
|
202
202
|
${a}
|
|
203
|
-
${
|
|
203
|
+
${_(e.auth,t)}
|
|
204
204
|
${i}
|
|
205
|
-
</div>`}function
|
|
205
|
+
</div>`}function _(e,t){const o=`secret__${t}`;if(e.kind==="oauth2ClientCredentials"){const a=e.scopes.length>0?e.scopes.map(r).join(", "):"default scopes";return`<label for="${o}">API token</label>
|
|
206
206
|
<input type="password" id="${o}" name="${o}" placeholder="paste a token you already have">
|
|
207
207
|
<p class="hint">Or exchange client credentials for one (scopes: ${a}). We add <code>Bearer</code> automatically.</p>
|
|
208
208
|
<label for="client_id__${t}">Client ID</label>
|
|
209
209
|
<input type="text" id="client_id__${t}" name="client_id__${t}">
|
|
210
210
|
<label for="client_secret__${t}">Client secret</label>
|
|
211
|
-
<input type="password" id="client_secret__${t}" name="client_secret__${t}">`}const n=
|
|
212
|
-
<input type="password" id="${o}" name="${o}" placeholder="value">`}function
|
|
211
|
+
<input type="password" id="client_secret__${t}" name="client_secret__${t}">`}const n=H(e);return`<label for="${o}">${r(n)}</label>
|
|
212
|
+
<input type="password" id="${o}" name="${o}" placeholder="value">`}function H(e){switch(e.kind){case"apiKey":return`${e.header} value`;case"cookie":return`${e.name} cookie value`;case"bearer":return"API token";default:return"Authorization header value"}}function M(e){switch(e.kind){case"apiKey":return{code:e.header,suffix:"header"};case"cookie":return{code:e.name,suffix:"cookie"};case"bearer":return{code:"Authorization",suffix:"header (bearer token)"};case"oauth2ClientCredentials":return{code:"Authorization",suffix:"header (OAuth2 bearer token)"};default:return{code:"Authorization",suffix:"header"}}}function S(e){if(!e||e.length===0)return"";const t=e.slice(0,b).map(r).join(", "),o=e.length-b,n=o>0?`, and ${o} more`:"";return`<p class="hint">Used by ${t}${n}.</p>`}function E(e){return s(`
|
|
213
213
|
${l("Credentials removed")}
|
|
214
|
-
<p>Credentials for ${r(
|
|
215
|
-
${d()}`)}function
|
|
214
|
+
<p>Credentials for ${r(h(e))} are removed. Calls to the API are anonymous again until you add new credentials.</p>
|
|
215
|
+
${d()}`)}function R(e,t){const o=t.saved.length===0?"Credentials removed":t.removed.length===0?"Credentials saved":"Credentials updated",n=[t.saved.length>0?`<p>Saved: ${m(t.saved)}.</p>`:"",t.removed.length>0?`<p>Removed: ${m(t.removed)}.</p>`:"",t.stored.length>0?`<p>${r(e.name)} now has stored credentials for ${m(t.stored)}.</p>`:`<p>${r(e.name)} has no stored credentials. Calls to the API are anonymous until you add new ones.</p>`];return s(`
|
|
216
216
|
${l(o)}
|
|
217
217
|
${n.join(`
|
|
218
218
|
`)}
|
|
219
219
|
<p>Retry in your AI client, or confirm any prompt it left open. You can close this tab.</p>
|
|
220
220
|
<p class="hint">This link is single-use and is now spent. To change anything, request a new one in your AI client.</p>
|
|
221
|
-
${d()}`)}function
|
|
221
|
+
${d()}`)}function m(e){return e.map(t=>`<code>${r(t)}</code>`).join(", ")}function F(e){return s(`
|
|
222
222
|
${l("Credentials saved")}
|
|
223
|
-
<p>Credentials for ${r(
|
|
223
|
+
<p>Credentials for ${r(h(e))} are saved. Retry in your AI client, or confirm any prompt it left open. You can close this tab.</p>
|
|
224
224
|
<p class="hint">This link is single-use and is now spent. To change anything, request a new one in your AI client.</p>
|
|
225
|
-
${d()}`)}function
|
|
225
|
+
${d()}`)}function q(){return s(`${l("Link expired")}<p>This credentials link is invalid, already used, or older than ${Math.round(f/6e4)} minutes. Retry in your AI client to get a fresh link.</p>${d()}`)}export{E as clearedPage,Z as formPage,R as multiOutcomePage,q as notFoundPage,F as successPage};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{fileURLToPath as
|
|
1
|
+
import{fileURLToPath as U}from"node:url";import{dirname as $,join as h}from"node:path";import{MCP_ROUTE as k,PUBLIC_MCP_ROUTE as D}from"@redocly/theme/core/constants/mcp";import{logger as c}from"../../tools/notifiers/logger.js";import{reporter as F}from"../../tools/notifiers/reporter.js";import{isDefined as B}from"../../../utils/guards/is-defined.js";import{MCP_CREDENTIALS_HANDLER_ID as P,MCP_CREDENTIALS_ROUTE as x,MCP_DOCS_SERVER_HANDLER_ID as E,MCP_NOT_ALLOWED_TEMPLATE_ID as V}from"../../constants/mcp.js";import{bundledOpenApiDocumentsToMcpOpenApi as J,bundledGraphqlDocumentsToMcpOpenApi as W,buildGraphqlDescriptions as X,collectIgnoredRoutes as z,filterIgnoredApiDescriptions as A,getCleanedUpApiDescriptions as K,isMcpInRedirects as Q}from"./utils.js";import{getMcpMode as R,isMcpElicitationEnabled as Y,isMcpGatewayEnabled as Z}from"./utils/mode.js";import{getPublicMcpEndpointStatus as _}from"./utils/public-endpoint.js";import{findGatedToolNames as ee}from"./utils/find-gated-tool-names.js";import{getDefaultToolSchemas as te}from"./docs-mcp/tool-schemas.js";import{EXECUTE_TOOL_SCHEMA as oe}from"./codemode/tools/execute-schema.js";import{isGatewayCapabilityEnabled as ie,isGatewayEligible as re}from"./gateway-mcp/eligibility.js";import{resolveGatewayFetchLimits as se}from"./gateway-mcp/rate-limit/limits.js";import{resetCustomMcpTools as ne,scanCustomMcpTools as ae}from"./custom-tools/scan-custom-tools.js";import{telemetryTraceStep as ce}from"../../../cli/telemetry/helpers/trace-step.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as S}from"./constants.js";const M=$(U(import.meta.url));async function pe(){let i;const l={registeredToolNames:[],warnedNoOutputSchemaTools:new Set};let m=0,u=!1;return{id:"mcp",async processContent(e,n){await ce("com.redocly.build.plugin.mcp",async s=>{const o=await n.getConfig(),{mcp:r,redirects:f}=o;if(s?.setAttribute("config",JSON.stringify(r||{})),s?.setAttribute("mcpMode",R()),s?.setAttribute("mcpGateway",String(Z())),r?.hide||r?.docs?.hide){ne(e,l);return}Q(f)&&await F.panicOnBuildContentError('The reserved route "/mcp" cannot be used in redirect configuration. Please remove any redirects involving "/mcp" from your redocly.yaml config.'),e.createTemplate(V,"@redocly/theme/layouts/McpNotAllowed");const p=!!o?.access?.requiresLogin,b=o?.access?.rbac||{},g=R(),d=Y(),{budgetPerExec:T}=se();if(!i){c.info("Configuring MCP servers...");const w=J(await e.loadApiDocuments(n)),O=W(await e.loadApiDocuments(n)),v=r?.docs?.ignore||[],L=K(A(w,v)),q=ie(r),C=new Map;for(const t of L){const H=t.definition.info?.title||"",j=t.definition.info?.version||"";C.set(t.relativePath,{name:H,description:typeof t.definition.info?.description=="string"?t.definition.info.description:"",version:j,servers:t.definition.servers||[],relativePath:t.relativePath,...q&&re(t.definition.info,t.relativePath,b)?{fetchEligible:!0}:{}})}const y=X(A(O,v)),I=Object.values(o.products||{}).map(t=>t?.name).filter(B),N=o.l10n?.locales?.map(t=>t.code)??n.fs.localeFolders,G=te({products:I,locales:N,requiresLogin:p,rbac:b,hasGraphqlApis:y.length>0,elicitationEnabled:d,hasCallableApis:C.values().some(t=>t.fetchEligible),feedbackHidden:o.feedback?.hide===!0});e.addMcpTools(h(M,"./docs-mcp/tools/index.js"),G),g==="code"&&e.addMcpTools(h(M,"./codemode/tools/execute.js"),[oe]),i={apiDescriptionsMap:Object.fromEntries(C),graphqlDescriptions:y}}const a=await ae(e,n,l);s?.setAttribute("customToolsCount",String(a)),a!==m&&(m=a,a>0&&c.info(`Registered ${a} custom MCP tool${a===1?"":"s"} from @mcp folders`)),await de({actions:e,handlerPath:h(M,"./handlers/docs-mcp-handler.js"),apiDescriptionsMap:i.apiDescriptionsMap,graphqlDescriptions:i.graphqlDescriptions,mcpConfig:r,mcpMode:g,elicitationEnabled:d,gatewayFetchBudgetPerExec:T,publicEndpointStatus:_(o)}),u||(c.info("Registered Docs MCP Server endpoint at /mcp"),u=!0)})},async afterRoutesCreated(e,n){const s=await n.getConfig(),{mcp:o}=s,r=!o?.hide&&!o?.docs?.hide;e.markGatedMcpTools(await ee(e.getMcpTools())),e.setGlobalData({mcpData:{docs:{enabled:r,name:o?.docs?.name||S,publicEndpoint:_(s)==="served"}}})}}}var Se=pe;async function de({actions:i,handlerPath:l,apiDescriptionsMap:m,graphqlDescriptions:u,mcpConfig:e,mcpMode:n,elicitationEnabled:s,gatewayFetchBudgetPerExec:o,publicEndpointStatus:r}){i.createRequestHandler(E,l);const f=p=>async(b,g)=>{const d=z(g.getAllRoutes(),e?.docs?.ignore||[]);return d.length>0&&c.verbose(`mcp.docs.ignore: hiding ${d.length} page routes from MCP search`),{props:{config:{apiDescriptionsMap:m,graphqlDescriptions:u,mcpDocsServerName:e?.docs?.name||S,mcpMode:n,elicitationEnabled:s,gatewayFetchBudgetPerExec:o,searchIgnoredRoutes:d,...p?{isPublicEndpoint:p}:{}},serverOutDir:i.serverOutDir,tools:i.getMcpTools().map(({importPath:T,...a})=>a)}}};if(i.addApiRoute({slug:k,requestHandlerId:E,httpMethod:"all",getStaticData:f(!1)}),r==="no-protected-content"?c.info(`mcp.docs.publicEndpoint is enabled, but the project has no protected content - /mcp already serves anonymous users, skipping ${D}.`):r==="anonymous-denied"?c.info(`mcp.docs.publicEndpoint is enabled, but anonymous users are denied the MCP feature (rbac.features.mcp or requiresLogin) - skipping ${D}.`):r==="served"&&(i.addApiRoute({slug:D,requestHandlerId:E,httpMethod:"all",getStaticData:f(!0)}),c.verbose(`Registered public Docs MCP Server endpoint at ${D}`)),s){const p=h(M,"./handlers/mcp-credentials-handler.js");i.createRequestHandler(P,p),i.addApiRoute({slug:x,requestHandlerId:P,httpMethod:"all",getStaticData:async()=>({props:{}})}),c.verbose("Registered MCP credentials endpoint at /mcp/credentials")}}export{Se as default,pe as mcpServerPlugin};
|