@redocly/realm 0.136.0 → 0.137.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/dist/server/plugins/mcp/auth/auth-handlers.d.ts +1 -1
- package/dist/server/plugins/mcp/auth/auth-handlers.js +1 -1
- package/dist/server/plugins/mcp/constants.d.ts +1 -0
- package/dist/server/plugins/mcp/constants.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/tool-schemas.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/tools/core/search.js +3 -3
- package/dist/server/plugins/mcp/docs-mcp/tools/openapi/constants.d.ts +1 -0
- package/dist/server/plugins/mcp/docs-mcp/tools/openapi/constants.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/tools/openapi/endpoint-suggestions.d.ts +11 -0
- package/dist/server/plugins/mcp/docs-mcp/tools/openapi/endpoint-suggestions.js +1 -0
- package/dist/server/plugins/mcp/docs-mcp/tools/openapi/get-endpoint-info.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/tools/openapi/list-apis.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/tools/openapi/load-api-description.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/tools/openapi/utils.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/utils/api-name-matching.d.ts +8 -0
- package/dist/server/plugins/mcp/docs-mcp/utils/api-name-matching.js +1 -0
- package/dist/server/plugins/mcp/docs-mcp/{utils.d.ts → utils/resolve-refs.d.ts} +1 -4
- package/dist/server/plugins/mcp/docs-mcp/utils/resolve-refs.js +1 -0
- package/dist/server/plugins/mcp/handlers/docs-mcp-handler.js +1 -1
- package/dist/server/plugins/mcp/handlers/handle-mcp-request.js +1 -1
- package/dist/server/plugins/mcp/index.js +1 -1
- package/dist/server/plugins/mcp/servers/mcp-server.js +2 -2
- package/dist/server/plugins/mcp/types.d.ts +2 -0
- package/dist/server/plugins/mcp/utils/xmcp-utils.d.ts +4 -0
- package/dist/server/plugins/mcp/utils/xmcp-utils.js +1 -1
- package/dist/server/plugins/mcp/utils.d.ts +11 -0
- package/dist/server/plugins/mcp/utils.js +1 -1
- package/dist/server/web-server/routes/mcp-routes/mcp-oauth.js +1 -1
- package/dist/server/web-server/utils/get-request-origin.d.ts +1 -0
- package/dist/server/web-server/utils/get-request-origin.js +1 -1
- package/package.json +7 -7
- package/dist/server/plugins/mcp/docs-mcp/utils.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @redocly/realm
|
|
2
2
|
|
|
3
|
+
## 0.137.0-next.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 1a3624bd9d8: Added recovery hints displayed when Docs MCP lookup fails.
|
|
8
|
+
- 2f5a68164c3: Fixed an issue where Docs MCP API lookups rejected names that are unique parts of API titles.
|
|
9
|
+
Names that match more than one API list the matching titles.
|
|
10
|
+
The `list-apis` filter matches API descriptions when no title matches.
|
|
11
|
+
- 3e77c0c2598: Fixed an issue where the MCP server served `http://` links behind proxies.
|
|
12
|
+
Credential, skill, and OAuth URLs respect the forwarded protocol and default to `https` for production.
|
|
13
|
+
In local development, URLs keep the `http` protocol.
|
|
14
|
+
- 6ee5565e909: Fixed MCP OAuth discovery: the authorization server metadata `issuer` matches the portal origin (RFC 8414), 401 challenges include `scope` and `authorization_uri`, and rejected bearer tokens return `error="invalid_token"`.
|
|
15
|
+
- 6d4c764d9f4: Fixed an issue where legacy `#operation` deep links to operation sub-sections (such as callbacks or responses) did not scroll correctly.
|
|
16
|
+
- 1a3624bd9d8: Fixed an issue where the Docs MCP `get-endpoint-info` tool returned operations for paths hidden with `x-mcp`.
|
|
17
|
+
- 67814ce9873: Fixed an issue in API docs where Markdown in MCP tool, resource, and prompt descriptions rendered as raw text.
|
|
18
|
+
- 126b5831557: Fixed `mcp.docs.ignore` to correctly exclude ignored content from Docs MCP search results.
|
|
19
|
+
- d811b5f6000: Fixed an issue where the **Try it** button in OpenAPI docs was displayed after a delay.
|
|
20
|
+
- Updated dependencies [6d4c764d9f4]
|
|
21
|
+
- Updated dependencies [67814ce9873]
|
|
22
|
+
- Updated dependencies [d811b5f6000]
|
|
23
|
+
- @redocly/api-docs@0.2.0-next.0
|
|
24
|
+
- @redocly/theme@0.69.0-next.0
|
|
25
|
+
- @redocly/openapi-docs@3.25.0-next.0
|
|
26
|
+
- @redocly/asyncapi-docs@1.14.0-next.0
|
|
27
|
+
- @redocly/graphql-docs@1.14.0-next.0
|
|
28
|
+
- @redocly/portal-plugin-mock-server@0.22.0-next.0
|
|
29
|
+
|
|
3
30
|
## 0.136.0
|
|
4
31
|
|
|
5
32
|
### Minor Changes
|
|
@@ -10,5 +10,5 @@ export declare function handleMcpAuth(request: Request, context: ApiFunctionsCon
|
|
|
10
10
|
}>;
|
|
11
11
|
export declare function constructUnauthorizedResponse(baseUrl: string): Response;
|
|
12
12
|
export declare function constructForbiddenResponse(): Response;
|
|
13
|
-
export declare function constructInvalidTokenResponse(): Response;
|
|
13
|
+
export declare function constructInvalidTokenResponse(baseUrl: string): Response;
|
|
14
14
|
//# sourceMappingURL=auth-handlers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{extractTokenFromAuthHeader as
|
|
1
|
+
import{extractTokenFromAuthHeader as l}from"../../../plugins/mcp/utils/jwt.js";import{SUPPORTED_MCP_SCOPES as p}from"../constants.js";import{getUserParamsFromCookies as f}from"../../../web-server/auth.js";import{ServerRoutes as a}from"../../../../constants/api.js";import{DEFAULT_ANONYMOUS_VISITOR_TEAM as c,RBAC_ALL_OTHER_TEAMS as u,RbacFeatures as h}from"../../../../constants/common.js";import{withPathPrefix as A}from"@redocly/theme/core/utils";function i(e){return!e||typeof e!="object"||Object.keys(e).length===0?!1:!(e[c]&&e[c]!=="none"||e[u]&&e[u]!=="none")}function m(e,t){if(!t||Object.keys(t).length===0)return e;const n=t.content;if(n&&Object.keys(n).length>0&&Object.values(n).some(i))return!0;const r=t.teamFoldersBaseRoles;if(i(r))return!0;const s=t.features;return i(s?.[h.MCP])?!0:e}function P(e){const t=e.getConfig().access;return m(!!t?.requiresLogin,t?.rbac||{})}async function M(e,t){const n=e.headers.get("Authorization");if(!n)return{isAuthenticated:!1};const r=l(n),s=t?.config?.ssoDirect||{},o=r?await f(s,{authorization:r}):{};return r&&o&&o.isAuthenticated?{isAuthenticated:!0,isTokenValid:!0,currentUser:{teams:o.teams||[],email:o.email||"",claims:o,isAuthenticated:!0,idpAccessToken:o.idpAccessToken||void 0,idpId:o.idpId||void 0},accessToken:r}:{isAuthenticated:!1,isTokenValid:!1}}function d(e,t){const n=`${e}${a.MCP_OAUTH_PROTECTED_RESOURCE}${A("/mcp")}`,r=`${e}${a.MCP_OAUTH_AUTHORIZATION_SERVER}`,s=t?[`error="${t.code}"`,`error_description="${t.description}"`]:[];return s.push(`resource_metadata="${n}"`,`scope="${p.join(" ")}"`,`authorization_uri="${r}"`),`Bearer ${s.join(", ")}`}function E(e){return new Response(JSON.stringify({error:"unauthorized",message:"Authentication required"}),{status:401,headers:{"Content-Type":"application/json","WWW-Authenticate":d(e),"Access-Control-Allow-Origin":"*"}})}function S(){return new Response(JSON.stringify({error:"forbidden",message:"You do not have access to this MCP server"}),{status:403,headers:{"Content-Type":"application/json","Access-Control-Allow-Origin":"*"}})}function y(e){return new Response(JSON.stringify({error:"invalid_token",message:"Invalid or expired token"}),{status:401,headers:{"Content-Type":"application/json","WWW-Authenticate":d(e,{code:"invalid_token",description:"Invalid or expired token"}),"Access-Control-Allow-Origin":"*"}})}export{S as constructForbiddenResponse,y as constructInvalidTokenResponse,E as constructUnauthorizedResponse,M as handleMcpAuth,P as isMcpContentProtected,m as shouldHandleMcpAuth};
|
|
@@ -14,6 +14,7 @@ export declare const SSE_KEEPALIVE_INTERVAL_MS = 15000;
|
|
|
14
14
|
export declare const MAX_DOCUMENTS_PER_CATEGORY = 3;
|
|
15
15
|
export declare const MCP_NOT_ALLOWED_TEMPLATE_ID = "mcp-not-allowed";
|
|
16
16
|
export declare const DEFAULT_DOCS_MCP_SERVER_NAME = "Docs MCP server";
|
|
17
|
+
export declare const SUPPORTED_MCP_SCOPES: readonly ['openid', 'profile', 'email', 'offline_access'];
|
|
17
18
|
export declare const CREATE_ANONYMOUS_SESSION_TOOL_NAME = "createAnonymousSession";
|
|
18
19
|
export declare const MANAGE_API_CREDENTIALS_TOOL_NAME = "manageApiCredentials";
|
|
19
20
|
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{LATEST_PROTOCOL_VERSION as o}from"@modelcontextprotocol/server";const
|
|
1
|
+
import{LATEST_PROTOCOL_VERSION as o}from"@modelcontextprotocol/server";const E={Docs:"docs"},r={InternalError:-32603,ServerError:-32e3,InvalidRequest:-32600,MethodNotFound:-32601,InvalidParams:-32602},_=o,t=3e4,O=15e3,n=3,s="mcp-not-allowed",S="Docs MCP server",T=["openid","profile","email","offline_access"],c="createAnonymousSession",A="manageApiCredentials";export{c as CREATE_ANONYMOUS_SESSION_TOOL_NAME,t as DEFAULT_CONNECTION_TIMEOUT_MS,S as DEFAULT_DOCS_MCP_SERVER_NAME,A as MANAGE_API_CREDENTIALS_TOOL_NAME,n as MAX_DOCUMENTS_PER_CATEGORY,s as MCP_NOT_ALLOWED_TEMPLATE_ID,_ as MCP_PROTOCOL_VERSION,r as McpErrorCodes,E as McpServerType,O as SSE_KEEPALIVE_INTERVAL_MS,T as SUPPORTED_MCP_SCOPES};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{shouldHandleMcpAuth as d}from"../auth/auth-handlers.js";import{CREATE_ANONYMOUS_SESSION_TOOL_NAME as m}from"../constants.js";import{GRAPHQL_TYPE_KINDS as o,GRAPHQL_LIST_SCOPES as h}from"./tools/graphql/constants.js";import{OPERATION_METHODS as p}from"./tools/openapi/constants.js";const i={type:"string",nullable:!0},n={limit:{type:"number"},total:{type:"number"},page:{type:"number"},totalPages:{type:"number"}},r={codemodeType:"OpenApiServer[]"},s={codemodeType:"OpenApiSecurityRequirement[]"},l={codemodeType:"Record<string, OpenApiSecurityScheme>"},y={codemodeType:"OpenApiDocument"},f={codemodeType:"OpenApiOperation & { path: string; method: string }"},g={type:"object",additionalProperties:!1,required:[],properties:{filter:{type:"string",description:"API 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 APIs per page. Default is 300",minimum:1,default:300,nullable:!0}}},a={type:"object",required:["name"],additionalProperties:!1,properties:{name:{type:"string",description:"API name",minLength:1},version:{type:"string",description:"API version",nullable:!0},filePath:{type:"string",description:"API definition file path",nullable:!0}}},b={type:"object",required:["name","path","method"],additionalProperties:!1,properties:{name:{type:"string",description:"API name",minLength:1},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:[...p.map(e=>e.toUpperCase()),...p],minLength:1},version:{type:"string",description:"API version (required when multiple APIs share the same name but differ by version)",nullable:!0},filePath:{type:"string",description:"API definition file path",nullable:!0}}},S={type:"object",required:[],additionalProperties:!1,properties:{}},v={type:"object",required:[],additionalProperties:!1,properties:{}};function I(e){const t=[{name:"list-apis",description:"Lists available APIs with their context and purpose",schema:g,annotations:{title:"List APIs",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:r,filePath:{type:"string",description:"API definition file path"}}}},...n}}},{name:"get-endpoints",description:"Get all endpoints for a specific API",schema:a,annotations:{title:"Get Endpoints",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{api:{type:"string"},version:{type:"string"},servers:r,endpoints:{type:"array",items:{type:"object",properties:{path:{type:"string"},method:{type:"string"},summary:i,description:i,security:s}}}}}},{name:"get-endpoint-info",description:"Get comprehensive information about specific endpoint including parameters, security, and examples",schema:b,annotations:{title:"Get Endpoint Info",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{api:{type:"string"},version:{type:"string"},servers:r,endpoint:f,globalSecurity:s,securitySchemes:l}}},{name:"get-security-schemes",description:"Get the security schemes for a specific API",schema:a,annotations:{title:"Get Security Schemes",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{name:i,version:i,securitySchemes:l,security:s}}},{name:"get-full-api-description",description:"Get the complete OpenAPI description",schema:a,annotations:{title:"Get Full API Description",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{api:{type:"string"},version:{type:"string"},definition:y}}},G(e.products,e.locales)];return d(e.requiresLogin,e.rbac)&&t.push({name:"whoami",description:"Get information about the currently authenticated user",schema:S,annotations:{title:"Who Am I",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.hasGraphqlApis&&t.push(...T()),e.elicitationEnabled&&t.push({name:m,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:v,annotations:{title:"Create anonymous session",readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1},outputSchema:{type:"object",required:["sessionHandle"],properties:{sessionHandle:{type:"string"}}}}),t}const H={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}}},P={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:[...o],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:[...h],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}}},A={type:"object",required:["name","type"],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},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}}},L={type:"object",required:["name"],additionalProperties:!1,properties:{name:{type:"string",description:"GraphQL schema name",minLength:1}}};function T(){return[{name:"list-graphql-apis",description:"Lists available GraphQL schemas with their context and purpose",schema:H,annotations:{title:"List GraphQL APIs",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{items:{type:"array",items:{type:"object",properties:{name:{type:"string"},description:i}}},...n}}},{name:"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:P,annotations:{title:"Get GraphQL Type List",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:o},name:{type:"string"},description:i,type:{type:"string"},args:{type:"string"},directives:{type:"string"}}}},...n}}},{name:"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:A,annotations:{title:"Get GraphQL Type",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"string"}},{name:"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:L,annotations:{title:"Get GraphQL Schema",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"string"}}]}function G(e,t){const c=e&&e.length>0,u=t&&t.length>0;return{name:"search",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},...c?{product:{type:"string",description:"Optional product name to filter search results by specific product.",enum:e,nullable:!0}}:{},...u?{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,nullable:!0}}:{}}},annotations:{title:"Search Documentation",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1},outputSchema:{type:"string"}}}export{I as getDefaultToolSchemas};
|
|
1
|
+
import{shouldHandleMcpAuth as d}from"../auth/auth-handlers.js";import{CREATE_ANONYMOUS_SESSION_TOOL_NAME as m}from"../constants.js";import{GRAPHQL_TYPE_KINDS as o,GRAPHQL_LIST_SCOPES as h}from"./tools/graphql/constants.js";import{OPERATION_METHODS as p}from"./tools/openapi/constants.js";const i={type:"string",nullable:!0},n={limit:{type:"number"},total:{type:"number"},page:{type:"number"},totalPages:{type:"number"}},r={codemodeType:"OpenApiServer[]"},s={codemodeType:"OpenApiSecurityRequirement[]"},l={codemodeType:"Record<string, OpenApiSecurityScheme>"},y={codemodeType:"OpenApiDocument"},f={codemodeType:"OpenApiOperation & { path: string; method: string }"},g={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}}},a={type:"object",required:["name"],additionalProperties:!1,properties:{name:{type:"string",description:"API name, or a unique part of it",minLength:1},version:{type:"string",description:"API version",nullable:!0},filePath:{type:"string",description:"API definition file path",nullable:!0}}},b={type:"object",required:["name","path","method"],additionalProperties:!1,properties:{name:{type:"string",description:"API name, or a unique part of it",minLength:1},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:[...p.map(e=>e.toUpperCase()),...p],minLength:1},version:{type:"string",description:"API version (required when multiple APIs share the same name but differ by version)",nullable:!0},filePath:{type:"string",description:"API definition file path",nullable:!0}}},S={type:"object",required:[],additionalProperties:!1,properties:{}},v={type:"object",required:[],additionalProperties:!1,properties:{}};function I(e){const t=[{name:"list-apis",description:"Lists available APIs with their context and purpose",schema:g,annotations:{title:"List APIs",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:r,filePath:{type:"string",description:"API definition file path"}}}},...n}}},{name:"get-endpoints",description:"Get all endpoints for a specific API",schema:a,annotations:{title:"Get Endpoints",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{api:{type:"string"},version:{type:"string"},servers:r,endpoints:{type:"array",items:{type:"object",properties:{path:{type:"string"},method:{type:"string"},summary:i,description:i,security:s}}}}}},{name:"get-endpoint-info",description:"Get comprehensive information about specific endpoint including parameters, security, and examples",schema:b,annotations:{title:"Get Endpoint Info",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{api:{type:"string"},version:{type:"string"},servers:r,endpoint:f,globalSecurity:s,securitySchemes:l}}},{name:"get-security-schemes",description:"Get the security schemes for a specific API",schema:a,annotations:{title:"Get Security Schemes",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{name:i,version:i,securitySchemes:l,security:s}}},{name:"get-full-api-description",description:"Get the complete OpenAPI description",schema:a,annotations:{title:"Get Full API Description",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{api:{type:"string"},version:{type:"string"},definition:y}}},q(e.products,e.locales)];return d(e.requiresLogin,e.rbac)&&t.push({name:"whoami",description:"Get information about the currently authenticated user",schema:S,annotations:{title:"Who Am I",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.hasGraphqlApis&&t.push(...T()),e.elicitationEnabled&&t.push({name:m,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:v,annotations:{title:"Create anonymous session",readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1},outputSchema:{type:"object",required:["sessionHandle"],properties:{sessionHandle:{type:"string"}}}}),t}const H={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}}},P={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:[...o],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:[...h],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}}},A={type:"object",required:["name","type"],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},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}}},L={type:"object",required:["name"],additionalProperties:!1,properties:{name:{type:"string",description:"GraphQL schema name",minLength:1}}};function T(){return[{name:"list-graphql-apis",description:"Lists available GraphQL schemas with their context and purpose",schema:H,annotations:{title:"List GraphQL APIs",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"object",properties:{items:{type:"array",items:{type:"object",properties:{name:{type:"string"},description:i}}},...n}}},{name:"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:P,annotations:{title:"Get GraphQL Type List",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:o},name:{type:"string"},description:i,type:{type:"string"},args:{type:"string"},directives:{type:"string"}}}},...n}}},{name:"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:A,annotations:{title:"Get GraphQL Type",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"string"}},{name:"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:L,annotations:{title:"Get GraphQL Schema",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},outputSchema:{type:"string"}}]}function q(e,t){const c=e&&e.length>0,u=t&&t.length>0;return{name:"search",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},...c?{product:{type:"string",description:"Optional product name to filter search results by specific product.",enum:e,nullable:!0}}:{},...u?{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,nullable:!0}}:{}}},annotations:{title:"Search Documentation",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1},outputSchema:{type:"string"}}}export{I as getDefaultToolSchemas};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{withPathPrefix as
|
|
1
|
+
import{removeTrailingSlash as y,withPathPrefix as g,withoutPathPrefix as E}from"@redocly/theme/core/utils";import{ServerRoutes as L}from"../../../../../../constants/api.js";import{DEFAULT_LOCALE_PLACEHOLDER as S}from"../../../../../../constants/common.js";const w=async(n,t)=>{const{query:e,product:i,locale:l}=n,u=t.config.l10n?.defaultLocale||S,h=JSON.stringify({query:e,product:i,locale:l||u}),f=`${t.baseUrl}${g(L.SEMANTIC_SEARCH)}`,o=t.accessToken?`authorization=${String(t.accessToken).replace(/^Bearer /,"")}`:"";try{const s=await fetch(f,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",...o?{Cookie:o}:{}},body:h});if(!s.ok)return{content:[{type:"text",text:"Error retrieving search results."}],isError:!0};const p=await s.json(),d=new Set(t.searchIgnoredRoutes??[]),a=(p||[]).filter(r=>!R(r.url,d));if(a.length===0)return{content:[{type:"text",text:"No results found."}]};const m=r=>{try{return new URL(r,t.baseUrl).toString()}catch{return r}},c=a.map(r=>`### [${r.title}](${m(r.url)})
|
|
2
2
|
|
|
3
|
-
${
|
|
3
|
+
${r.content}
|
|
4
4
|
`).join(`
|
|
5
5
|
|
|
6
|
-
`).trim();return{content:[{type:"text",text:
|
|
6
|
+
`).trim();return{content:[{type:"text",text:c.length?c:"No results found."}]}}catch{return{content:[{type:"text",text:"Error retrieving search results."}],isError:!0}}};function R(n,t){if(t.size===0)return!1;let e;try{e=new URL(n,"https://relative.invalid").pathname}catch{return!1}e=y(E(e));try{e=decodeURI(e)}catch{}return t.has(e)}var k={search:w};export{k as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=["get","put","post","delete","options","head","patch","trace","query","x-query"];export{e as OPERATION_METHODS};
|
|
1
|
+
const e=["get","put","post","delete","options","head","patch","trace","query","x-query"],t=new Set(e);export{t as HTTP_METHODS,e as OPERATION_METHODS};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { OpenAPIDefinition } from '@redocly/api-docs';
|
|
2
|
+
import type { McpMode } from '../../../types.js';
|
|
3
|
+
/** Methods documented and visible at `path`, uppercased. */
|
|
4
|
+
export declare function visibleMethodsAt(definition: OpenAPIDefinition, path: string): string[];
|
|
5
|
+
/** Paths with at least one visible operation. */
|
|
6
|
+
export declare function visiblePaths(definition: OpenAPIDefinition): string[];
|
|
7
|
+
/** Message for a path the definition does not document, or documents only behind `x-mcp`. */
|
|
8
|
+
export declare function describePathMiss(definition: OpenAPIDefinition, apiTitle: string, path: string, mcpMode?: McpMode): string;
|
|
9
|
+
/** Message for a visible path that does not document the requested method, or hides it. */
|
|
10
|
+
export declare function describeMethodMiss(definition: OpenAPIDefinition, apiTitle: string, path: string, method: string): string;
|
|
11
|
+
//# sourceMappingURL=endpoint-suggestions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isEndpointHidden as s}from"../../../utils/xmcp-utils.js";import{formatCandidates as i}from"../../utils/api-name-matching.js";import{HTTP_METHODS as p}from"./constants.js";import{isMcpEndpoint as a}from"./utils.js";function d(t,r){const e=t.paths?.[r];return!e||s(e,"docs")?[]:Object.entries(e).filter(([n,o])=>p.has(n.toLowerCase())&&a(o)&&!s(o,"docs")).map(([n])=>n.toUpperCase()).sort()}function f(t){return Object.keys(t.paths??{}).filter(r=>d(t,r).length>0).sort()}function $(t,r,e,n){const o=f(t);if(o.length===0)return"Endpoint not found";const h=n==="code"?"Call tools.getEndpoints for the full list, or tools.search with a keyword from the request.":"Call get-endpoints for the full list, or search with a keyword from the request.";return`No path "${e}" in "${r}". Documented paths: ${i(o)}. ${h}`}function b(t,r,e,n){const o=d(t,e);return o.length===0?"Endpoint not found":`"${e}" in "${r}" documents no ${n.toUpperCase()}. Available methods: ${i(o)}.`}export{b as describeMethodMiss,$ as describePathMiss,d as visibleMethodsAt,f as visiblePaths};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveParameters as
|
|
1
|
+
import{resolveParameters as l,resolveRequestBody as f,resolveResponses as y}from"../../utils/resolve-refs.js";import{isMcpEndpoint as x}from"./utils.js";import{describeMethodMiss as v,describePathMiss as E,visibleMethodsAt as M}from"./endpoint-suggestions.js";import{loadApiDescription as g}from"./load-api-description.js";import{checkEndpointAndDeleteXMcp as P}from"../../../utils/xmcp-utils.js";const b=async(p,a)=>{const{path:r,method:o}=p;if(!r||!o)return{content:[{type:"text",text:'Both "path" and "method" are required (e.g. path: "/tickets", method: "GET").'}],isError:!0};let e;try{e=await g(p,a)}catch(u){return{content:[{type:"text",text:u.message}],isError:!0}}const s=r.startsWith("/")?r:`/${r}`,{title:n=""}=e.info||{},i=e.paths?.[s],c=o.toLowerCase();if(!i||M(e,s).length===0)return{content:[{type:"text",text:E(e,n,s,a.mcpMode)}],isError:!0};const t=i[c];if(!x(t)||!P(t,"docs"))return{content:[{type:"text",text:v(e,n,s,o)}],isError:!0};const d=i?.parameters||[],m=t.parameters||[],h={...t,parameters:l({pathParams:d,opParams:m,definition:e}),requestBody:f(t.requestBody,e),responses:y(t.responses,e)};return{content:[{type:"text",text:JSON.stringify({api:n,version:e.info?.version||"",servers:e.servers||[],endpoint:{path:r,method:o.toUpperCase(),...h},globalSecurity:e.security||[],securitySchemes:e.components?.securitySchemes||{}},null,2)}]}};var C={"get-endpoint-info":b};export{C as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{filterApiDescriptionsByName as g}from"../../utils.js";const h=async(l,a)=>{const{filter:i,page:n=1,limit:e=300}=l;let t=Object.values(a.apiDescriptionsMap);i&&(t=g(t,i));const s=(n-1)*e,o=s+e,c=Math.ceil(t.length/e),p=t.length;return t=t.slice(s,o),t.length===0?{content:[{type:"text",text:"No APIs available"}]}:{content:[{type:"text",text:JSON.stringify({items:t.map(({relativePath:r,fetchEligible:x,...f})=>({filePath:r,...f})),limit:e,total:p,page:n,totalPages:c})}]}};var u={"list-apis":h};export{u as default};
|
|
1
|
+
import{filterApiDescriptionsByName as g}from"../../utils/api-name-matching.js";const h=async(l,a)=>{const{filter:i,page:n=1,limit:e=300}=l;let t=Object.values(a.apiDescriptionsMap);i&&(t=g(t,i));const s=(n-1)*e,o=s+e,c=Math.ceil(t.length/e),p=t.length;return t=t.slice(s,o),t.length===0?{content:[{type:"text",text:"No APIs available"}]}:{content:[{type:"text",text:JSON.stringify({items:t.map(({relativePath:r,fetchEligible:x,...f})=>({filePath:r,...f})),limit:e,total:p,page:n,totalPages:c})}]}};var u={"list-apis":h};export{u as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{findApiDescriptionByNameAndVersion as
|
|
1
|
+
import{describeAvailableApis as c,findApiDescriptionByNameAndVersion as u}from"../../utils/api-name-matching.js";import{getApiDescriptionFromFs as d}from"./utils.js";async function l({name:r,version:n,filePath:o},i){const e=o?Object.values(i.apiDescriptionsMap).find(s=>s.relativePath===o):u(i.apiDescriptionsMap,r,n);if(!e){if(o)throw new Error(`No API found with filePath "${o}". File paths are returned by the list-apis tool.`);const s=c(i.apiDescriptionsMap,r);throw new Error(s?`No API found matching "${r}". Available APIs: ${s}.`:`No API found matching "${r}".`)}const t=!!i?.config?.access?.requiresLogin,p=i?.config?.access?.rbac||{},a=await d({relativePath:e.relativePath||"",outdir:i.outdir||"",user:i.user,rbac:p,requiresLogin:t});if(!a)throw new Error(`No API found matching "${o||r}".`);return a}export{l as loadApiDescription};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import a from"node:path";import{existsSync as f}from"node:fs";import{readFile as d}from"node:fs/promises";import{replaceFileExtension as h}from"../../../../../plugins/openapi-docs/store-definition-bundles";import{PUBLIC_API_DEFINITIONS_FOLDER as u}from"../../../../../constants/common";import{filterDataByAccessDeep as l}from"../../../../../utils/rbac";import{checkEndpointAndDeleteXMcp as c}from"../../../utils/xmcp-utils.js";import{
|
|
1
|
+
import a from"node:path";import{existsSync as f}from"node:fs";import{readFile as d}from"node:fs/promises";import{replaceFileExtension as h}from"../../../../../plugins/openapi-docs/store-definition-bundles";import{PUBLIC_API_DEFINITIONS_FOLDER as u}from"../../../../../constants/common";import{filterDataByAccessDeep as l}from"../../../../../utils/rbac";import{checkEndpointAndDeleteXMcp as c}from"../../../utils/xmcp-utils.js";import{HTTP_METHODS as y}from"./constants.js";function P(t){return typeof t=="object"&&t!==null&&"responses"in t&&typeof t.responses=="object"}function S(t){const{paths:n={}}=t,e=[];for(const[r,i]of Object.entries(n)){const s=!c(i,"docs");for(const[p,o]of Object.entries(i))y.has(p.toLowerCase())&&(s||!c(o,"docs")||e.push({path:r,method:p.toUpperCase(),summary:o.summary,description:o.description,security:o.security}))}return e}async function T({relativePath:t,outdir:n,user:e,rbac:r={},requiresLogin:i=!1}){const s=a.join(n||"",u,h(t,".json"));if(!f(s))return;const o=await d(s,"utf-8"),m=JSON.parse(o);return l(m,{isAuthenticated:e?.isAuthenticated,email:e?.email,teams:e?.teams},r,i)}export{T as getApiDescriptionFromFs,S as getEndpointsFromPaths,P as isMcpEndpoint};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ApiDescriptionInfo } from '../../types.js';
|
|
2
|
+
export declare const MAX_LISTED_CANDIDATES = 15;
|
|
3
|
+
export declare function formatCandidates(items: string[]): string;
|
|
4
|
+
/** Every API name in the catalog, closest to `name` first. Used to make a miss recoverable. */
|
|
5
|
+
export declare function describeAvailableApis(apiDescriptionsMap: Record<string, ApiDescriptionInfo>, name: string): string;
|
|
6
|
+
export declare function findApiDescriptionByNameAndVersion(apiDescriptionsMap: Record<string, ApiDescriptionInfo>, name: string, version?: string): ApiDescriptionInfo | undefined;
|
|
7
|
+
export declare function filterApiDescriptionsByName(apiDescriptions: ApiDescriptionInfo[], filter: string): ApiDescriptionInfo[];
|
|
8
|
+
//# sourceMappingURL=api-name-matching.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const $=15,p=new Set(["api","apis","rest","restful","openapi","graphql","spec","specification","service","services"]);function w(s){return s.toLowerCase().split(/[^a-z0-9_]+/).filter(Boolean)}function f(s){return new Set(w(s).filter(t=>!p.has(t)))}function d(s,t){return s.size===t.size&&[...s].every(n=>t.has(n))}function h(s){const t=s.slice(0,15),n=s.length-t.length;return n>0?`${t.join(", ")} (+${n} more)`:t.join(", ")}function v(s,t){const n=f(t);return[...new Set(s.map(e=>e.name))].map(e=>({name:e,score:[...f(e)].filter(r=>n.has(r)).length})).sort((e,r)=>r.score-e.score||e.name.localeCompare(r.name)).map(({name:e})=>e)}function g(s,t){const n=Object.values(s);return n.length===0?"":h(v(n,t).map(e=>`"${e}"`))}function T(s,t){const n=t.trim().toLowerCase();if(!n)return{matches:[],isExactTitle:!1};const e=s.map(a=>({api:a,title:a.name.trim().toLowerCase()})),r=a=>a.map(({api:o})=>o),i=e.filter(({title:a})=>a===n);if(i.length>0)return{matches:r(i),isExactTitle:!0};const l=e.filter(({title:a})=>a.includes(n));if(l.length>0)return{matches:r(l),isExactTitle:!1};const m=f(n);if(m.size===0)return{matches:[],isExactTitle:!1};const u=e.filter(({title:a})=>{const o=f(a);return o.size>0&&d(o,m)});return{matches:r(u),isExactTitle:!1}}function A(s,t,n){if(!t)return;const{matches:e,isExactTitle:r}=T(Object.values(s),t);if(e.length===0)return;if(!r){const o=[...new Set(e.map(({name:c})=>c))];if(o.length>1)throw new Error(`"${t}" matches ${o.length} APIs: ${h(o.map(c=>`"${c}"`))}. Pass one of those names exactly, or the "filePath" parameter from list-apis.`)}const i=n?e.filter(o=>o.version===n):e;if(i.length===0){const o=[...new Set(e.map(({version:c})=>c||"(no version)"))];throw new Error(`"${e[0].name||t}" has no version "${n}". Available versions: ${h(o)}.`)}if(i.length===1)return i[0];const l=i[0].name||t;if(new Set(i.map(({version:o})=>o||"")).size>1){const o=i.map(({version:c})=>c||"(no version)").join(", ");throw new Error(`Multiple versions of "${l}" found: ${o}. Specify a version to disambiguate.`)}const u=i.map(({relativePath:o})=>`"${o||"(unknown path)"}"`).join(", "),a=i[0].version?` at version "${i[0].version}"`:"";throw new Error(`Multiple APIs are titled "${l}"${a}. Pass the "filePath" parameter to select one of: ${u}.`)}function E(s,t){const n=t.trim().toLowerCase();if(!n)return s;const e=s.filter(({name:r})=>r.toLowerCase().includes(n));return e.length>0?e:s.filter(({description:r})=>!!r?.toLowerCase().includes(n))}export{$ as MAX_LISTED_CANDIDATES,g as describeAvailableApis,E as filterApiDescriptionsByName,A as findApiDescriptionByNameAndVersion,h as formatCandidates};
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import type { OpenAPISchema, OpenAPIParameter, OpenAPIResponse, OpenAPIRequestBody, Referenced, OpenAPIDefinition } from '@redocly/api-docs';
|
|
2
|
-
import type { ApiDescriptionInfo } from '../types.js';
|
|
3
|
-
export declare function findApiDescriptionByNameAndVersion(apiDescriptionsMap: Record<string, ApiDescriptionInfo>, name: string, version?: string): ApiDescriptionInfo | undefined;
|
|
4
|
-
export declare function filterApiDescriptionsByName(apiDescriptionsMap: ApiDescriptionInfo[], filter: string): ApiDescriptionInfo[];
|
|
5
2
|
export declare function resolveRef<T>(ref: string, def?: OpenAPIDefinition): T | undefined;
|
|
6
3
|
type ResolveSchemaRefsParams = {
|
|
7
4
|
schema: Referenced<OpenAPISchema> | undefined;
|
|
@@ -20,4 +17,4 @@ export declare function resolveParameters({ pathParams, opParams, definition, }:
|
|
|
20
17
|
export declare function resolveRequestBody(requestBody: Referenced<OpenAPIRequestBody> | undefined, definition: OpenAPIDefinition): OpenAPIRequestBody | undefined;
|
|
21
18
|
export declare function resolveResponses(responses: Record<string, Referenced<OpenAPIResponse>>, definition: OpenAPIDefinition): Record<string, OpenAPIResponse>;
|
|
22
19
|
export {};
|
|
23
|
-
//# sourceMappingURL=
|
|
20
|
+
//# sourceMappingURL=resolve-refs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function $(e,o){if(!o||!e?.startsWith("#/"))return;const n=e.slice(2).split("/");let t=o;for(const r of n)if(typeof t=="object"&&t!==null&&r in t)t=t[r];else return;return t}function m(e,o){const n=Object.fromEntries(Object.entries(e).filter(([t,r])=>t!=="$ref"&&r!==void 0));return{...o,...n}}function l({schema:e,definition:o,visitedRefs:n=new Set,maxDepth:t=50,currentDepth:r=0}){if(!e)return;if(r>=t)return{type:"object",description:`Maximum resolution depth exceeded (${t})`};if("$ref"in e&&e.$ref){if(n.has(e.$ref))return{type:"object",description:`Circular reference detected: ${e.$ref}`,"x-circular-ref":e.$ref};const f=new Set(n);f.add(e.$ref);const i=$(e.$ref,o);if(!i)return{type:"object",description:`Could not resolve reference: ${e.$ref}`,"x-unresolved-ref":e.$ref};const u=l({schema:i,definition:o,visitedRefs:f,maxDepth:t,currentDepth:r+1});return u&&m(e,u)}const s={...e};if(s.properties){const f={};for(const[i,u]of Object.entries(s.properties)){const d=l({schema:u,definition:o,visitedRefs:new Set(n),maxDepth:t,currentDepth:r+1});f[i]=d||u}s.properties=f}if(s.items&&typeof s.items=="object")if(Array.isArray(s.items))s.items=s.items.map(f=>l({schema:f,definition:o,visitedRefs:new Set(n),maxDepth:t,currentDepth:r+1})||f);else{const f=l({schema:s.items,definition:o,visitedRefs:new Set(n),maxDepth:t,currentDepth:r+1});f&&(s.items=f)}return["allOf","anyOf","oneOf"].forEach(f=>{const i=s[f];Array.isArray(i)&&(s[f]=i.reduce((u,d)=>{const p=l({schema:d,definition:o,visitedRefs:new Set(n),maxDepth:t,currentDepth:r+1});return p&&u.push(p),u},[]))}),s}function a({pathParams:e,opParams:o,definition:n}){return[...e,...o].reduce((t,r)=>{if(!r)return t;if("$ref"in r&&r.$ref){const s=$(r.$ref,n);if(!s)return t;const c=m(r,s);return t.push({...c,schema:"schema"in c&&c.schema?l({schema:c.schema,definition:n}):void 0}),t}return t.push({...r,schema:"schema"in r&&r.schema?l({schema:r.schema,definition:n}):void 0}),t},[])}function v(e,o){const n={...e};if(e.example!==void 0&&(n.example=e.example),!e.examples)return n;const t={};for(const[r,s]of Object.entries(e.examples))if("$ref"in s){const c=$(s.$ref,o);c&&(t[r]=c)}else t[r]=s;return n.examples=t,n}function h(e,o){if(!e)return;const n="$ref"in e&&e.$ref?$(e.$ref,o):e;if(!n)return;const t=m(e,n),r={...t};if("content"in t&&t.content){const s={};for(const[c,f]of Object.entries(t.content))s[c]={...v(f,o),schema:f.schema?l({schema:f.schema,definition:o}):void 0};r.content=s}return r}function b(e,o){return Object.entries(e).reduce((n,[t,r])=>{const s="$ref"in r&&r.$ref?$(r.$ref,o):r;if(!s)return n;const c=m(r,s),f={...c};if("content"in c&&c.content){const i={};for(const[u,d]of Object.entries(c.content))i[u]={...v(d,o),schema:d.schema?l({schema:d.schema,definition:o}):void 0};f.content=i}return n[t]=f,n},{})}export{a as resolveParameters,$ as resolveRef,h as resolveRequestBody,b as resolveResponses,l as resolveSchemaRefs};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{logger as
|
|
1
|
+
import{logger as A}from"../../../tools/notifiers/logger.js";import{getFeedbackClientIp as v}from"../../../web-server/utils/feedback-ip.js";import{getForwardedOrigin as f}from"../../../web-server/utils/get-request-origin.js";import{GATEWAY_FETCH_LIMIT_DEFAULTS as N}from"../../../../constants/common.js";import{isObject as _}from"../../../../utils/guards/is-object.js";import{getAccessibleSkills as k}from"../../../utils/skills/get-accessible-skills.js";import{getSkillUrl as B}from"../../../utils/skills/get-skill-url.js";import{createDocsMcpServer as P}from"../servers/docs-server.js";import{filterApiDescriptionsByRbac as H,filterGraphqlDescriptionsByRbac as L}from"../utils.js";import{createMethodNotAllowedError as j,withErrorHandling as V}from"./errors.js";import{constructForbiddenResponse as G,constructInvalidTokenResponse as J,constructUnauthorizedResponse as $,handleMcpAuth as z,shouldHandleMcpAuth as K}from"../auth/auth-handlers.js";import{extractTokenFromAuthHeader as Y}from"../utils/jwt.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as W}from"../constants.js";import{canAccessFeature as X}from"../../../utils/rbac.js";import{DEFAULT_ANONYMOUS_VISITOR_TEAM as Q,RbacFeatures as Z}from"../../../../constants/common.js";const m="X-Redocly-AI-Metadata";function q(r){if(r)try{const e=JSON.parse(r);if(_(e))return e;A.warn(`Ignoring ${m} header: not a JSON object.`)}catch{A.warn(`Ignoring ${m} header: invalid JSON.`)}}const x=async(r,e,o)=>{const t=!!e?.config?.access?.requiresLogin,i=e?.config?.access?.rbac||{},g=o?.props?.config?.mcpMode??"tools",M=o?.props?.config?.gatewayFetchBudgetPerExec??N.budgetPerExec,E=o?.props?.config?.searchIgnoredRoutes??[];let d;const h=r.headers.get("Authorization");if(K(t,i)){const{isAuthenticated:l,isTokenValid:s,currentUser:a,accessToken:u}=await z(r,e);if(!l){const p=f(r);return s===!1?J(p):$(p)}a&&(e.user=a),d=u}else h&&(d=Y(h||"")??void 0);const T={isAuthenticated:!!e.user?.isAuthenticated,teams:e.user?.teams||[Q],claims:e.user?.claims??{}};if(!X(Z.MCP,T,i,t))return G();let c;return await V(async()=>{const l=o?.props?.config?.elicitationEnabled??!1;if(r.method==="GET")return new Response(JSON.stringify({error:"Method Not Allowed",message:`In order to use this MCP server, you need to register it in your MCP Client (VS Code, Cursor, Claude Code, etc.) using this URL: ${r.url}`}),{status:405,headers:{"Content-Type":"application/json"}});if(r.method!=="POST")return j();const s=o,a=s?.props?.config?.apiDescriptionsMap||{},u=s?.props?.config?.graphqlDescriptions||[],p=s?.props?.config?.mcpDocsServerName||W,{config:{mcp:b={}}}=e,D=H(a,e.user,i,t),R=L(u,e.user,i,t),S=e.config.products?Object.values(e.config.products).map(n=>n?.name):[],y=b.docs?.name||p,w=q(r.headers.get(m)),C=s?.props?.tools||[],I=s?.props?.skillsBaseUrl||f(r),O=k(s?.props?.skills||[],e.user,i,t).map(n=>({slug:n.slug,name:n.name,description:n.description,content:n.content,uri:B(I,n.slug)})),U=await r.json(),F={name:y,tools:C,skills:O,context:{...e,accessToken:d,outdir:e.outdir||"",baseUrl:e.baseUrl||f(r),apiDescriptionsMap:D,graphqlDescriptions:R,products:S,metadata:w,mcpMode:g,elicitationEnabled:l,gatewayFetchBudgetPerExec:M,searchIgnoredRoutes:E,clientIp:v(r)}};return c=await P({...F,getKv:e.getKv}),await ee(r,c,U)},async()=>{c&&(await c.cleanup(),c=void 0)},g)};async function ee(r,e,o){return e.transport.handleRequest(r,o!==void 0?{parsedBody:o}:void 0)}var Ae=x;export{Ae as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{withPathPrefix as g}from"@redocly/theme/core/utils";import{importApiRoutesHandler as D}from"../../../api-routes/import-api-routes-handlers.js";import{enhanceContext as h}from"../../../api-routes/helpers/enhance-context.js";import{telemetry as
|
|
1
|
+
import{withPathPrefix as g}from"@redocly/theme/core/utils";import{importApiRoutesHandler as D}from"../../../api-routes/import-api-routes-handlers.js";import{enhanceContext as h}from"../../../api-routes/helpers/enhance-context.js";import{telemetry as A}from"../../../telemetry/index.js";import{KvService as E}from"../../../persistence/kv/services/kv-service.js";import{envConfig as l}from"../../../config/env-config.js";import{acceptsHtml as R}from"../../../utils/ai-agent-detection.js";import{handleErrorPageRender as T}from"../../../web-server/utils.js";import{MCP_NOT_ALLOWED_TEMPLATE_ID as _}from"../constants.js";import{MCP_DOCS_SERVER_HANDLER_ID as w}from"../index.js";import{resolvePublicBaseUrl as S}from"../../../web-server/utils/resolve-public-base-url.js";import{getForwardedOrigin as q}from"../../../web-server/utils/get-request-origin.js";import{applyEdgeRateLimit as O}from"../gateway-mcp/rate-limit/apply-edge-rate-limit.js";async function K(a,t,e){const n=t.req.raw;if(a.requestHandlerId===w&&n.method==="GET"&&R(n.headers.get("accept"))&&e.globalData.mcpData?.docs?.enabled)return T(t,e,{slug:a.slug},405,_);const r=t.get("auth"),o=await O(n,e);if(o)return o;const s=await e.resolveRouteStaticData(a)||{},m=g(a.slug),{requestHandlers:p}=await D(e.serverOutDir),c=p[a.requestHandlerId],d=(await c()).default,u=async()=>await E.getInstance({baseDbDir:e.serverOutDir,sqldRemoteDatabaseUrl:l.SQLD_REMOTE_DATABASE_URL,sqldRemoteDatabaseAuthToken:l.SQLD_REMOTE_DATABASE_AUTH_TOKEN}),f=h({honoCtx:t,ctx:{user:{teams:r.teams,email:r.claims.email,claims:r.claims,idpAccessToken:r.idpAccessToken,idpId:r.claims.idpId,isAuthenticated:r.isAuthenticated},config:e.config,outdir:e.outdir,baseUrl:q(t.req.raw)},telemetry:A,getKv:u}),i=await d(t.req.raw,f,{...s,props:{...s.props,routeSlug:m,outdir:e.outdir,skills:e.config.skills?.hide===!0?[]:e.getSkills(),skillsBaseUrl:S(e,t)}});return i instanceof Response?i:typeof i=="string"?new Response(i,{headers:{"Content-Type":"text/plain"}}):new Response(JSON.stringify(i),{headers:{"Content-Type":"application/json"}})}export{K as handleMcpRequest};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{fileURLToPath as
|
|
1
|
+
import{fileURLToPath as q}from"node:url";import{dirname as I,join as p}from"node:path";import{logger as l}from"../../tools/notifiers/logger.js";import{reporter as G}from"../../tools/notifiers/reporter.js";import{isDefined as N}from"../../../utils/guards/is-defined.js";import{bundledOpenApiDocumentsToMcpOpenApi as H,bundledGraphqlDocumentsToMcpOpenApi as U,buildGraphqlDescriptions as j,collectIgnoredRoutes as x,filterIgnoredApiDescriptions as _,getCleanedUpApiDescriptions as F,isMcpInRedirects as B}from"./utils.js";import{getMcpMode as V,isMcpElicitationEnabled as J}from"./utils/mode.js";import{getDefaultToolSchemas as W}from"./docs-mcp/tool-schemas.js";import{EXECUTE_TOOL_SCHEMA as X}from"./codemode/tools/execute-schema.js";import{isGatewayCapabilityEnabled as $,isGatewayEligible as k}from"./gateway-mcp/eligibility.js";import{resolveGatewayFetchLimits as z}from"./gateway-mcp/rate-limit/limits.js";import{telemetryTraceStep as K}from"../../../cli/telemetry/helpers/trace-step.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as v,MCP_NOT_ALLOWED_TEMPLATE_ID as Q}from"./constants.js";const y="mcp-docs-server-handler",Y="/mcp",A="mcp-credentials-handler",Z="/mcp/credentials",m=I(q(import.meta.url));async function ee(){let o=!1;return{id:"mcp",async processContent(t,r){o||await K("com.redocly.build.plugin.mcp",async a=>{const n=H(await t.loadApiDocuments(r)),f=U(await t.loadApiDocuments(r)),s=await r.getConfig(),{mcp:i,redirects:d}=s;if(a?.setAttribute("config",JSON.stringify(i||{})),!(i?.hide||i?.docs?.hide)&&(B(d)&&await G.panicOnBuildContentError('The reserved route "/mcp" cannot be used in redirect configuration. Please remove any redirects involving "/mcp" from your redocly.yaml config.'),t.createTemplate(Q,"@redocly/theme/layouts/McpNotAllowed"),!i?.docs?.hide)){l.info("Configuring MCP servers...");const u=p(m,"./handlers/docs-mcp-handler.js"),c=i?.docs?.ignore||[],h=F(_(n,c)),g=!!s?.access?.requiresLogin,D=s?.access?.rbac||{},R=$(i),E=new Map;for(const e of h){const L=e.definition.info?.title||"",w=e.definition.info?.version||"";E.set(e.relativePath,{name:L,description:typeof e.definition.info?.description=="string"?e.definition.info.description:"",version:w,servers:e.definition.servers||[],relativePath:e.relativePath,...R&&k(e.definition.info,e.relativePath,D)?{fetchEligible:!0}:{}})}const M=j(_(f,c)),b=Object.values(s.products||{}).map(e=>e?.name).filter(N),S=s.l10n?.locales?.map(e=>e.code)??r.fs.localeFolders,C=V(),P=J(),{budgetPerExec:T}=z(),O=W({products:b,locales:S,requiresLogin:g,rbac:D,hasGraphqlApis:M.length>0,elicitationEnabled:P});t.addMcpTools(p(m,"./docs-mcp/tools/index.js"),O),C==="code"&&t.addMcpTools(p(m,"./codemode/tools/execute.js"),[X]),await te({actions:t,handlerPath:u,apiDescriptionsMap:Object.fromEntries(E),graphqlDescriptions:M,mcpConfig:i,mcpMode:C,elicitationEnabled:P,gatewayFetchBudgetPerExec:T}),o=!0}})},async afterRoutesCreated(t,r){const a=await r.getConfig(),{mcp:n}=a;t.setGlobalData({mcpData:{docs:{enabled:!n?.hide&&!n?.docs?.hide,name:n?.docs?.name||v}}})}}}var ge=ee;async function te({actions:o,handlerPath:t,apiDescriptionsMap:r,graphqlDescriptions:a,mcpConfig:n,mcpMode:f,elicitationEnabled:s,gatewayFetchBudgetPerExec:i}){if(o.createRequestHandler(y,t),o.addApiRoute({slug:Y,requestHandlerId:y,httpMethod:"all",getStaticData:async(d,u)=>{const c=x(u.getAllRoutes(),n?.docs?.ignore||[]);return c.length>0&&l.verbose(`mcp.docs.ignore: hiding ${c.length} page routes from MCP search`),{props:{config:{apiDescriptionsMap:r,graphqlDescriptions:a,mcpDocsServerName:n?.docs?.name||v,mcpMode:f,elicitationEnabled:s,gatewayFetchBudgetPerExec:i,searchIgnoredRoutes:c},serverOutDir:o.serverOutDir,tools:o.getMcpTools().map(({importPath:h,...g})=>g)}}}}),l.info("Registered Docs MCP Server endpoint at /mcp"),s){const d=p(m,"./handlers/mcp-credentials-handler.js");o.createRequestHandler(A,d),o.addApiRoute({slug:Z,requestHandlerId:A,httpMethod:"all",getStaticData:async()=>({props:{}})}),l.info("Registered MCP credentials endpoint at /mcp/credentials")}}export{A as MCP_CREDENTIALS_HANDLER_ID,y as MCP_DOCS_SERVER_HANDLER_ID,ge as default,ee as mcpServerPlugin};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{McpServer as T,WebStandardStreamableHTTPServerTransport as q}from"@modelcontextprotocol/server";import{logger as g}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as
|
|
1
|
+
import{McpServer as T,WebStandardStreamableHTTPServerTransport as q}from"@modelcontextprotocol/server";import{logger as g}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as I,MCP_TOOL_WORKER_KEY as v}from"../../../workers/mcp-tool-worker-pool.js";import{SSE_KEEPALIVE_INTERVAL_MS as R}from"../constants.js";import{secretStore as _}from"../gateway-mcp/secret-store.js";import{readAuthRequiredMeta as A,stripAuthRequiredMeta as b}from"../gateway-mcp/upstream-auth.js";import{buildCredentialsToolHint as O,buildUpstreamCredentialFallback as w,buildUpstreamCredentialInputRequired as U,isCredentialElicitationDeclined as C,targetOf as D}from"../gateway-mcp/elicitation.js";import{resolveUpstreamAuthScheme as j}from"../gateway-mcp/upstream-auth-scheme.js";import{MISSING_SESSION_HANDLE_MESSAGE as K,resolveSecretSubject as P}from"../gateway-mcp/subject.js";const L=6e4,S=new Map;function $(e,t){const r=S.get(e);if(r)return r;const n=t().finally(()=>{S.delete(e)});return S.set(e,n),n}function ee(e,t){const r=new T({name:e,version:new Date().toISOString().slice(0,10)},{capabilities:{logging:{}},...t?{instructions:t}:{}}),n=new q({keepAliveMs:R,sessionIdGenerator:void 0});return{server:r,transport:n}}async function te(e,t){return await e.connect(t),{server:e,transport:t,cleanup:async()=>{t.close()}}}function re(e,t){return r=>async(n,a)=>{g.info(`MCP tool called: ${r}`);const i=e.elicitationEnabled??!1,s=a.mcpReq.envelope!==void 0,c=i?P(e.user,n.sessionHandle):{kind:"anonymous"},o=c.kind==="resolved"?c.subject:void 0;if(c.kind==="invalid")return{content:[{type:"text",text:c.message}],isError:!0};const d=async()=>{if(!(!i||!o||!t?.getKv))try{const u=await t.getKv();return await _.getSecretsForSubject(u,o)}catch(u){g.warn(`gateway mcp secret store error, continuing unauthenticated: ${String(u)}`);return}};let m;const E=async()=>{m=await d();const u={toolName:r,args:n,context:{...e,upstreamSecrets:m},extra:N(a)};return await I.exec(v,[u],{timeout:L})};let p=await E();const l=A(p);if(l.length>0&&i&&!o)return b({...p,content:[{type:"text",text:K}],isError:!0});if(l.length>0&&i&&o&&t?.getKv){g.info(`gateway mcp credentials prompt: era=${s?"2026-07-28":"2025"} clientElicitation=${H(t)}`);const u=await t.getKv(),h=new Set(Object.keys(m??{})),f=s?await x(e,a,l,o,u,h,t):await B(e,l,o,u,h);switch(f.kind){case"inputRequired":return f.result;case"retry":p=await E();break;case"prependText":p={...p,content:[{type:"text",text:f.text},...p.content],isError:!0};break;case"none":break}}return b(p)}}function F(e){return!!e?.getClientCapabilities()?.elicitation?.url}function H(e){const t=e?.getClientCapabilities()?.elicitation;if(!t)return"none";const r=[t.form?"form":void 0,t.url?"url":void 0].filter(Boolean);return r.length>0?r.join("+"):"none"}function k(e,t){return e.status===401&&!t.has(e.api)}function y(e){return e.map(O).join(`
|
|
2
2
|
|
|
3
|
-
`)}async function M(e,t,r,n){const[a,...i]=t;return{subject:r,baseUrl:e.baseUrl??"",target:D(a),auth:await j(e,a.api),kv:n,hasExistingSecret:!1,reason:{kind:"authFailure",status:a.status,otherApiNames:i.map(s=>s.name)}}}async function
|
|
3
|
+
`)}async function M(e,t,r,n){const[a,...i]=t;return{subject:r,baseUrl:e.baseUrl??"",target:D(a),auth:await j(e,a.api),kv:n,hasExistingSecret:!1,reason:{kind:"authFailure",status:a.status,otherApiNames:i.map(s=>s.name)}}}async function x(e,t,r,n,a,i,s){if(C(t.mcpReq.inputResponses))return{kind:"none"};const c=r.filter(d=>k(d,i));if(c.length===0)return{kind:"prependText",text:y(r)};const o=await M(e,c,n,a);return F(s)?{kind:"inputRequired",result:await U(o)}:{kind:"prependText",text:await w(o)}}async function B(e,t,r,n,a){const i=t.filter(o=>k(o,a));if(i.length===0)return{kind:"prependText",text:y(t)};const s=await M(e,i,r,n);return{kind:"prependText",text:await $(`${r}:${i[0].api}`,()=>w(s))}}function N(e){return{sessionId:e.sessionId,authInfo:e.http?.authInfo,requestId:e.mcpReq.id,_meta:e.mcpReq._meta}}function ne(e){return{user:e.user,config:e.config,outdir:e.outdir,baseUrl:e.baseUrl,params:e.params,query:e.query,cookies:e.cookies,apiDescriptionsMap:e.apiDescriptionsMap,graphqlDescriptions:e.graphqlDescriptions,products:e.products,accessToken:e.accessToken,metadata:e.metadata,upstreamAuthorization:e.upstreamAuthorization,mcpMode:e.mcpMode,elicitationEnabled:e.elicitationEnabled,gatewayFetchBudgetPerExec:e.gatewayFetchBudgetPerExec,clientIp:e.clientIp,upstreamSecrets:e.upstreamSecrets,searchIgnoredRoutes:e.searchIgnoredRoutes}}export{te as connectMcpServer,ee as createMcpServer,re as createToolDispatch,ne as extractSerializableContext};
|
|
@@ -28,6 +28,7 @@ export type McpDocsStaticDataPropsConfig = {
|
|
|
28
28
|
mcpMode: McpMode;
|
|
29
29
|
elicitationEnabled: boolean;
|
|
30
30
|
gatewayFetchBudgetPerExec: number;
|
|
31
|
+
searchIgnoredRoutes?: string[];
|
|
31
32
|
};
|
|
32
33
|
export type McpDocsStaticData = PageStaticData & {
|
|
33
34
|
props: {
|
|
@@ -98,6 +99,7 @@ export type McpToolContext = Omit<ApiFunctionsContext, 'telemetry' | 'getKv' | k
|
|
|
98
99
|
gatewayFetchBudgetPerExec?: number;
|
|
99
100
|
clientIp?: string;
|
|
100
101
|
upstreamSecrets?: Record<string, StoredSecret>;
|
|
102
|
+
searchIgnoredRoutes?: string[];
|
|
101
103
|
};
|
|
102
104
|
export type McpToolHandler<TArgs extends ToolArgs = ToolArgs> = (args: TArgs, context: McpToolContext, extra: McpToolExtra) => Promise<McpToolWorkerResponse>;
|
|
103
105
|
export type McpToolIsAvailable = (context: McpToolContext) => boolean | Promise<boolean>;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { OpenAPIDefinition } from '@redocly/api-docs';
|
|
2
2
|
import type { XMcpConfig, XMcpSection } from '../types.js';
|
|
3
|
+
/** Whether the path or operation is hidden for `section`. */
|
|
4
|
+
export declare function isEndpointHidden(endpoint: {
|
|
5
|
+
'x-mcp'?: XMcpConfig;
|
|
6
|
+
} | undefined, section: XMcpSection): boolean;
|
|
3
7
|
/**
|
|
4
8
|
* Returns false when the endpoint is hidden for `section`; otherwise strips x-mcp from the
|
|
5
9
|
* output and returns true.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function n(e,t){return!!e?.["x-mcp"]?.[t]?.hide}function i(e,t){return n(e,t)?!1:(e&&delete e["x-mcp"],!0)}function o(e,t){if(!e)return!1;const r=e["x-mcp"];return!r||typeof r!="object"?!1:t?r[t]?.hide===!0:r.docs?.hide===!0}function c(e){if(!e)return!1;const t=e["x-mcp"];return!!(t&&typeof t=="object"&&t.gateway)}export{i as checkEndpointAndDeleteXMcp,c as hasGatewayConfig,o as isDefinitionHidden,n as isEndpointHidden};
|
|
@@ -55,6 +55,17 @@ export declare function buildGraphqlDescriptions(definitions: McpGraphqlSpec[]):
|
|
|
55
55
|
export declare function filterIgnoredApiDescriptions<T extends {
|
|
56
56
|
relativePath: string;
|
|
57
57
|
}>(apiDescriptions: T[], ignoredPaths: string[]): T[];
|
|
58
|
+
/**
|
|
59
|
+
* Registered page routes whose source file `mcp.docs.ignore` matches, as exact route slugs
|
|
60
|
+
* for the `search` tool to drop. The catalog tools filter definitions by path; `search` reads
|
|
61
|
+
* the site-wide semantic index, which only knows page URLs — so the affected routes are looked
|
|
62
|
+
* up in the route table at static-data time rather than derived from file paths (a derived
|
|
63
|
+
* slug bypasses the content-slugs transforms and cannot see operation pages).
|
|
64
|
+
*/
|
|
65
|
+
export declare function collectIgnoredRoutes(routes: {
|
|
66
|
+
fsPath?: string;
|
|
67
|
+
slug: string;
|
|
68
|
+
}[], ignoredPaths: string[]): string[];
|
|
58
69
|
/**
|
|
59
70
|
* Checks if the MCP route (/mcp) appears in redirect configuration.
|
|
60
71
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import u,{join as l}from"node:path";import s from"picomatch";import{PUBLIC_API_DEFINITIONS_FOLDER as m,L10N_DIR_NAME as p,I18N_DIR_NAME as c}from"../../constants/common.js";import{RESERVED_ROUTES as h}from"../../plugins/get-reserved-routes.js";import{removeL10nPrefix as D}from"../../fs/utils/remove-l10n-prefix.js";import{removeTrailingSlash as g}from"../../../utils/url/remove-trailing-slash.js";import{slash as A}from"../../../utils/path/slash.js";import{canDownloadApiDefinition as d}from"../../utils/rbac.js";function T(e){return e.filter(t=>t.type==="openapi").map(t=>({relativePath:t.relativePath,definition:t.document}))}function W(e){return e.filter(t=>t.type==="graphql").map(t=>({relativePath:t.relativePath,definition:t.document,options:t.options}))}function f(e){if(typeof e!="object"||e===null)return e;if(Array.isArray(e))return e.map(f);const t={};for(const i in e)if(Object.hasOwn(e,i)){if(i.toString().startsWith("x-parsed-"))continue;t[i]=f(e[i])}return t}function b(e){return e.reduce((t,i)=>(i.relativePath.startsWith(p)||i.relativePath.startsWith(c)||!i.definition||typeof i.definition!="object"||!("paths"in i.definition)||i.definition?.paths&&Object.keys(i.definition.paths).length>0&&t.push({...i,definition:f(i.definition)}),t),[])}function M(e,t,i,r){const n={};for(const[o,a]of Object.entries(e))d(l(m,a.relativePath),i,r,t)&&(n[o]=a);return n}function N(e,t,i,r){return e.filter(n=>d(l(m,n.relativePath),i,r,t))}function S(e){return e.filter(({relativePath:t})=>!t.startsWith(p)&&!t.startsWith(c)).map(({relativePath:t,options:i})=>({name:i.info?.title||u.basename(t,u.extname(t)),description:i.info?.description,relativePath:t}))}function v(e,t){const i=s(t||[]);return e.filter(({relativePath:r})=>!i(r))}function k(e,t){if(!t?.length)return[];const i=s(t),r=e.filter(({fsPath:n})=>{if(!n)return!1;const o=A(n);return i(o)||i(D(o))}).map(({slug:n})=>g(n));return[...new Set(r)].sort()}function q(e={}){for(const[t,i]of Object.entries(e))if(t===h.mcp||(typeof i=="string"?i:i.to)===h.mcp)return!0;return!1}function w(e,t,i){return e.startsWith(p)||e.startsWith(c)||!t?.paths||Object.keys(t.paths).length===0?!1:!s(i?.docs?.ignore||[])(e)}export{S as buildGraphqlDescriptions,W as bundledGraphqlDocumentsToMcpOpenApi,T as bundledOpenApiDocumentsToMcpOpenApi,k as collectIgnoredRoutes,M as filterApiDescriptionsByRbac,N as filterGraphqlDescriptionsByRbac,v as filterIgnoredApiDescriptions,b as getCleanedUpApiDescriptions,w as isApiIncludedInDocsMcp,q as isMcpInRedirects};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getCookie as A}from"hono/cookie";import{createHash as J,timingSafeEqual as V}from"node:crypto";import{ulid as Y}from"ulid";import{isMcpContentProtected as P}from"../../../plugins/mcp/auth/auth-handlers.js";import{isRedoclySso as Z,isOidcProviderConfig as v}from"../../auth.js";import{AUTH_URL as R,JWT_SECRET_KEY as m,MCP_ACCESS_TOKEN_TTL_SEC as b,MCP_REFRESH_SESSION_LIFETIME_SEC as W}from"../../../constants/common.js";import{ServerRoutes as T}from"../../../../constants/api.js";import{AuthCookieNames as U}from"../../../../constants/common.js";import{withPathPrefix as I}from"@redocly/theme/core/utils";import{telemetry as M}from"../../../telemetry/index.js";import{envConfig as D}from"../../../config/env-config.js";import{createMcpAuthorizationCode as x,verifyMcpAuthorizationCode as Q,createMcpRefreshToken as z,verifyMcpRefreshToken as X,createMcpSessionResource as y,oidcJwksCache as F,oidcRefreshTokenGrant as ee,getOidcMetadata as re,discoverJwks as te}from"../../auth.js";import*as S from"../../jwt/jwt.js";import{AlgorithmTypes as q}from"../../jwt/types.js";import{getRequestOrigin as L}from"../../utils/get-request-origin.js";import{encryptClaimValue as H}from"../../../utils/crypto/encrypt-claim-value.js";import{decryptAesGcm as ne}from"../../../utils/crypto/decrypt-aes-gcm.js";import{encryptAesGcm as G}from"../../../utils/crypto/encrypt-aes-gcm.js";import{fetchClientMetadataDocument as oe,isClientMetadataDocumentUrl as N,isRedirectUriRegistered as ie}from"./mcp-client-metadata.js";const s=(r,e,n=200,i)=>r.json(e,n,{"Content-Type":"application/json",...i??{}}),j=new Set(["S256"]),$=["authorization_code","refresh_token"],E=["openid","profile","email","offline_access"],se="https://claude.ai/oauth/claude-code-client-metadata";function ce(r,e,n){if(!j.has(e||""))return!1;const i=J("sha256").update(n).digest("base64url"),c=Buffer.from(i),o=Buffer.from(r);return c.length!==o.length?!1:V(c,o)}async function de(r){const e=Math.floor(Date.now()/1e3);return S.sign({type:"mcp_context",...r,iat:e,exp:e+600},m,q.HS256)}async function ae(r){await S.verify(r,m,q.HS256);const{payload:e}=S.decode(r);if(e.type!=="mcp_context")throw new Error("Invalid context token type");return e}function Re(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);if(e.req.method!=="GET")return s(e,{error:"Method not allowed"},405,{Allow:"GET"});const n=L(e);return s(e,{resource:`${n}${I("/mcp")}`,authorization_servers:[n,R].filter(Boolean),bearer_methods_supported:["header"],resource_documentation:`${n}${T.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:[...E],bearer_token_types_supported:["Bearer"]})}}function Ue(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const n=L(e);return s(e,{issuer:R||"",authorization_endpoint:`${n}${I(T.MCP_AUTHORIZATION)}`,token_endpoint:`${n}${I(T.MCP_TOKEN_PORTAL)}`,jwks_uri:`${R||""}/.well-known/jwks.json`,scopes_supported:[...E],registration_endpoint:`${n}${I(T.MCP_DYNAMIC_CLIENT_REGISTRATION)}`,response_types_supported:["code"],grant_types_supported:[...$],subject_types_supported:["public"],id_token_signing_alg_values_supported:["RS256"],code_challenge_methods_supported:["S256"],token_endpoint_auth_methods_supported:["none"],client_id_metadata_document_supported:!0})}}function Oe(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{return s(e,{client_id:D.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:[...$],response_types:["code"],scope:E.join(" "),subject_type:"public",token_endpoint_auth_method:"none",created_at:new Date().toISOString(),updated_at:new Date().toISOString()},201)}catch(n){return s(e,{error:"invalid_request",error_description:n?.message||"Unable to register client"},500)}}}function B(r){const e=r||{},n=Object.keys(e),i=n.find(o=>v(e[o])&&Z(e[o])),c=n.find(o=>v(e[o]));return i||c||n[0]}function De(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const n=new URL(e.req.url),{searchParams:i}=n,c=i.get("redirect_uri"),o=i.get("client_id"),a=i.get("code_challenge"),t=i.get("code_challenge_method"),p=Y();if(a&&!j.has(t||""))return s(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...j].join(", ")}`},400);if(N(o)&&!a)return s(e,{error:"invalid_request",error_description:"code_challenge is required for client_id_metadata_document clients"},400);if(N(o)&&c)try{const d=await oe(o),l=o===se;if(!ie(c,d.redirect_uris,{ignorePort:l}))return s(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(d){const l=d instanceof Error?d.message:String(d);return s(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${l}`},400)}M.sendMcpAuthorizationStartedMessage([{...y(p),redirect_uri:c||null}]);const f=L(e),_={isMcpFlow:!0,originalRedirectUri:c,mcpClientId:o,mcpState:i.get("state"),mcpSessionId:p,mcpCodeChallenge:a,mcpCodeChallengeMethod:a?t:null,timestamp:Date.now()};try{const d=await de(_),l=new URL(I(T.IDP_LOGIN),f);l.searchParams.set("redirectTo",`${T.MCP_CALLBACK}/${d}`);const k=B(r.getConfig().ssoDirect);return k?(l.searchParams.set("idpId",k),e.redirect(l.toString())):s(e,{error:"not_found"},404)}catch(d){const l=d instanceof Error?d.message:String(d),k=d instanceof Error?d.stack:String(d);M.sendMcpAuthorizationFailedMessage([{...y(p),error:l,error_details:k}]);const C=new URL(I(`${R}/oauth2/auth`));return C.search=i.toString(),e.redirect(C.toString())}}}async function K(r){const e=Math.floor(Date.now()/1e3),n=await S.sign({...r.claims,...r.idpId?{idpId:r.idpId}:{},...r.idpAccessTokenEnc?{idp_access_token:r.idpAccessTokenEnc}:{},...r.ttlSec?{iat:e,exp:e+r.ttlSec}:{}},m,q.HS256,r.kid),i=r.ttlSec??(typeof r.claims.exp=="number"?Math.max(r.claims.exp-e,0):b);return{accessToken:n,expiresIn:i}}async function pe(r,e,n){const i=g=>s(r,{error:"invalid_grant",error_description:g},400),c=r.get("logger"),o=n.get("refresh_token"),a=n.get("client_id")||void 0;if(!o)return s(r,{error:"invalid_request",error_description:"refresh_token is required"},400);let t;try{t=await X(o)}catch(g){return c?.warn("MCP refresh token rejected: %s",g instanceof Error?g.message:String(g)),i("Invalid refresh token")}if(a&&a!==t.client_id)return i("Client mismatch");const p=t.idp_id?e.getConfig().ssoDirect?.[t.idp_id]:void 0;let f,_,d=t.idp_access_token_enc,l=t.idp_refresh_token_enc;if(t.idp_refresh_token_enc){let g;try{g=ne(t.idp_refresh_token_enc,m)}catch{return i("Invalid refresh token")}if(!t.idp_id||!v(p))return i("Unknown identity provider");let u;try{const h=await re(t.idp_id,p);if(!h?.token_endpoint)return i("Identity provider has no token endpoint");u=await ee(h.token_endpoint,g,p)}catch(h){return c?.warn("MCP refresh failed to reach the IdP token endpoint: %s",h instanceof Error?h.message:String(h)),s(r,{error:"server_error",error_description:"Failed to refresh session"},500)}if(u.error||!(u.id_token||u.access_token))return c?.warn("MCP refresh rejected by IdP: %s",u.error||"empty token response"),i("Refresh rejected by identity provider");if(u.refresh_token&&(l=G(u.refresh_token,m)),u.access_token&&(d=H(u.access_token,m)),u.id_token)try{const h=S.decode(u.id_token);f=h.payload,_=h.header.kid}catch{return i("Invalid token response from identity provider")}}const k=v(p)&&p.tokenExpirationTime||b,C=f??t.claims,w=await K({claims:C,kid:_,idpId:t.idp_id,idpAccessTokenEnc:d,ttlSec:f?void 0:k}),O=await z({clientId:t.client_id,idpId:t.idp_id,claims:C,idpAccessTokenEnc:d,idpRefreshTokenEnc:l,sessionExp:t.session_exp});return s(r,{access_token:w.accessToken,token_type:"Bearer",expires_in:w.expiresIn,refresh_token:O,scope:E.join(" ")},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}function qe(r){return async e=>{if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{const n=await e.req.formData(),i=n.get("grant_type"),c=n.get("code"),o=n.get("redirect_uri")||void 0,a=n.get("code_verifier")||void 0;if(!$.includes(i))return s(e,{error:"invalid_request",error_description:"Invalid grant type"},400);if(i==="refresh_token")return await pe(e,r,n);if(!c)return s(e,{error:"invalid_request",error_description:"Missing authorization code"},400);try{const t=await Q(c);if(o&&o!==t.redirect_uri)return s(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(t.code_challenge){if(!a)return s(e,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!ce(t.code_challenge,t.code_challenge_method,a))return s(e,{error:"invalid_grant",error_description:"code_verifier mismatch"},400)}if(D.OAUTH_CLIENT_ID&&t.client_id&&!N(t.client_id)&&t.client_id!==D.OAUTH_CLIENT_ID)return s(e,{error:"invalid_client",error_description:"Client mismatch"},400);const p=t.id_token;if(typeof p!="string"||p.length===0)return s(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400);const f=t.idp_access_token_enc??(t.idp_access_token?H(t.idp_access_token,m):void 0),_=S.decode(p),{accessToken:d,expiresIn:l}=await K({claims:_.payload,kid:_.header.kid,idpId:t.idp_id,idpAccessTokenEnc:f}),C=Math.floor(Date.now()/1e3)+W,w=await z({clientId:t.client_id,idpId:t.idp_id,claims:_.payload,idpAccessTokenEnc:f,idpRefreshTokenEnc:t.idp_refresh_token_enc,sessionExp:C}),O={access_token:d,token_type:"Bearer",expires_in:l,refresh_token:w,scope:E.join(" "),id_token:p};return s(e,O,200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch(t){return e.get("logger")?.warn("MCP token exchange failed: %s",t instanceof Error?t.message:String(t)),s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(n){const i=n instanceof Error?n.message:String(n);return s(e,{error:"server_error",error_description:"Failed to process token request",error_details:i},500)}}}async function le(r,e){try{const{payload:n,header:i}=S.decode(r);if(typeof n.idpId=="string"&&n.idpId)return n.idpId;if(i.kid){F.jwks[i.kid]==null&&await te(e||{});const c=F.jwks[i.kid];if(c?.idpId)return c.idpId}}catch{}return B(e)}function Le(r){return async e=>{const n=new URL(e.req.url);let i=n.searchParams.get("context");if(!i&&n.pathname.startsWith(I(`${T.MCP_CALLBACK}/`))){const o=n.pathname.split("/");i=o[o.length-1]}if(!i)return M.sendMcpAuthorizationFailedMessage([{...y(null),error:"Missing context parameter",error_details:null}]),e.text("Missing context parameter",400);let c=null;try{const o=await ae(i);if(c=o.mcpSessionId||null,!o.isMcpFlow||!o.originalRedirectUri)throw new Error("Invalid MCP context");const a=A(e,U.IDP_ID_TOKEN)||A(e,U.AUTHORIZATION),t=A(e,U.IDP_ACCESS_TOKEN),p=A(e,U.IDP_REFRESH_TOKEN);if(!a)return M.sendMcpAuthorizationFailedMessage([{...y(c),error:"Authentication required",error_details:null}]),e.text("Authentication required",401);const f=await x({idToken:a,idpId:await le(a,r.getConfig().ssoDirect),idpAccessTokenEnc:t?H(t,m):void 0,idpRefreshTokenEnc:p?G(p,m):void 0,clientId:o.mcpClientId||"",redirectUri:o.originalRedirectUri,codeChallenge:o.mcpCodeChallenge||void 0,codeChallengeMethod:o.mcpCodeChallengeMethod||void 0,ttlSec:600}),_=new URL(o.originalRedirectUri);return _.searchParams.set("code",f),o.mcpState&&_.searchParams.set("state",o.mcpState),M.sendMcpAuthorizationCompletedMessage([{...y(c),redirect_uri:o.originalRedirectUri||null}]),e.redirect(_.toString())}catch(o){const a=o instanceof Error?o.message:String(o),t=o instanceof Error?o.stack:String(o);return M.sendMcpAuthorizationFailedMessage([{...y(c),error:a,error_details:t}]),e.text(`Invalid MCP callback: ${a}`,400)}}}export{$ as SUPPORTED_MCP_GRANT_TYPES,de as createMcpContextToken,De as mcpAuthorizationHandler,Le as mcpCallbackHandler,Oe as mcpDynamicClientRegistrationHandler,Ue as mcpOAuthAuthorizationServerHandler,Re as mcpOAuthProtectedResourceHandler,qe as mcpTokenPortalHandler,B as resolveMcpIdpId,ae as verifyAndParseMcpContextToken,ce as verifyPkce};
|
|
1
|
+
import{getCookie as A}from"hono/cookie";import{createHash as K,timingSafeEqual as J}from"node:crypto";import{ulid as V}from"ulid";import{isMcpContentProtected as P}from"../../../plugins/mcp/auth/auth-handlers.js";import{SUPPORTED_MCP_SCOPES as E}from"../../../plugins/mcp/constants.js";import{isRedoclySso as Y,isOidcProviderConfig as v}from"../../auth.js";import{AUTH_URL as Z,JWT_SECRET_KEY as m,MCP_ACCESS_TOKEN_TTL_SEC as b,MCP_REFRESH_SESSION_LIFETIME_SEC as W}from"../../../constants/common.js";import{ServerRoutes as T}from"../../../../constants/api.js";import{AuthCookieNames as R}from"../../../../constants/common.js";import{withPathPrefix as I}from"@redocly/theme/core/utils";import{telemetry as M}from"../../../telemetry/index.js";import{envConfig as U}from"../../../config/env-config.js";import{createMcpAuthorizationCode as x,verifyMcpAuthorizationCode as Q,createMcpRefreshToken as j,verifyMcpRefreshToken as X,createMcpSessionResource as y,oidcJwksCache as z,oidcRefreshTokenGrant as ee,getOidcMetadata as re,discoverJwks as te}from"../../auth.js";import*as S from"../../jwt/jwt.js";import{AlgorithmTypes as D}from"../../jwt/types.js";import{getRequestOrigin as q}from"../../utils/get-request-origin.js";import{encryptClaimValue as L}from"../../../utils/crypto/encrypt-claim-value.js";import{decryptAesGcm as ne}from"../../../utils/crypto/decrypt-aes-gcm.js";import{encryptAesGcm as F}from"../../../utils/crypto/encrypt-aes-gcm.js";import{fetchClientMetadataDocument as oe,isClientMetadataDocumentUrl as H,isRedirectUriRegistered as ie}from"./mcp-client-metadata.js";const s=(r,e,t=200,i)=>r.json(e,t,{"Content-Type":"application/json",...i??{}}),N=new Set(["S256"]),$=["authorization_code","refresh_token"],se="https://claude.ai/oauth/claude-code-client-metadata";function ce(r,e,t){if(!N.has(e||""))return!1;const i=K("sha256").update(t).digest("base64url"),c=Buffer.from(i),o=Buffer.from(r);return c.length!==o.length?!1:J(c,o)}async function de(r){const e=Math.floor(Date.now()/1e3);return S.sign({type:"mcp_context",...r,iat:e,exp:e+600},m,D.HS256)}async function ae(r){await S.verify(r,m,D.HS256);const{payload:e}=S.decode(r);if(e.type!=="mcp_context")throw new Error("Invalid context token type");return e}function Oe(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);if(e.req.method!=="GET")return s(e,{error:"Method not allowed"},405,{Allow:"GET"});const t=q(e);return s(e,{resource:`${t}${I("/mcp")}`,authorization_servers:[t],bearer_methods_supported:["header"],resource_documentation:`${t}${T.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:[...E],bearer_token_types_supported:["Bearer"]},200,{"Access-Control-Allow-Origin":"*"})}}function Ue(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const t=q(e);return s(e,{issuer:t,authorization_endpoint:`${t}${I(T.MCP_AUTHORIZATION)}`,token_endpoint:`${t}${I(T.MCP_TOKEN_PORTAL)}`,scopes_supported:[...E],registration_endpoint:`${t}${I(T.MCP_DYNAMIC_CLIENT_REGISTRATION)}`,response_types_supported:["code"],grant_types_supported:[...$],subject_types_supported:["public"],id_token_signing_alg_values_supported:["RS256"],code_challenge_methods_supported:["S256"],token_endpoint_auth_methods_supported:["none"],client_id_metadata_document_supported:!0},200,{"Access-Control-Allow-Origin":"*"})}}function De(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{return s(e,{client_id:U.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:[...$],response_types:["code"],scope:E.join(" "),subject_type:"public",token_endpoint_auth_method:"none",created_at:new Date().toISOString(),updated_at:new Date().toISOString()},201,{"Access-Control-Allow-Origin":"*"})}catch(t){return s(e,{error:"invalid_request",error_description:t?.message||"Unable to register client"},500,{"Access-Control-Allow-Origin":"*"})}}}function G(r){const e=r||{},t=Object.keys(e),i=t.find(o=>v(e[o])&&Y(e[o])),c=t.find(o=>v(e[o]));return i||c||t[0]}function qe(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const t=new URL(e.req.url),{searchParams:i}=t,c=i.get("redirect_uri"),o=i.get("client_id"),a=i.get("code_challenge"),n=i.get("code_challenge_method"),l=V();if(a&&!N.has(n||""))return s(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...N].join(", ")}`},400);if(H(o)&&!a)return s(e,{error:"invalid_request",error_description:"code_challenge is required for client_id_metadata_document clients"},400);if(H(o)&&c)try{const d=await oe(o),p=o===se;if(!ie(c,d.redirect_uris,{ignorePort:p}))return s(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(d){const p=d instanceof Error?d.message:String(d);return s(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${p}`},400)}M.sendMcpAuthorizationStartedMessage([{...y(l),redirect_uri:c||null}]);const f=q(e),_={isMcpFlow:!0,originalRedirectUri:c,mcpClientId:o,mcpState:i.get("state"),mcpSessionId:l,mcpCodeChallenge:a,mcpCodeChallengeMethod:a?n:null,timestamp:Date.now()};try{const d=await de(_),p=new URL(I(T.IDP_LOGIN),f);p.searchParams.set("redirectTo",`${T.MCP_CALLBACK}/${d}`);const k=G(r.getConfig().ssoDirect);return k?(p.searchParams.set("idpId",k),e.redirect(p.toString())):s(e,{error:"not_found"},404)}catch(d){const p=d instanceof Error?d.message:String(d),k=d instanceof Error?d.stack:String(d);M.sendMcpAuthorizationFailedMessage([{...y(l),error:p,error_details:k}]);const C=new URL(I(`${Z}/oauth2/auth`));return C.search=i.toString(),e.redirect(C.toString())}}}async function B(r){const e=Math.floor(Date.now()/1e3),t=await S.sign({...r.claims,...r.idpId?{idpId:r.idpId}:{},...r.idpAccessTokenEnc?{idp_access_token:r.idpAccessTokenEnc}:{},...r.ttlSec?{iat:e,exp:e+r.ttlSec}:{}},m,D.HS256,r.kid),i=r.ttlSec??(typeof r.claims.exp=="number"?Math.max(r.claims.exp-e,0):b);return{accessToken:t,expiresIn:i}}async function le(r,e,t){const i=g=>s(r,{error:"invalid_grant",error_description:g},400),c=r.get("logger"),o=t.get("refresh_token"),a=t.get("client_id")||void 0;if(!o)return s(r,{error:"invalid_request",error_description:"refresh_token is required"},400);let n;try{n=await X(o)}catch(g){return c?.warn("MCP refresh token rejected: %s",g instanceof Error?g.message:String(g)),i("Invalid refresh token")}if(a&&a!==n.client_id)return i("Client mismatch");const l=n.idp_id?e.getConfig().ssoDirect?.[n.idp_id]:void 0;let f,_,d=n.idp_access_token_enc,p=n.idp_refresh_token_enc;if(n.idp_refresh_token_enc){let g;try{g=ne(n.idp_refresh_token_enc,m)}catch{return i("Invalid refresh token")}if(!n.idp_id||!v(l))return i("Unknown identity provider");let u;try{const h=await re(n.idp_id,l);if(!h?.token_endpoint)return i("Identity provider has no token endpoint");u=await ee(h.token_endpoint,g,l)}catch(h){return c?.warn("MCP refresh failed to reach the IdP token endpoint: %s",h instanceof Error?h.message:String(h)),s(r,{error:"server_error",error_description:"Failed to refresh session"},500)}if(u.error||!(u.id_token||u.access_token))return c?.warn("MCP refresh rejected by IdP: %s",u.error||"empty token response"),i("Refresh rejected by identity provider");if(u.refresh_token&&(p=F(u.refresh_token,m)),u.access_token&&(d=L(u.access_token,m)),u.id_token)try{const h=S.decode(u.id_token);f=h.payload,_=h.header.kid}catch{return i("Invalid token response from identity provider")}}const k=v(l)&&l.tokenExpirationTime||b,C=f??n.claims,w=await B({claims:C,kid:_,idpId:n.idp_id,idpAccessTokenEnc:d,ttlSec:f?void 0:k}),O=await j({clientId:n.client_id,idpId:n.idp_id,claims:C,idpAccessTokenEnc:d,idpRefreshTokenEnc:p,sessionExp:n.session_exp});return s(r,{access_token:w.accessToken,token_type:"Bearer",expires_in:w.expiresIn,refresh_token:O,scope:E.join(" ")},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}function Le(r){return async e=>{if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{const t=await e.req.formData(),i=t.get("grant_type"),c=t.get("code"),o=t.get("redirect_uri")||void 0,a=t.get("code_verifier")||void 0;if(!$.includes(i))return s(e,{error:"invalid_request",error_description:"Invalid grant type"},400);if(i==="refresh_token")return await le(e,r,t);if(!c)return s(e,{error:"invalid_request",error_description:"Missing authorization code"},400);try{const n=await Q(c);if(o&&o!==n.redirect_uri)return s(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(n.code_challenge){if(!a)return s(e,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!ce(n.code_challenge,n.code_challenge_method,a))return s(e,{error:"invalid_grant",error_description:"code_verifier mismatch"},400)}if(U.OAUTH_CLIENT_ID&&n.client_id&&!H(n.client_id)&&n.client_id!==U.OAUTH_CLIENT_ID)return s(e,{error:"invalid_client",error_description:"Client mismatch"},400);const l=n.id_token;if(typeof l!="string"||l.length===0)return s(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400);const f=n.idp_access_token_enc??(n.idp_access_token?L(n.idp_access_token,m):void 0),_=S.decode(l),{accessToken:d,expiresIn:p}=await B({claims:_.payload,kid:_.header.kid,idpId:n.idp_id,idpAccessTokenEnc:f}),C=Math.floor(Date.now()/1e3)+W,w=await j({clientId:n.client_id,idpId:n.idp_id,claims:_.payload,idpAccessTokenEnc:f,idpRefreshTokenEnc:n.idp_refresh_token_enc,sessionExp:C}),O={access_token:d,token_type:"Bearer",expires_in:p,refresh_token:w,scope:E.join(" "),id_token:l};return s(e,O,200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch(n){return e.get("logger")?.warn("MCP token exchange failed: %s",n instanceof Error?n.message:String(n)),s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(t){const i=t instanceof Error?t.message:String(t);return s(e,{error:"server_error",error_description:"Failed to process token request",error_details:i},500)}}}async function pe(r,e){try{const{payload:t,header:i}=S.decode(r);if(typeof t.idpId=="string"&&t.idpId)return t.idpId;if(i.kid){z.jwks[i.kid]==null&&await te(e||{});const c=z.jwks[i.kid];if(c?.idpId)return c.idpId}}catch{}return G(e)}function He(r){return async e=>{const t=new URL(e.req.url);let i=t.searchParams.get("context");if(!i&&t.pathname.startsWith(I(`${T.MCP_CALLBACK}/`))){const o=t.pathname.split("/");i=o[o.length-1]}if(!i)return M.sendMcpAuthorizationFailedMessage([{...y(null),error:"Missing context parameter",error_details:null}]),e.text("Missing context parameter",400);let c=null;try{const o=await ae(i);if(c=o.mcpSessionId||null,!o.isMcpFlow||!o.originalRedirectUri)throw new Error("Invalid MCP context");const a=A(e,R.IDP_ID_TOKEN)||A(e,R.AUTHORIZATION),n=A(e,R.IDP_ACCESS_TOKEN),l=A(e,R.IDP_REFRESH_TOKEN);if(!a)return M.sendMcpAuthorizationFailedMessage([{...y(c),error:"Authentication required",error_details:null}]),e.text("Authentication required",401);const f=await x({idToken:a,idpId:await pe(a,r.getConfig().ssoDirect),idpAccessTokenEnc:n?L(n,m):void 0,idpRefreshTokenEnc:l?F(l,m):void 0,clientId:o.mcpClientId||"",redirectUri:o.originalRedirectUri,codeChallenge:o.mcpCodeChallenge||void 0,codeChallengeMethod:o.mcpCodeChallengeMethod||void 0,ttlSec:600}),_=new URL(o.originalRedirectUri);return _.searchParams.set("code",f),o.mcpState&&_.searchParams.set("state",o.mcpState),M.sendMcpAuthorizationCompletedMessage([{...y(c),redirect_uri:o.originalRedirectUri||null}]),e.redirect(_.toString())}catch(o){const a=o instanceof Error?o.message:String(o),n=o instanceof Error?o.stack:String(o);return M.sendMcpAuthorizationFailedMessage([{...y(c),error:a,error_details:n}]),e.text(`Invalid MCP callback: ${a}`,400)}}}export{$ as SUPPORTED_MCP_GRANT_TYPES,de as createMcpContextToken,qe as mcpAuthorizationHandler,He as mcpCallbackHandler,De as mcpDynamicClientRegistrationHandler,Ue as mcpOAuthAuthorizationServerHandler,Oe as mcpOAuthProtectedResourceHandler,Le as mcpTokenPortalHandler,G as resolveMcpIdpId,ae as verifyAndParseMcpContextToken,ce as verifyPkce};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function s(
|
|
1
|
+
const s=["localhost","127.0.0.1","::1","[::1]"];function n(o){return o?.split(",")[0].trim()||void 0}function d(o){const t=o.replace(/:\d+$/,"");return s.includes(t)||t.endsWith(".localhost")}function a(o){const t=new URL(o.req.url),r=o.req.header("x-forwarded-proto")||t.protocol.slice(0,-1)||"https",e=o.req.header("x-forwarded-host")||t.host;return`${r}://${e}`}function c(o){const t=new URL(o.url),r=n(o.headers.get("x-forwarded-host"))||t.host;return`${n(o.headers.get("x-forwarded-proto"))||(d(r)?"http":"https")}://${r}`}export{c as getForwardedOrigin,a as getRequestOrigin};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/realm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.137.0-next.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -93,15 +93,15 @@
|
|
|
93
93
|
"xpath": "0.0.34",
|
|
94
94
|
"yaml-ast-parser": "0.0.43",
|
|
95
95
|
"zod": "^3.25.76",
|
|
96
|
-
"@redocly/
|
|
97
|
-
"@redocly/asyncapi-docs": "1.
|
|
96
|
+
"@redocly/api-docs": "0.2.0-next.0",
|
|
97
|
+
"@redocly/asyncapi-docs": "1.14.0-next.0",
|
|
98
98
|
"@redocly/config": "0.53.1",
|
|
99
|
-
"@redocly/
|
|
100
|
-
"@redocly/
|
|
101
|
-
"@redocly/theme": "0.68.0",
|
|
99
|
+
"@redocly/graphql-docs": "1.14.0-next.0",
|
|
100
|
+
"@redocly/openapi-docs": "3.25.0-next.0",
|
|
102
101
|
"@redocly/portal-legacy-ui": "0.19.0",
|
|
102
|
+
"@redocly/portal-plugin-mock-server": "0.22.0-next.0",
|
|
103
103
|
"@redocly/realm-asyncapi-sdk": "0.14.0",
|
|
104
|
-
"@redocly/
|
|
104
|
+
"@redocly/theme": "0.69.0-next.0"
|
|
105
105
|
},
|
|
106
106
|
"peerDependencies": {
|
|
107
107
|
"react": "^19.2.7",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
function v(e,o,s){if(!o)return;const r=o.toLowerCase(),t=Object.values(e).filter(({name:f})=>f.toLowerCase()===r);if(t.length===0)return;const n=s?t.filter(f=>f.version===s):t;if(n.length===0)return;if(n.length===1)return n[0];if(new Set(n.map(({version:f})=>f||"")).size>1){const f=n.map(({version:l})=>l||"(no version)").join(", ");throw new Error(`Multiple versions of "${o}" found: ${f}. Specify a version to disambiguate.`)}const i=n.map(({relativePath:f})=>`"${f||"(unknown path)"}"`).join(", "),u=n[0].version?` at version "${n[0].version}"`:"";throw new Error(`Multiple APIs are titled "${o}"${u}. Pass the "filePath" parameter to select one of: ${i}.`)}function h(e,o){return Object.values(e).filter(({name:s})=>s.toLowerCase().includes(o.toLowerCase()))}function d(e,o){if(!o||!e?.startsWith("#/"))return;const s=e.slice(2).split("/");let r=o;for(const t of s)if(typeof r=="object"&&r!==null&&t in r)r=r[t];else return;return r}function p(e,o){const s=Object.fromEntries(Object.entries(e).filter(([r,t])=>r!=="$ref"&&t!==void 0));return{...o,...s}}function a({schema:e,definition:o,visitedRefs:s=new Set,maxDepth:r=50,currentDepth:t=0}){if(!e)return;if(t>=r)return{type:"object",description:`Maximum resolution depth exceeded (${r})`};if("$ref"in e&&e.$ref){if(s.has(e.$ref))return{type:"object",description:`Circular reference detected: ${e.$ref}`,"x-circular-ref":e.$ref};const i=new Set(s);i.add(e.$ref);const u=d(e.$ref,o);if(!u)return{type:"object",description:`Could not resolve reference: ${e.$ref}`,"x-unresolved-ref":e.$ref};const f=a({schema:u,definition:o,visitedRefs:i,maxDepth:r,currentDepth:t+1});return f&&p(e,f)}const n={...e};if(n.properties){const i={};for(const[u,f]of Object.entries(n.properties)){const l=a({schema:f,definition:o,visitedRefs:new Set(s),maxDepth:r,currentDepth:t+1});i[u]=l||f}n.properties=i}if(n.items&&typeof n.items=="object")if(Array.isArray(n.items))n.items=n.items.map(i=>a({schema:i,definition:o,visitedRefs:new Set(s),maxDepth:r,currentDepth:t+1})||i);else{const i=a({schema:n.items,definition:o,visitedRefs:new Set(s),maxDepth:r,currentDepth:t+1});i&&(n.items=i)}return["allOf","anyOf","oneOf"].forEach(i=>{const u=n[i];Array.isArray(u)&&(n[i]=u.reduce((f,l)=>{const m=a({schema:l,definition:o,visitedRefs:new Set(s),maxDepth:r,currentDepth:t+1});return m&&f.push(m),f},[]))}),n}function w({pathParams:e,opParams:o,definition:s}){return[...e,...o].reduce((r,t)=>{if(!t)return r;if("$ref"in t&&t.$ref){const n=d(t.$ref,s);if(!n)return r;const c=p(t,n);return r.push({...c,schema:"schema"in c&&c.schema?a({schema:c.schema,definition:s}):void 0}),r}return r.push({...t,schema:"schema"in t&&t.schema?a({schema:t.schema,definition:s}):void 0}),r},[])}function $(e,o){const s={...e};if(e.example!==void 0&&(s.example=e.example),!e.examples)return s;const r={};for(const[t,n]of Object.entries(e.examples))if("$ref"in n){const c=d(n.$ref,o);c&&(r[t]=c)}else r[t]=n;return s.examples=r,s}function b(e,o){if(!e)return;const s="$ref"in e&&e.$ref?d(e.$ref,o):e;if(!s)return;const r=p(e,s),t={...r};if("content"in r&&r.content){const n={};for(const[c,i]of Object.entries(r.content))n[c]={...$(i,o),schema:i.schema?a({schema:i.schema,definition:o}):void 0};t.content=n}return t}function j(e,o){return Object.entries(e).reduce((s,[r,t])=>{const n="$ref"in t&&t.$ref?d(t.$ref,o):t;if(!n)return s;const c=p(t,n),i={...c};if("content"in c&&c.content){const u={};for(const[f,l]of Object.entries(c.content))u[f]={...$(l,o),schema:l.schema?a({schema:l.schema,definition:o}):void 0};i.content=u}return s[r]=i,s},{})}export{h as filterApiDescriptionsByName,v as findApiDescriptionByNameAndVersion,w as resolveParameters,d as resolveRef,b as resolveRequestBody,j as resolveResponses,a as resolveSchemaRefs};
|