@redocly/revel 0.138.0-next.9 → 0.138.1
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 +276 -0
- package/dist/api-schemas/mcp-fetch-count.d.ts +24 -0
- package/dist/api-schemas/mcp-fetch-count.js +1 -0
- package/dist/bin.js +0 -0
- package/dist/cli/build/analytics/collect-analytics.js +1 -1
- package/dist/cli/build/analytics/collectors/get-processed-config-stats-usage.js +1 -1
- package/dist/cli/build/index.js +1 -1
- package/dist/cli/develop.js +1 -1
- package/dist/cli/eject/ejectTheme.js +1 -1
- package/dist/cli/translations/collect-config-translations.js +1 -1
- package/dist/client/App.js +1 -1
- package/dist/client/app/markdoc/custom-components/openapi/openapi-response-sample.js +1 -1
- package/dist/client/app/markdoc/custom-components/openapi/replay-openapi.js +1 -1
- package/dist/client/app/pages/Invite/Invite.js +1 -1
- package/dist/client/app/utils/loadAndNavigate.js +1 -1
- package/dist/client/polyfills.js +1 -1
- package/dist/client/runtime/loader.js +1 -1
- package/dist/client/utils/catalog/parse-filter-query.js +1 -1
- package/dist/compiled/svgo/svgo-node.js +39 -39
- package/dist/constants/api.d.ts +1 -0
- package/dist/constants/api.js +1 -1
- package/dist/constants/common.d.ts +6 -0
- package/dist/constants/common.js +1 -1
- package/dist/constants/l10n/langs/ar.js +1 -1
- package/dist/constants/l10n/langs/de.js +1 -1
- package/dist/constants/l10n/langs/en.js +1 -1
- package/dist/constants/l10n/langs/es.js +1 -1
- package/dist/constants/l10n/langs/fr.js +1 -1
- package/dist/constants/l10n/langs/hi.js +1 -1
- package/dist/constants/l10n/langs/it.js +1 -1
- package/dist/constants/l10n/langs/ja.js +1 -1
- package/dist/constants/l10n/langs/ko.js +1 -1
- package/dist/constants/l10n/langs/pl.js +1 -1
- package/dist/constants/l10n/langs/pt-BR.js +1 -1
- package/dist/constants/l10n/langs/pt.js +1 -1
- package/dist/constants/l10n/langs/ru.js +1 -1
- package/dist/constants/l10n/langs/uk.js +1 -1
- package/dist/constants/l10n/langs/zh.js +1 -1
- package/dist/markdoc/helpers/get-inner-text.js +2 -2
- package/dist/markdoc/helpers/guards/is-prompt-node.d.ts +13 -0
- package/dist/markdoc/helpers/guards/is-prompt-node.js +1 -0
- package/dist/server/config/env-config.d.ts +0 -2
- package/dist/server/config/env-schema.d.ts +0 -5
- package/dist/server/config/env-schemas/feature-flags.d.ts +0 -4
- package/dist/server/config/env-schemas/feature-flags.js +1 -1
- package/dist/server/constants/entitlements.js +1 -1
- package/dist/server/entitlements/entitlements-provider.js +1 -1
- package/dist/server/esbuild/esbuild.js +3 -3
- package/dist/server/esbuild/plugins/on-rebuild.js +1 -1
- package/dist/server/esbuild/plugins/styled-components-ssr.js +1 -1
- package/dist/server/esbuild/plugins/yaml-loader.js +1 -1
- package/dist/server/fs/content-fs.js +1 -1
- package/dist/server/fs/utils/is-l10n-root-file.d.ts +3 -0
- package/dist/server/fs/utils/is-l10n-root-file.js +1 -0
- package/dist/server/plugins/analytics/amplitude/index.js +1 -1
- package/dist/server/plugins/analytics/heap/browser-hooks.js +1 -1
- package/dist/server/plugins/api-docs/index.js +1 -1
- package/dist/server/plugins/api-docs/loader.js +3 -3
- package/dist/server/plugins/api-docs/spec-download.api.js +1 -1
- package/dist/server/plugins/api-docs/utils/apply-nav-item-to-root.js +1 -1
- package/dist/server/plugins/api-docs/utils/decorators.d.ts +11 -0
- package/dist/server/plugins/api-docs/utils/decorators.js +1 -1
- package/dist/server/plugins/arazzo-docs/arazzo-doc-loader.js +2 -2
- package/dist/server/plugins/catalog-entities/extensions/extractors/api-description/openapi-entities-extractor.js +1 -1
- package/dist/server/plugins/catalog-entities/utils/extract-parts-from-channels-message-ref.js +1 -1
- package/dist/server/plugins/config-parser/format-custom-tags.js +1 -1
- package/dist/server/plugins/config-parser/loaders/content-slugs-loader.js +1 -1
- package/dist/server/plugins/config-parser/loaders/versions-config-loader.js +1 -1
- package/dist/server/plugins/config-parser/resolve-mutual-exclusion.js +1 -1
- package/dist/server/plugins/config-parser/safe-parse.js +1 -1
- package/dist/server/plugins/default-theme/resolve-logo.js +1 -1
- package/dist/server/plugins/dev-onboarding/api/adapters/apigee/adapter.js +2 -2
- package/dist/server/plugins/dev-onboarding/api/adapters/gravitee/client.js +2 -2
- package/dist/server/plugins/dev-onboarding/api/combine.js +1 -1
- package/dist/server/plugins/dev-onboarding/template/components/AppOverview.js +1 -1
- package/dist/server/plugins/dev-onboarding/template/components/useSortableData.js +1 -1
- package/dist/server/plugins/markdown/attribute-resolvers/code-walkthrough/file-chunk-parser.d.ts +1 -1
- package/dist/server/plugins/markdown/attribute-resolvers/index.js +1 -1
- package/dist/server/plugins/markdown/attribute-resolvers/resolve-link.js +1 -1
- package/dist/server/plugins/markdown/attribute-resolvers/resolve-svg-content.js +1 -1
- package/dist/server/plugins/markdown/attribute-resolvers/resolve-tag-raw-content.d.ts +4 -0
- package/dist/server/plugins/markdown/attribute-resolvers/resolve-tag-raw-content.js +2 -0
- package/dist/server/plugins/markdown/compiler.js +1 -1
- package/dist/server/plugins/markdown/create-source-lines-reader.d.ts +7 -0
- package/dist/server/plugins/markdown/create-source-lines-reader.js +1 -0
- package/dist/server/plugins/markdown/html/normalize-html-headings.d.ts +9 -0
- package/dist/server/plugins/markdown/html/normalize-html-headings.js +1 -0
- package/dist/server/plugins/markdown/markdoc/markdoc-options.js +1 -1
- package/dist/server/plugins/markdown/markdoc/plugins/utils.d.ts +1 -1
- package/dist/server/plugins/markdown/markdoc/plugins/utils.js +1 -1
- package/dist/server/plugins/markdown/markdown-static-data-loader.js +1 -1
- package/dist/server/plugins/markdown/search/get-ai-search-documents.js +1 -1
- package/dist/server/plugins/markdown/search/search-resolver.js +1 -1
- package/dist/server/plugins/markdown/search/to-markdown.js +13 -13
- package/dist/server/plugins/markdown/search/walk-sections.d.ts +3 -1
- package/dist/server/plugins/markdown/search/walk-sections.js +3 -3
- package/dist/server/plugins/markdown/utils/sanitize-malformed-md-content.js +2 -2
- package/dist/server/plugins/mcp/audit/audit-log.d.ts +1 -1
- package/dist/server/plugins/mcp/audit/audit-log.js +1 -1
- package/dist/server/plugins/mcp/audit/code-credentials.d.ts +9 -0
- package/dist/server/plugins/mcp/audit/code-credentials.js +2 -0
- package/dist/server/plugins/mcp/codemode/capabilities/fetch/index.js +5 -5
- package/dist/server/plugins/mcp/codemode/capabilities/types.d.ts +3 -1
- package/dist/server/plugins/mcp/codemode/capabilities/utils.d.ts +2 -1
- package/dist/server/plugins/mcp/codemode/capabilities/utils.js +3 -3
- package/dist/server/plugins/mcp/codemode/code-audit.d.ts +4 -3
- package/dist/server/plugins/mcp/codemode/code-execution-audit.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/tools/core/utils.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/utils.d.ts +12 -3
- package/dist/server/plugins/mcp/docs-mcp/tools/openapi/utils.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/utils/resolve-refs.d.ts +2 -2
- package/dist/server/plugins/mcp/docs-mcp/utils/resolve-refs.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/allowed-http-methods.d.ts +11 -0
- package/dist/server/plugins/mcp/gateway-mcp/allowed-http-methods.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/api-call-operations.d.ts +32 -0
- package/dist/server/plugins/mcp/gateway-mcp/api-call-operations.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/callable-apis.d.ts +7 -4
- package/dist/server/plugins/mcp/gateway-mcp/callable-apis.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/caller-api-calls.d.ts +9 -0
- package/dist/server/plugins/mcp/gateway-mcp/caller-api-calls.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/elicitation.d.ts +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/elicitation.js +5 -5
- package/dist/server/plugins/mcp/gateway-mcp/eligibility.d.ts +8 -4
- package/dist/server/plugins/mcp/gateway-mcp/eligibility.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-audit.d.ts +2 -1
- package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-audit.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-bridge.d.ts +11 -1
- package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-bridge.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/fetch/redact.d.ts +11 -0
- package/dist/server/plugins/mcp/gateway-mcp/fetch/redact.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/fetch-capability.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-count-client.d.ts +4 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-count-client.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-count-secret.d.ts +4 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-count-secret.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-count.d.ts +11 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-count.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-counter-repository.d.ts +13 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-counter-repository.js +13 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/{limits.d.ts → fetch-limiter/fetch-limits.d.ts} +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-limits.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/{apply-edge-rate-limit.d.ts → request-limiter/apply-edge-rate-limit.d.ts} +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/request-limiter/apply-edge-rate-limit.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/{edge-limiter.d.ts → request-limiter/edge-limiter.d.ts} +3 -3
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/request-limiter/edge-limiter.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/request-limiter/rate-limit-telemetry.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/request-limiter/request-limits.d.ts +6 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/request-limiter/request-limits.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/utils/ip-key.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/{window-counter.d.ts → utils/window-counter.d.ts} +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/utils/window-counter.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/route-pattern.d.ts +18 -0
- package/dist/server/plugins/mcp/gateway-mcp/route-pattern.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/secret-store.d.ts +9 -3
- package/dist/server/plugins/mcp/gateway-mcp/secret-store.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/subject.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/upstream-auth-scheme.d.ts +2 -0
- package/dist/server/plugins/mcp/gateway-mcp/upstream-auth-scheme.js +2 -2
- package/dist/server/plugins/mcp/gateway-mcp/upstream-auth.d.ts +0 -2
- package/dist/server/plugins/mcp/gateway-mcp/upstream-auth.js +1 -1
- package/dist/server/plugins/mcp/handlers/credentials-page/constants.d.ts +7 -0
- package/dist/server/plugins/mcp/handlers/credentials-page/constants.js +1 -0
- package/dist/server/plugins/mcp/handlers/credentials-page/form-contract.d.ts +5 -0
- package/dist/server/plugins/mcp/handlers/credentials-page/form-contract.js +1 -0
- package/dist/server/plugins/mcp/handlers/credentials-page/form-page.d.ts +14 -0
- package/dist/server/plugins/mcp/handlers/credentials-page/form-page.js +62 -0
- package/dist/server/plugins/mcp/handlers/credentials-page/layout.d.ts +17 -0
- package/dist/server/plugins/mcp/handlers/credentials-page/layout.js +11 -0
- package/dist/server/plugins/mcp/handlers/credentials-page/outcome-pages.d.ts +10 -0
- package/dist/server/plugins/mcp/handlers/credentials-page/outcome-pages.js +11 -0
- package/dist/server/plugins/mcp/handlers/credentials-page/site-branding.d.ts +10 -0
- package/dist/server/plugins/mcp/handlers/credentials-page/site-branding.js +1 -0
- package/dist/server/plugins/mcp/handlers/credentials-page/styles.d.ts +4 -0
- package/dist/server/plugins/mcp/handlers/credentials-page/styles.js +186 -0
- package/dist/server/plugins/mcp/handlers/credentials-page/types.d.ts +32 -0
- package/dist/server/plugins/mcp/handlers/credentials-page/types.js +0 -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/handlers/mcp-credentials-handler.d.ts +0 -1
- package/dist/server/plugins/mcp/handlers/mcp-credentials-handler.js +1 -1
- package/dist/server/plugins/mcp/index.js +1 -1
- package/dist/server/plugins/mcp/servers/docs-server.js +1 -1
- package/dist/server/plugins/mcp/servers/mcp-server.js +2 -2
- package/dist/server/plugins/mcp/types.d.ts +16 -1
- package/dist/server/plugins/mcp/utils/mode.d.ts +4 -1
- package/dist/server/plugins/mcp/utils/mode.js +1 -1
- package/dist/server/plugins/mcp/utils/xmcp-utils.d.ts +3 -5
- package/dist/server/plugins/mcp/utils/xmcp-utils.js +1 -1
- package/dist/server/plugins/nav-utils.js +1 -1
- package/dist/server/plugins/scorecard-classic/loaders/scorecard.js +1 -1
- package/dist/server/plugins/scorecard-classic/template/components.js +1 -1
- package/dist/server/plugins/scorecard-classic/template/useData.js +1 -1
- package/dist/server/plugins/search/engines/flexsearch/index.js +2 -2
- package/dist/server/plugins/sidebars/index.js +2 -2
- package/dist/server/plugins/sidebars/utils.js +1 -1
- package/dist/server/providers/database/client.d.ts +5 -0
- package/dist/server/providers/database/client.js +1 -1
- package/dist/server/providers/database/copy-migrations.js +1 -1
- package/dist/server/providers/database/database-connections-manager.js +1 -1
- package/dist/server/providers/database/has-project-database.d.ts +3 -0
- package/dist/server/providers/database/has-project-database.js +1 -0
- package/dist/server/store.d.ts +2 -0
- package/dist/server/store.js +1 -1
- package/dist/server/tools/notifiers/formatter.js +1 -1
- package/dist/server/tools/notifiers/helpers/privacy/mask-email.js +1 -1
- package/dist/server/tools/notifiers/logger.js +1 -1
- package/dist/server/types/plugins/markdown.d.ts +2 -0
- package/dist/server/types/web-server.d.ts +1 -0
- package/dist/server/utils/codeframes/codeframes.js +2 -2
- package/dist/server/utils/entitlements/read-entitlement-number.d.ts +3 -0
- package/dist/server/utils/entitlements/read-entitlement-number.js +1 -0
- package/dist/server/utils/fs.js +1 -1
- package/dist/server/utils/is-api-download-link.d.ts +1 -0
- package/dist/server/utils/is-api-download-link.js +1 -1
- package/dist/server/utils/rbac.js +1 -1
- package/dist/server/utils/redact-url.d.ts +3 -2
- package/dist/server/utils/redact-url.js +1 -1
- package/dist/server/utils/sandbox-context.d.ts +7 -4
- package/dist/server/utils/sandbox-context.js +1 -1
- package/dist/server/web-server/auth.d.ts +0 -1
- package/dist/server/web-server/auth.js +4 -4
- package/dist/server/web-server/dev-server.js +1 -1
- package/dist/server/web-server/http.d.ts +2 -1
- package/dist/server/web-server/http.js +2 -2
- package/dist/server/web-server/jwt/token-types.d.ts +1 -0
- package/dist/server/web-server/jwt/token-types.js +1 -1
- package/dist/server/web-server/middleware/catalogAuthMiddleware.js +1 -1
- package/dist/server/web-server/middleware/dynamic-middleware/hono-compose.js +1 -1
- package/dist/server/web-server/middleware/responseHeadersMiddleware.js +1 -1
- package/dist/server/web-server/routes/auth.d.ts +1 -1
- package/dist/server/web-server/routes/auth.js +1 -1
- package/dist/server/web-server/routes/catalog/helpers/create-entity-schema.d.ts +1756 -1756
- package/dist/server/web-server/routes/catalog/helpers/create-entity-update-schema.d.ts +295 -295
- package/dist/server/web-server/routes/cors-proxy.js +2 -2
- package/dist/server/web-server/routes/index.js +1 -1
- package/dist/server/web-server/routes/mcp-routes/mcp-consent-page.d.ts +24 -0
- package/dist/server/web-server/routes/mcp-routes/mcp-consent-page.js +35 -0
- package/dist/server/web-server/routes/mcp-routes/mcp-fetch-count.d.ts +8 -0
- package/dist/server/web-server/routes/mcp-routes/mcp-fetch-count.js +1 -0
- package/dist/server/web-server/routes/mcp-routes/mcp-oauth-store.d.ts +15 -0
- package/dist/server/web-server/routes/mcp-routes/mcp-oauth-store.js +1 -0
- package/dist/server/web-server/routes/mcp-routes/mcp-oauth.d.ts +3 -1
- package/dist/server/web-server/routes/mcp-routes/mcp-oauth.js +1 -1
- package/dist/server/web-server/routes/mcp-routes/mcp-routes.js +1 -1
- package/dist/server/web-server/routes/mcp-routes/mcp-server-card.js +1 -1
- package/dist/server/web-server/routes/resolve-route.js +1 -1
- package/dist/server/workers/worker-pool.js +1 -1
- package/dist/types/entitlements.d.ts +2 -0
- package/dist/utils/time/debounce.js +1 -1
- package/package.json +10 -11
- package/dist/client/app/utils/withLoadProgress.d.ts +0 -2
- package/dist/client/app/utils/withLoadProgress.js +0 -1
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/apply-edge-rate-limit.js +0 -1
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/edge-limiter.js +0 -1
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/ip-key.js +0 -1
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/limits.js +0 -1
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/rate-limit-telemetry.js +0 -1
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/window-counter.js +0 -1
- package/dist/server/plugins/mcp/handlers/mcp-credentials-page.d.ts +0 -12
- package/dist/server/plugins/mcp/handlers/mcp-credentials-page.js +0 -225
- /package/dist/server/plugins/mcp/gateway-mcp/rate-limit/{rate-limit-telemetry.d.ts → request-limiter/rate-limit-telemetry.d.ts} +0 -0
- /package/dist/server/plugins/mcp/gateway-mcp/rate-limit/{ip-key.d.ts → utils/ip-key.d.ts} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RedoclyConfig } from '@redocly/config';
|
|
1
2
|
import type { ToolAnnotations } from '@modelcontextprotocol/server';
|
|
2
3
|
import type { McpToolContext } from '../../types.js';
|
|
3
4
|
/** A capability is a per-request-gated global the code-mode sandbox exposes beyond the schema-driven `tools.*` surface. */
|
|
@@ -6,7 +7,8 @@ export type SandboxCapability = {
|
|
|
6
7
|
purpose: string;
|
|
7
8
|
/** Whether this capability is live for THIS request (RBAC/config/flag-dependent). */
|
|
8
9
|
isActive(context: McpToolContext): boolean;
|
|
9
|
-
|
|
10
|
+
/** The tool hints this capability adds to `execute` under the project's `mcp` config. */
|
|
11
|
+
annotations(mcp: RedoclyConfig['mcp'] | undefined): Partial<ToolAnnotations>;
|
|
10
12
|
/**
|
|
11
13
|
* The self-contained `describe-tools` section for this capability: heading, TS declaration,
|
|
12
14
|
* guidance, and an example built only from `describedToolNames`.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import type { RedoclyConfig } from '@redocly/config';
|
|
1
2
|
import type { ToolAnnotations } from '@modelcontextprotocol/server';
|
|
2
3
|
import type { McpToolSchema } from '../../../../types';
|
|
3
4
|
import type { McpToolContext } from '../../types.js';
|
|
4
5
|
import type { SandboxCapability } from './types.js';
|
|
5
6
|
export declare function getActiveCapabilities(context: McpToolContext): SandboxCapability[];
|
|
6
|
-
export declare function composeExecuteAnnotations(tools: McpToolSchema[], capabilities: SandboxCapability[]): ToolAnnotations;
|
|
7
|
+
export declare function composeExecuteAnnotations(tools: McpToolSchema[], capabilities: SandboxCapability[], mcp: RedoclyConfig['mcp'] | undefined): ToolAnnotations;
|
|
7
8
|
/**
|
|
8
9
|
* The describe-tools output: the tool signatures, the always-present
|
|
9
10
|
* `execute` args note, then one self-contained section per selected capability.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{buildToolTypeDeclarations as p,sanitizeToolName as c}from"../build-tool-type-declarations.js";import{buildExecuteExample as
|
|
1
|
+
import{buildToolTypeDeclarations as p,sanitizeToolName as c}from"../build-tool-type-declarations.js";import{buildExecuteExample as a,EXECUTE_ARGS_NOTE as m,USAGE_NOTES as u}from"../prompts.js";import{FETCH_TOOL_NAME as l}from"../constants.js";import{ALL_CAPABILITIES as d}from"./registry.js";function H(n){return d.filter(o=>o.isActive(n))}function h(n,o,r){const i=[...n.map(({annotations:t})=>t??{}),...o.map(t=>t.annotations(r))],e=i.filter(t=>t.readOnlyHint!==!0);return{readOnlyHint:e.length===0,destructiveHint:e.some(t=>t.destructiveHint!==!1),idempotentHint:e.every(t=>t.idempotentHint===!0),openWorldHint:i.some(t=>t.openWorldHint!==!1)}}function O(n,o,r){const i=n.map(t=>c(t.name)),e=[];if(n.length>0){const t=p(n),s=t.includes("OpenApi")?`
|
|
2
2
|
|
|
3
|
-
${
|
|
3
|
+
${u}`:"";e.push(`## Sandbox tools
|
|
4
4
|
|
|
5
|
-
${
|
|
5
|
+
${t}${s}`)}e.push(m),n.length>0&&!o.some(t=>t.id===l)&&e.push(a(i));for(const t of o)e.push(t.renderSection(r,i));return e.join(`
|
|
6
6
|
|
|
7
7
|
`)}export{h as composeExecuteAnnotations,H as getActiveCapabilities,O as renderDescribeToolsOutput};
|
|
@@ -4,9 +4,10 @@ export type CodeAudit = {
|
|
|
4
4
|
flags: string;
|
|
5
5
|
};
|
|
6
6
|
/**
|
|
7
|
-
* Fingerprint model-supplied code
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Fingerprint model-supplied code. Telemetry never carries the body — it can contain data the model
|
|
8
|
+
* already pulled from a customer API — so it gets a hash plus flags for patterns worth reviewing; the
|
|
9
|
+
* redacted body goes only to the audit log. The sandbox blocks all of the flagged targets; a flag is
|
|
10
|
+
* an attempt.
|
|
10
11
|
*/
|
|
11
12
|
export declare function auditCode(code: string): CodeAudit;
|
|
12
13
|
//# sourceMappingURL=code-audit.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{auditInput as t,auditSha256 as
|
|
1
|
+
import{auditInput as t,auditSha256 as a,logAuditRecord as f}from"../audit/audit-log.js";import{maskCodeCredentials as g}from"../audit/code-credentials.js";function p({audit:e,code:d,description:c,sessionHandle:o,toolNames:i,fetchBudget:n,fetchCapable:r,requestId:s,messageId:u}){f("Record code execution input",{code_sha256:e.sha256,code_length:e.length,request_id:s,message_id:u,flags:e.flags,subject_sha256:o?a(o):void 0,description:t(c),tools:[...i].sort().join(","),fetch_capable:r,fetch_budget:n,code:t(g(d))})}export{p as logCodeExecutionAudit};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import c from"node:fs";import f from"node:path";import{MAX_DOCUMENTS_PER_CATEGORY as $}from"../../../constants.js";function j(m,u){return Object.entries(m).map(([h,s])=>{if(!s||s.length===0)return"";const x=s.slice(0,$).map(d=>{const{document:t,highlight:o}=d,p=o?.title||(Array.isArray(t.title)?t.title[0]:t.title);let n=`Document: ${t.title}`;n+=`### [${p}](${t.url})
|
|
2
2
|
|
|
3
|
-
`;let r;if(t.url)try{let e=t.url.startsWith("/")?t.url.slice(1):t.url;const i=e.indexOf("#");i!==-1&&(e=e.substring(0,i));const l=f.extname(e);l&&(e=e.slice(0,-l.length));
|
|
3
|
+
`;let r;if(t.url)try{let e=t.url.startsWith("/")?t.url.slice(1):t.url;const i=e.indexOf("#");i!==-1&&(e=e.substring(0,i));const l=f.extname(e);l&&(e=e.slice(0,-l.length));const y=e+".md",a=f.join(u,y);c.existsSync(a)&&(r=c.readFileSync(a,"utf8"))}catch{}return r||(r=o?.text||(Array.isArray(t.text)?t.text[0]:t.text)),n+=r,t.facets&&(n+=`
|
|
4
4
|
|
|
5
5
|
**Categories:**
|
|
6
6
|
`,Object.entries(t.facets).forEach(([e,i])=>{n+=`- ${e}: ${i}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{getCallableApis as b}from"../../../gateway-mcp/callable-apis.js";import{filterApiDescriptionsByName as d}from"../../utils/api-name-matching.js";import{apiTitleKey as y,countApiTitles as A,orderSharedTitles as T}from"../../utils/api-titles.js";const x=async(r,l)=>{const{filter:s,page:a=1,limit:e=300}=r,n=new Set(b(l).map(i=>i.relativePath)),m=n.size>0,o=T(Object.values(l.apiDescriptionsMap)),f=A(o);let t=o;s&&(t=d(t,s));const c=(a-1)*e,u=c+e,g=Math.ceil(t.length/e),h=t.length;return t=t.slice(c,u),{content:[{type:"text",text:JSON.stringify({items:t.map(({relativePath:i,fetchEligible:D,...p})=>({filePath:i,...p,...(f.get(y(p.name))??0)>1?{ambiguousTitle:!0}:{},...m?{callable:n.has(i)}:{}})),limit:e,total:h,page:a,totalPages:g})}]}};var I={"list-apis":x};export{I as default};
|
|
@@ -8,13 +8,22 @@ type EndpointInfo = Pick<OpenAPIOperation, 'summary' | 'description' | 'security
|
|
|
8
8
|
path: string;
|
|
9
9
|
};
|
|
10
10
|
export declare function getEndpointsFromPaths(def: OpenAPIDefinition): EndpointInfo[];
|
|
11
|
-
|
|
12
|
-
relativePath: string;
|
|
13
|
-
outdir: string;
|
|
11
|
+
type ApiDescriptionAccess = {
|
|
14
12
|
user: ApiFunctionsUser;
|
|
15
13
|
rbac?: RbacConfig;
|
|
16
14
|
requiresLogin?: boolean;
|
|
15
|
+
};
|
|
16
|
+
export declare function getApiDescriptionFromFs({ relativePath, outdir, ...access }: ApiDescriptionAccess & {
|
|
17
|
+
relativePath: string;
|
|
18
|
+
outdir: string;
|
|
17
19
|
}): Promise<OpenAPIDefinition | undefined>;
|
|
20
|
+
/** The bundled description with no RBAC filtering; never return it to a caller. */
|
|
21
|
+
export declare function readApiDescriptionFromFs(relativePath: string, outdir: string): Promise<OpenAPIDefinition | undefined>;
|
|
22
|
+
/**
|
|
23
|
+
* A filtered copy of `definition` holding only what the user can read, or undefined when the user
|
|
24
|
+
* can read none of it; the input is not mutated.
|
|
25
|
+
*/
|
|
26
|
+
export declare function filterApiDescriptionByAccess(definition: OpenAPIDefinition, { user, rbac, requiresLogin }: ApiDescriptionAccess): OpenAPIDefinition | undefined;
|
|
18
27
|
export declare function absolutePath(requestedPath: string): string;
|
|
19
28
|
/** Models copy a path and lose its casing; the definition's own spelling still answers. */
|
|
20
29
|
export declare function matchDocumentedPath(description: OpenAPIDefinition, requestedPath: string): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import a from"node:path";import{existsSync as p}from"node:fs";import{readFile as m}from"node:fs/promises";import{replaceFileExtension as f}from"../../../../../plugins/api-docs/utils/replace-file-extension.js";import{PUBLIC_API_DEFINITIONS_FOLDER as u}from"../../../../../constants/common.js";import{filterDataByAccessDeep as d}from"../../../../../utils/rbac.js";import{isEndpointHidden as c}from"../../../utils/xmcp-utils.js";import{HTTP_METHODS as h}from"./constants.js";function l(t){return!t||c(t,"docs")?[]:Object.entries(t).filter(o=>{const[e,r]=o;return h.has(e.toLowerCase())&&typeof r=="object"&&r!==null&&!c(r,"docs")})}function L(t){const{paths:o={}}=t,e=[];for(const[r,s]of Object.entries(o))for(const[n,i]of l(s))e.push({path:r,method:n.toUpperCase(),summary:i.summary,description:i.description,security:i.security});return e}async function P({relativePath:t,outdir:o,...e}){const r=await y(t,o);return r&&x(r,e)}async function y(t,o){const e=a.join(o||"",u,f(t,".json"));if(p(e))return JSON.parse(await m(e,"utf-8"))}function x(t,{user:o,rbac:e={},requiresLogin:r=!1}){return d(t,{isAuthenticated:o?.isAuthenticated,email:o?.email,teams:o?.teams},e,r)}function D(t){return t.startsWith("/")?t:`/${t}`}function I(t,o){const e=D(o);if(t.paths?.[e])return e;const r=e.toLowerCase();return Object.keys(t.paths??{}).find(n=>n.toLowerCase()===r)??e}export{D as absolutePath,x as filterApiDescriptionByAccess,P as getApiDescriptionFromFs,L as getEndpointsFromPaths,I as matchDocumentedPath,y as readApiDescriptionFromFs,l as visibleOperations};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { OpenAPIParameter, Referenced, OpenAPIDefinition } from '@redocly/api-docs';
|
|
2
2
|
import type { McpEndpoint } from '../../types.js';
|
|
3
|
+
import { resolveRef } from '@redocly/api-docs/lib/utils/inline-openapi-refs.js';
|
|
4
|
+
export { resolveRef };
|
|
3
5
|
type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
4
6
|
[key: string]: JsonValue;
|
|
5
7
|
};
|
|
@@ -10,7 +12,6 @@ export type ResolvedOperation = {
|
|
|
10
12
|
operation: JsonObject;
|
|
11
13
|
components?: Record<string, JsonObject>;
|
|
12
14
|
};
|
|
13
|
-
export declare function resolveRef<T>(ref: string, def?: OpenAPIDefinition): T | undefined;
|
|
14
15
|
type ResolveOperationParams = {
|
|
15
16
|
operation: McpEndpoint;
|
|
16
17
|
pathParams: Referenced<OpenAPIParameter>[];
|
|
@@ -22,5 +23,4 @@ export declare function resolveOperation({ operation, pathParams, definition, }:
|
|
|
22
23
|
* `maxBytes` or the inliner cannot read the operation.
|
|
23
24
|
*/
|
|
24
25
|
export declare function inlineOperation({ operation, pathParams, definition }: ResolveOperationParams, maxBytes: number): ResolvedOperation | undefined;
|
|
25
|
-
export {};
|
|
26
26
|
//# sourceMappingURL=resolve-refs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
import{resolveParameters as y,resolveRef as p,resolveRequestBody as g,resolveResponses as m,unescapeJsonPointerToken as $}from"@redocly/api-docs/lib/utils/inline-openapi-refs.js";const h=/^#\/components\/([^/]+)\/([^/]+)(\/.*)?$/;function d(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function l(e,o){const n={},s=new Set,t=new Set,r=[e];for(;r.length>0;){const c=r.pop();if(!(c===null||typeof c!="object")){if(d(c)&&typeof c.$ref=="string"&&!s.has(c.$ref)){s.add(c.$ref);const[,f,i]=h.exec(c.$ref)??[],u=i&&$(i);if(f&&u&&!t.has(`${f}/${u}`)){const a=p(`#/components/${f}/${i}`,o);a!==void 0&&(t.add(`${f}/${u}`),(n[f]??={})[u]=a,r.push(a))}}for(const f of Object.values(c))r.push(f)}}return Object.keys(n).length>0?n:void 0}function b(e,o){const n=d(e)&&typeof e.$ref=="string"?p(e.$ref,o)??e:e,{name:s,in:t}=d(n)?n:{};return typeof s=="string"&&typeof t=="string"?`${t}:${s}`:JSON.stringify(e)}function O(e,o,n){const s=new Map;for(const t of[...e,...o])t&&s.set(b(t,n),t);return[...s.values()]}function x({operation:e,pathParams:o,definition:n}){const s={...e,parameters:O(o,e.parameters??[],n)};return{operation:s,components:l(s,n)}}class w extends Error{}function B(e){let o=e;const n=new Map;return(s,t)=>{let r=t===void 0?void 0:n.get(t);if(r===void 0&&(r=JSON.stringify(s).length,t!==void 0&&n.set(t,r)),o-=r,o<0)throw new w}}function R({operation:e,pathParams:o,definition:n},s){const t=B(s);try{const r={...e,parameters:y({pathParams:o,opParams:e.parameters||[],definition:n,charge:t}),requestBody:g(e.requestBody,n,t),responses:e.responses&&m(e.responses,n,t)};return{operation:r,components:l(r,n)}}catch{return}}export{R as inlineOperation,x as resolveOperation,p as resolveRef};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RedoclyConfig } from '@redocly/config';
|
|
2
|
+
export type ApiCallsConfig = NonNullable<NonNullable<RedoclyConfig['mcp']>['apiCalls']>;
|
|
3
|
+
type ApiCallsHttpMethod = NonNullable<ApiCallsConfig['allowedHttpMethods']>[number];
|
|
4
|
+
/** The methods agents may send when `mcp.apiCalls.allowedHttpMethods` is not set. */
|
|
5
|
+
export declare const DEFAULT_ALLOWED_HTTP_METHODS: readonly ApiCallsHttpMethod[];
|
|
6
|
+
/** Upper-case HTTP methods agents may send; read-only unless `redocly.yaml` allows more. */
|
|
7
|
+
export declare function getAllowedHttpMethods(mcp: RedoclyConfig['mcp'] | undefined): string[];
|
|
8
|
+
/** Whether every allowed method is safe by HTTP semantics (`GET`, `HEAD`, `OPTIONS`). */
|
|
9
|
+
export declare function allowsOnlySafeHttpMethods(mcp: RedoclyConfig['mcp'] | undefined): boolean;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=allowed-http-methods.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const o=["GET","HEAD","OPTIONS"],r={GET:!0,HEAD:!0,OPTIONS:!0};function T(t){return(t?.apiCalls?.allowedHttpMethods??o).map(e=>e.toUpperCase())}function p(t){return T(t).every(e=>r[e])}export{o as DEFAULT_ALLOWED_HTTP_METHODS,p as allowsOnlySafeHttpMethods,T as getAllowedHttpMethods};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { OpenAPIDefinition, OpenAPIServer } from '@redocly/api-docs';
|
|
2
|
+
export type ApiCallOperation = {
|
|
3
|
+
pattern: RegExp;
|
|
4
|
+
/** Upper case. */
|
|
5
|
+
method: string;
|
|
6
|
+
/**
|
|
7
|
+
* False when `x-mcp.apiCalls.hide` or `x-mcp.docs.hide` on the path item or operation, or an
|
|
8
|
+
* `mcp.apiCalls.excludeOperations` entry, excludes it.
|
|
9
|
+
*/
|
|
10
|
+
callable: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* False when RBAC hides the operation from the caller. The bridge then answers as for an
|
|
13
|
+
* undocumented path, so the reply does not reveal that the operation exists.
|
|
14
|
+
*/
|
|
15
|
+
visible: boolean;
|
|
16
|
+
/** Number of `{variable}` segments in the path; fewer is more specific. */
|
|
17
|
+
variables: number;
|
|
18
|
+
};
|
|
19
|
+
/** An `mcp.apiCalls.excludeOperations` entry of one API; no `method` excludes every method of `path`. */
|
|
20
|
+
export type ExcludedOperation = {
|
|
21
|
+
method?: string;
|
|
22
|
+
path: string;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Every operation of the unfiltered `description`, with the ones missing from `visibleDescription`,
|
|
26
|
+
* the caller's RBAC-filtered copy, marked not visible. A hidden or `excluded` operation stays in the
|
|
27
|
+
* list, so a less specific template cannot claim its requests.
|
|
28
|
+
*/
|
|
29
|
+
export declare function apiCallOperationsOf(description: OpenAPIDefinition, visibleDescription: OpenAPIDefinition, servers: readonly OpenAPIServer[], excluded?: readonly ExcludedOperation[]): ApiCallOperation[];
|
|
30
|
+
/** The documented operations that an upper-case `method` on `pathname` matches. */
|
|
31
|
+
export declare function matchingApiCallOperations(operations: readonly ApiCallOperation[], pathname: string, method: string): ApiCallOperation[];
|
|
32
|
+
//# sourceMappingURL=api-call-operations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{OPERATION_METHODS as P}from"../docs-mcp/tools/openapi/constants.js";import{isEndpointHidden as u}from"../utils/xmcp-utils.js";import{basePathPrefix as T,operationHttpMethod as d,pathPattern as y,pathTemplateKey as C,pathVariableCount as H}from"./route-pattern.js";const M=new Set(P);function m(t){return u(t,"apiCalls")||u(t,"docs")}function w(t,n,i,a=[]){const b=T(i),c=new Set(a.map(({method:e,path:o})=>p(e&&d(e),o))),f=[];for(const[e,o]of Object.entries(t.paths??{})){if(!o||typeof o!="object")continue;const h=y(b,e);if(!h)continue;const O=m(o)||c.has(p(void 0,e)),x=n.paths?.[e],E=H(e);for(const[r,s]of Object.entries(o)){if(!M.has(r.toLowerCase())||!s||typeof s!="object")continue;const l=d(r);f.push({pattern:h,method:l,callable:!O&&!m(s)&&!c.has(p(l,e)),visible:!!x?.[r],variables:E})}}return f}function p(t,n){return`${t??"*"} ${C(n)}`}function I(t,n,i){return t.filter(a=>a.method===i&&a.pattern.test(n))}export{w as apiCallOperationsOf,I as matchingApiCallOperations};
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { ApiDescriptionInfo, McpToolContext } from '../types.js';
|
|
2
|
-
|
|
2
|
+
import type { ExcludedOperation } from './api-call-operations.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The APIs an agent may call in this request: the build-stamped `fetchEligible` entries of the
|
|
5
|
+
* already RBAC-filtered map, minus what `mcp.apiCalls` removes. The config is read from the
|
|
6
|
+
* request context, so a `redocly.yaml` edit applies without restarting the dev server.
|
|
7
7
|
* No EntitlementsProvider read — this code runs in graphs where the provider is uninitialized.
|
|
8
8
|
*/
|
|
9
|
+
export declare function getCallableApis(context: McpToolContext): ApiDescriptionInfo[];
|
|
10
|
+
/** The `mcp.apiCalls.excludeOperations` entries whose `file` glob matches the API at `relativePath`. */
|
|
11
|
+
export declare function getExcludedOperations(context: McpToolContext, relativePath: string): ExcludedOperation[];
|
|
9
12
|
export declare function isFetchCapable(context: McpToolContext): boolean;
|
|
10
13
|
//# sourceMappingURL=callable-apis.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
import c from"picomatch";function r(e){const i=e.config?.mcp?.apiCalls;if(i?.hide)return[];const t=c(i?.excludeFiles??[]);return Object.values(e.apiDescriptionsMap??{}).filter(l=>l.fetchEligible&&!t(l.relativePath))}function n(e,i){return(e.config?.mcp?.apiCalls?.excludeOperations??[]).filter(({file:t})=>c.isMatch(i,t))}function p(e){return r(e).length>0}export{r as getCallableApis,n as getExcludedOperations,p as isFetchCapable};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RedoclyConfig } from '@redocly/config';
|
|
2
|
+
/**
|
|
3
|
+
* `mcp` with `apiCalls` narrowed by the caller's `metadata.apiCalls` (`hide`, `allowedHttpMethods`),
|
|
4
|
+
* which the AI Assistant fills from `aiAssistant.mcp.apiCalls`. The caller can only take API calls
|
|
5
|
+
* or methods away: a value of another shape changes nothing, and a method list that keeps none of
|
|
6
|
+
* the project's methods turns API calls off.
|
|
7
|
+
*/
|
|
8
|
+
export declare function narrowMcpConfigForCaller(mcp: RedoclyConfig['mcp'], metadata: Record<string, unknown> | undefined): RedoclyConfig['mcp'];
|
|
9
|
+
//# sourceMappingURL=caller-api-calls.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{DEFAULT_ALLOWED_HTTP_METHODS as d}from"./allowed-http-methods.js";function h(e,a){const o=a?.apiCalls;if(!o||typeof o!="object")return e;const{hide:i,allowedHttpMethods:s}=o,t={...e?.apiCalls};if(i===!0&&(t.hide=!0),Array.isArray(s)){const n=new Set(s.map(r=>String(r).toUpperCase())),l=(e?.apiCalls?.allowedHttpMethods??d).filter(r=>n.has(r));l.length===0?t.hide=!0:t.allowedHttpMethods=l}return{...e,apiCalls:t}}export{h as narrowMcpConfigForCaller};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{inputRequired as
|
|
2
|
-
`)}async function
|
|
3
|
-
`)
|
|
4
|
-
`)}function b({status:e,detail:t}){return t?`HTTP ${e}: ${t}`:`HTTP ${e}`}async function f({kv:e,subject:t,baseUrl:
|
|
5
|
-
`)}export{m as CREDENTIALS_INPUT_REQUEST_KEY,
|
|
1
|
+
import{inputRequired as d,inputResponse as I}from"@modelcontextprotocol/server";import{withPathPrefix as w}from"@redocly/theme/core/utils";import{MCP_CREDENTIALS_ROUTE as E}from"../../../constants/mcp.js";import{MANAGE_API_CREDENTIALS_TOOL_NAME as h}from"../constants.js";import{secretStore as l,ELICITATION_TTL_MINUTES as u}from"./secret-store.js";import{operationsForScheme as A,orderedSchemesOf as P}from"./upstream-auth-scheme.js";import{ANONYMOUS_SUBJECT_PREFIX as p}from"./subject.js";const m="credentials",c=new Map;function F(e,t){const n=c.get(e);if(n)return n;const i=t().finally(()=>{c.delete(e)});return c.set(e,i),i}function M(e){return!!e?.elicitation?.url}function D(e){return e.envelope!==void 0}function H(e){const t=I(e,m);if(t.kind==="elicit")return t.action==="accept"?"accepted":"declined"}function q(e,t){return[`The user accepted the prompt to add credentials for the "${e}" API.`,...t?["The credentials page:",t]:[],"",`Wait until the user says they saved the credentials, then retry the call that needed them. Do not call \`${h}\` again unless the user asks for a new link.`].join(`
|
|
2
|
+
`)}async function B(e,{kv:t,subject:n,baseUrl:i,apis:r}){if(typeof e!="string")return;const s=await l.peekElicitation(t,e);if(!(s?.subject!==n||!r.includes(s.api)))return{apiName:s.name,url:$(i,e)}}function W(e,t){return Object.keys(e).length>1?t:void 0}async function Y(e){const{token:t,url:n}=await f(e);return d({inputRequests:{[m]:d.elicitUrl({message:C(e),url:n})},requestState:t})}async function z(e){const{url:t}=await f(e);return x(e,t,S(e.subject))}function G(e,t){const n=b(e);if(e.status===401&&!e.sentScheme)return[`The "${e.name}" API returned ${n}. No stored credential was sent with this request, so this result says nothing about the stored credentials.`,"","Do not replace them. Check whether this operation needs a different kind of credential or a different route."].join(`
|
|
3
|
+
`);const i=e.status===401?`The "${e.name}" API rejected the stored ${t?`"${t}" `:""}credential (${n}).`:`The "${e.name}" API rejected the request with ${n}.`,r=t?`api: "${e.api}" and scheme: "${t}"`:`api: "${e.api}"`;return[i,"",`Do not retry the same call. Call \`${h}\` with ${r} to get a link the user has to open in a browser to replace or remove the stored credentials, then retry once they confirm they saved them.`].join(`
|
|
4
|
+
`)}function b({status:e,detail:t}){return t?`HTTP ${e}: ${t}`:`HTTP ${e}`}async function f({kv:e,subject:t,baseUrl:n,target:i,auth:r,apiAuth:s}){const a=s?k(s):[],o=await l.createElicitation(e,{subject:t,api:i.api,name:i.name,scheme:i.scheme,...i.schemeLabel?{schemeLabel:i.schemeLabel}:{},auth:r,...a.length>1?{schemes:a}:{},...a.length>1&&i.acceptedSchemes?{acceptedSchemes:i.acceptedSchemes}:{}});return{token:o,url:$(n,o)}}function k(e){return P(e.schemes).map(t=>{const n=A(e,t),i=e.descriptions?.[t];return{name:t,...i?{label:i}:{},auth:e.schemes[t],...n.length>0?{operations:n}:{}}})}function S(e){return e.startsWith(p)?e.slice(p.length):void 0}function C({target:e,hasExistingSecret:t,reason:n}){const i=`The link works for ${u} minutes.`,r=O(e),s=n.kind==="authFailure"&&n.detail?` The API answered: ${n.detail}.`:"";return t?`Open this page in a browser to replace or remove the stored credentials for ${r}. They stay out of the chat. ${i}`:`${g(r)} needs credentials.${s} Open this page in a browser to paste them - they stay out of the chat. ${i}`}function O({name:e,schemeLabel:t}){return t?`the "${t}" scheme of the "${e}" API`:`the "${e}" API`}function g(e){return e.charAt(0).toUpperCase()+e.slice(1)}function $(e,t){const n=w(E);if(!e)return`${n}?token=${t}`;try{const i=new URL(n,e);return i.searchParams.set("token",t),i.toString()}catch{return`${e}${n}?token=${t}`}}function x({target:e,reason:t,hasExistingSecret:n},i,r){const a=[`**Stop and show the user this link. They have to open it in a browser to ${n?`replace or remove the stored credentials for the "${e.name}" API`:`add credentials for the "${e.name}" API`} - nothing continues until they do.**`,"",i,"",`The link is valid ${u} minutes and opens a form on this site. The site stores the credentials and adds them to API calls: never ask for them in the conversation and never pass them as a tool argument.`,"",t.kind==="authFailure"?`The upstream API "${e.name}" returned ${t.status}${t.detail?` (${t.detail})`:""} and needs credentials.`:n?`Credentials are already stored for the "${e.name}" API; the link lets the user replace or remove them.`:`The "${e.name}" API needs credentials.`,...e.schemeLabel?[`The operation authenticates with its "${e.schemeLabel}" security scheme.`]:[],r?`Once the user confirms they saved them, retry the call that needed them with sessionHandle: "${r}" - the credentials are stored against that handle, applied server-side, and never appear in the conversation.`:"Once the user confirms they saved them, retry the call that needed them - the credentials are applied server-side and never appear in the conversation."],o=t.kind==="authFailure"?t.otherApiNames??[]:[];if(o.length>0){const T=o.map(y=>`"${y}"`).join(", ");a.push("",`The remaining APIs (${T}) will prompt for credentials on subsequent calls.`)}return a.join(`
|
|
5
|
+
`)}export{m as CREDENTIALS_INPUT_REQUEST_KEY,B as acceptedCredentialsLink,q as buildCredentialsAcceptedHint,G as buildCredentialsToolHint,z as buildUpstreamCredentialFallback,Y as buildUpstreamCredentialInputRequired,H as credentialElicitationAnswer,F as dedupeElicitation,D as isStatelessRequest,W as schemeLabelOf,M as supportsUrlElicitation};
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import type { OpenAPIDefinition, OpenAPIMcp } from '@redocly/api-docs';
|
|
2
|
-
import type {
|
|
2
|
+
import type { RedoclyConfig } from '@redocly/config';
|
|
3
3
|
/** Whether the gateway fetch capability is on: the feature flag plus none of mcp/docs hidden. */
|
|
4
4
|
export declare function isGatewayCapabilityEnabled(mcp?: RedoclyConfig['mcp']): boolean;
|
|
5
|
-
/**
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Whether the API description allows agent calls: every documented API does unless its
|
|
7
|
+
* `info.x-mcp.apiCalls.hide` is set. Who may call it is decided per request by RBAC, and
|
|
8
|
+
* `mcp.apiCalls` config by `getCallableApis`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isGatewayEligible(info: OpenAPIDefinition['info'] | undefined): boolean;
|
|
7
11
|
/**
|
|
8
12
|
* Whether an API docs page advertises the `/mcp` endpoint via an injected root-level x-mcp:
|
|
9
13
|
* the capability is on, the author has not provided their own x-mcp servers, and the API is
|
|
@@ -13,6 +17,6 @@ export declare function shouldAdvertiseGatewayXMcp(definition: (OpenAPIDefinitio
|
|
|
13
17
|
'x-mcp'?: {
|
|
14
18
|
servers?: unknown[];
|
|
15
19
|
};
|
|
16
|
-
}) | undefined, relativePath: string,
|
|
20
|
+
}) | undefined, relativePath: string, mcpConfig?: RedoclyConfig['mcp']): boolean;
|
|
17
21
|
export declare function buildGatewayXMcp(url: string): OpenAPIMcp;
|
|
18
22
|
//# sourceMappingURL=eligibility.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{MCP_PROTOCOL_VERSION as t}from"../constants.js";import{isApiIncludedInDocsMcp as i}from"../utils.js";import{isMcpGatewayEnabled as s}from"../utils/mode.js";import{isDefinitionHidden as a}from"../utils/xmcp-utils.js";function p(e){return s()&&!e?.hide&&!e?.docs?.hide}function n(e){return!a(e,"apiCalls")}function m(e,o,r){return p(r)&&!e?.["x-mcp"]?.servers?.length&&n(e?.info)&&i(o,e,r)}function f(e){return{protocolVersion:t,capabilities:{tools:{}},servers:[{url:e}],tools:[],resources:[],prompts:[]}}export{f as buildGatewayXMcp,p as isGatewayCapabilityEnabled,n as isGatewayEligible,m as shouldAdvertiseGatewayXMcp};
|
|
@@ -6,6 +6,7 @@ type GatewayFetchAuditRecord = {
|
|
|
6
6
|
outcome: GatewayFetchOutcome;
|
|
7
7
|
durationMs: number;
|
|
8
8
|
injectedSecretValues: readonly string[];
|
|
9
|
+
credentialQueryParams?: readonly string[];
|
|
9
10
|
path?: string;
|
|
10
11
|
api?: string;
|
|
11
12
|
status?: number;
|
|
@@ -20,6 +21,6 @@ type GatewayFetchAuditRecord = {
|
|
|
20
21
|
* telemetry event verbatim so a Grafana row joins onto this line; `url_sha256` identifies the
|
|
21
22
|
* request itself, since the telemetry event's `path` is deliberately normalized.
|
|
22
23
|
*/
|
|
23
|
-
export declare function logGatewayFetchAudit({ rawUrl, method, host, outcome, durationMs, injectedSecretValues, path, api, status, injectedScheme, requestHeaders, requestBody, responseHeaders, responseBody, }: GatewayFetchAuditRecord): void;
|
|
24
|
+
export declare function logGatewayFetchAudit({ rawUrl, method, host, outcome, durationMs, injectedSecretValues, credentialQueryParams, path, api, status, injectedScheme, requestHeaders, requestBody, responseHeaders, responseBody, }: GatewayFetchAuditRecord): void;
|
|
24
25
|
export {};
|
|
25
26
|
//# sourceMappingURL=fetch-audit.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{auditHeaderNames as n,auditInput as
|
|
1
|
+
import{auditHeaderNames as n,auditInput as b,auditOutputDigest as l,auditSha256 as y,auditUrl as A,logAuditRecord as D}from"../../audit/audit-log.js";function q({rawUrl:e,method:u,host:i,outcome:r,durationMs:o,injectedSecretValues:t,credentialQueryParams:_,path:h,api:f,status:m,injectedScheme:p,requestHeaders:c,requestBody:d,responseHeaders:g,responseBody:s}){const a=s===void 0?void 0:l(s);D("Record gateway fetch",{url_sha256:y(e),host:i,method:u,path:h,api:f,outcome:r,status:m,injected_scheme:p,duration_ms:o,url:A(e,t,_),request_header_names:n(c),request_body:d===void 0?void 0:b(d,t),response_header_names:n(g),response_sha256:a?.sha256,response_bytes:a?.bytes})}export{q as logGatewayFetchAudit};
|
|
@@ -2,6 +2,7 @@ import { Agent, type Dispatcher } from 'undici';
|
|
|
2
2
|
import type { SandboxFetch } from '../../codemode/types.js';
|
|
3
3
|
import type { UpstreamAuthRequiredEvent } from '../upstream-auth.js';
|
|
4
4
|
import type { StoredSecret } from '../secret-store.js';
|
|
5
|
+
import { type ApiCallOperation } from '../api-call-operations.js';
|
|
5
6
|
export declare function getDefaultAgent(): Agent;
|
|
6
7
|
export type FetchBridgeRoute = {
|
|
7
8
|
pattern: RegExp;
|
|
@@ -16,7 +17,13 @@ export type FetchBridgeApi = {
|
|
|
16
17
|
defaultScheme?: string;
|
|
17
18
|
routes?: readonly FetchBridgeRoute[];
|
|
18
19
|
credentialHeaders?: readonly string[];
|
|
20
|
+
credentialQueryParams?: readonly string[];
|
|
19
21
|
schemePriority?: readonly string[];
|
|
22
|
+
/**
|
|
23
|
+
* The documented operations agents may reach on this API's hosts; see `apiCallOperationsOf`.
|
|
24
|
+
* Undefined puts no path restriction on the API.
|
|
25
|
+
*/
|
|
26
|
+
operations?: readonly ApiCallOperation[];
|
|
20
27
|
};
|
|
21
28
|
type FetchBridgeOptions = {
|
|
22
29
|
allowedHosts: readonly RegExp[];
|
|
@@ -28,10 +35,13 @@ type FetchBridgeOptions = {
|
|
|
28
35
|
timeoutMs?: number;
|
|
29
36
|
maxResponseBytes?: number;
|
|
30
37
|
maxFetchesPerExec?: number;
|
|
38
|
+
beforeSend?: () => Promise<void>;
|
|
31
39
|
clientIp?: string;
|
|
40
|
+
/** Upper-case methods that may reach the network (`mcp.apiCalls.allowedHttpMethods`); undefined allows all. */
|
|
41
|
+
allowedHttpMethods?: readonly string[];
|
|
32
42
|
};
|
|
33
43
|
/** Build the host-side `fetch` the gateway sandbox calls. */
|
|
34
|
-
export declare function buildFetchBridge({ allowedHosts, apis, authorization, onAuthRequired, fetchImpl, dispatcher, timeoutMs, maxResponseBytes, maxFetchesPerExec, clientIp, }: FetchBridgeOptions): SandboxFetch;
|
|
44
|
+
export declare function buildFetchBridge({ allowedHosts, apis, authorization, onAuthRequired, fetchImpl, dispatcher, timeoutMs, maxResponseBytes, maxFetchesPerExec, beforeSend, clientIp, allowedHttpMethods, }: FetchBridgeOptions): SandboxFetch;
|
|
35
45
|
export declare function readCappedBody(response: Response, maxBytes: number): Promise<string>;
|
|
36
46
|
export {};
|
|
37
47
|
//# sourceMappingURL=fetch-bridge.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Agent as
|
|
1
|
+
import{Agent as ie}from"undici";import{envConfig as le}from"../../../../config/env-config.js";import{logger as K}from"../../../../tools/notifiers/logger.js";import{auditHeaderNames as de}from"../../audit/audit-log.js";import{assertUrlAllowed as X}from"./ssrf-guard.js";import{createSsrfSafeLookup as ue}from"../../../../web-server/utils/ssrf-safe-lookup.js";import{SsrfBlockedError as J}from"../../../../web-server/utils/ssrf-blocklist.js";import{GatewayFetchBlockedError as p,GatewayFetchSystemError as fe,GatewayFetchUserError as S}from"./fetch-errors.js";import{redactHeaders as he,redactSecretValuesFromHeaders as me,redactSecretsFromString as _,secretEchoForms as pe}from"./redact.js";import{extractUpstreamDetail as we}from"./upstream-detail.js";import{normalizeFetchPath as ye}from"./normalize-path.js";import{reportGatewayFetch as Z}from"./fetch-telemetry.js";import{logGatewayFetchAudit as q}from"./fetch-audit.js";import{matchingApiCallOperations as Se}from"../api-call-operations.js";import{operationHttpMethod as be}from"../route-pattern.js";const ee="x-forwarded-for",ge=3e4,Ae=1024*1024,Ee=/%2f|%5c/i,ve=new Set([401,403,407]),$e=["authorization","proxy-authorization","cookie","x-api-key"];let x;function ke(){if(!x){const e=le.isDevelopMode;e&&K.warn("Gateway MCP SSRF guard: loopback upstreams are ALLOWED (develop mode only, never in runtime)."),x=new ie({connect:{lookup:ue({allowLoopback:e})}})}return x}function nt({allowedHosts:e,apis:t=[],authorization:r,onAuthRequired:n,fetchImpl:c=fetch,dispatcher:d=ke(),timeoutMs:f=ge,maxResponseBytes:b=Ae,maxFetchesPerExec:i,beforeSend:oe,clientIp:ae,allowedHttpMethods:P}){const O=[...e,...t.flatMap(a=>a.matchers)],se=[...new Set(t.map(a=>a.name).filter(Boolean))].join(", "),j=t.flatMap(a=>Object.values(a.secrets??{})),w=[...r?[{header:"Authorization",value:r}]:[],...j].flatMap(pe),ce=j.map(a=>a.header),I=t.flatMap(a=>a.credentialQueryParams??[]);let N=0,B=!1;return async a=>{const l=be(a.method??"GET"),D=Date.now();let G,A,E,v={},$,U,H;try{if(i!==void 0){const o=Math.max(0,i);if(N+=1,N>o)throw new S(`Outbound fetch budget exceeded (max ${o} fetches per execute); split the work across calls`)}if(P&&!P.includes(l))throw new p(`${l} is not allowed: this project permits only ${P.join(", ")} API calls`,te(a.url));const s=Re(a.url,O,se);if(G=s.hostname,A=ye(s.pathname),Ee.test(s.pathname))throw new p(`${l} ${s.pathname} is refused: the path holds an encoded path separator (%2F or %5C), which could reach an operation other than the documented one`,s.hostname);const y=t.filter(o=>o.matchers.some(T=>T.test(s.hostname))),m=y.flatMap(o=>o.operations?Se(o.operations,s.pathname,l).map(T=>({api:o,callable:T.callable,visible:T.visible,variables:T.variables})):[{api:o,callable:!0,visible:!0,variables:Number.POSITIVE_INFINITY}]),k=Math.min(...m.map(o=>o.variables)),M=m.filter(o=>o.variables===k);if(y.length>0&&(m.length===0||M.some(o=>!o.visible)))throw new p(`${l} ${s.pathname} is not a documented operation of ${y.map(o=>o.name).join(", ")}; \`fetch\` calls only the operations the API description documents`,s.hostname);const V=M.find(o=>!o.callable);if(V)throw new p(`${l} ${s.pathname} is not callable: this operation of ${V.api.name} is excluded from agent calls`,s.hostname);const h=new Set(M.map(o=>o.api)).size===1?M[0].api:void 0;E=h?.slug;const z=He(a.securityScheme,h),F=z?[z]:h?Pe(h,s.pathname,l):void 0,C=h&&F?Ue(h,F):void 0,Q=C?h?.secrets?.[C]:void 0,L=Q??(r?{header:"Authorization",value:r}:void 0);v=je({callerHeaders:a.headers,injected:L,clientIp:ae});const W=xe(a.headers,s,y,L);if(W&&(L||B))throw new S(Oe(W,!!L));await oe?.(),B||=!!L,$=Q?C:void 0;const Y=new AbortController;H=setTimeout(()=>Y.abort(),f);const u=await c(s,{method:l,headers:v,body:a.body,redirect:"manual",signal:Y.signal,dispatcher:d});U=u.status;let R="";try{R=await Fe(u,b)}finally{if(ve.has(u.status)&&h&&n){const o=we(R,u.headers.get("content-type"),w);n({api:h.slug,name:h.name,status:u.status,...C?{scheme:C}:{},...F?.length?{schemes:[...F]}:{},...$?{sentScheme:$}:{},...o?{detail:o}:{}})}}let g=me(Ie(u.headers),w);if(Te(u.status)){g=he(g,ce);const{location:o}=g;o&&!De(o,s,O)&&delete g.location}return Z({host:s.hostname,method:l,outcome:"success",durationMs:Date.now()-D,path:A,api:E,status:u.status,bytes:Buffer.byteLength(R,"utf8")}),q({rawUrl:s.href,method:l,host:s.hostname,outcome:"success",durationMs:Date.now()-D,injectedSecretValues:w,credentialQueryParams:I,path:A,api:E,status:u.status,injectedScheme:$,requestHeaders:v,requestBody:a.body,responseHeaders:g,responseBody:R}),{status:u.status,statusText:_(u.statusText,w),ok:u.ok,headers:g,bodyText:_(R,w)}}catch(s){const{outcome:y,userSafe:m}=Le(s),k=G??(m instanceof p?m.host:void 0)??te(a.url)??"invalid-url";throw Z({host:k,method:l,outcome:y,durationMs:Date.now()-D,path:A,api:E,status:U}),q({rawUrl:a.url,method:l,host:k,outcome:y,durationMs:Date.now()-D,injectedSecretValues:w,credentialQueryParams:I,path:A,api:E,status:U,injectedScheme:$,requestHeaders:v,requestBody:a.body}),m||(K.error(`Gateway MCP fetch failed [${l} ${k}]: ${_(Me(s),w)} headers=${de(v)}`),new fe("Upstream fetch failed due to an internal error"))}finally{H!==void 0&&clearTimeout(H)}}}function Ce(e){let t=e;for(let r=0;r<5&&t instanceof Error;r++){if(t instanceof S||t instanceof J)return t;t=t.cause}}function Le(e){const t=Ce(e);return{outcome:t instanceof p||t instanceof J?"blocked":t?"user_error":"system_error",userSafe:t}}function Re(e,t,r){try{return X(e,t)}catch(n){throw n instanceof p&&r?new p(`${n.message}. \`fetch\` reaches only the documented hosts of the callable APIs (${r}); other documented APIs can be read through the tools but not called.`,n.host):n}}function te(e){try{return new URL(e).hostname||void 0}catch{return}}function Te(e){return e>=300&&e<400}function De(e,t,r){try{return!!X(new URL(e,t).href,r)}catch{return!1}}function Me(e){if(!(e instanceof Error))return String(e);const t=e.cause;return t instanceof Error?`${e.message}: ${t.message}`:e.message}async function Fe(e,t){const r=Number(e.headers.get("content-length"));if(Number.isFinite(r)&&r>t)throw re(t,r);if(!e.body)return"";const n=e.body.getReader(),c=new TextDecoder;let d=0,f="";try{for(;;){const{done:b,value:i}=await n.read();if(b)break;if(d+=i.byteLength,d>t)throw re(t,d);f+=c.decode(i,{stream:!0})}return f+c.decode()}finally{await n.cancel().catch(()=>{})}}function re(e,t){return new S(`Response is too large (over ${e} bytes, got ${t}); request less data`)}function Pe(e,t,r){const n=e.routes?.find(c=>c.method===r&&c.pattern.test(t));return n?n.schemes:_e(e)}function Ue(e,t){const r=t.filter(n=>e.secrets?.[n]);return r.length===0?t[0]:r.reduce((n,c)=>{const d=e.secrets?.[n]?.savedAt??0;return(e.secrets?.[c]?.savedAt??0)>d?c:n})}function He(e,t){if(e==null)return;if(typeof e!="string")throw new S("securityScheme must be a security scheme name (a string)");if(!t)throw new S("securityScheme cannot be applied: the URL host does not identify exactly one callable API");const r=t.schemePriority??[];if(r.length>0&&!r.includes(e))throw new S(`Unknown securityScheme "${e}" for ${t.name}; valid values: ${r.join(", ")}`);return e}function _e(e){const t=Object.keys(e.secrets??{});if(t.length===0)return e.defaultScheme?[e.defaultScheme]:[];const r=(e.schemePriority??[]).filter(n=>t.includes(n));return[...r,...t.filter(n=>!r.includes(n))]}function xe(e,t,r,n){const c=ne([...$e,...r.flatMap(i=>i.credentialHeaders??[]),...n?[n.header]:[]]),d=Object.keys(e??{}).find(i=>c.has(i.toLowerCase()));if(d)return{kind:"header",name:d};const f=ne(r.flatMap(i=>i.credentialQueryParams??[])),b=[...t.searchParams.keys()].find(i=>f.has(i.toLowerCase()));return b?{kind:"query parameter",name:b}:void 0}function ne(e){return new Set(e.map(t=>t.toLowerCase()))}function Oe({kind:e,name:t},r){return r?`Remove the "${t}" ${e}: this request already sends the stored credential.`:`Remove the "${t}" ${e}: this script already sent a stored credential, so it cannot also send its own.`}function je({callerHeaders:e,injected:t,clientIp:r}){const n={};for(const[c,d]of Object.entries(e??{})){const f=c.toLowerCase();f==="host"||f==="content-length"||f!==ee&&(n[c]=d)}return r&&(n[ee]=r),t&&(n[t.header]=t.value),n}function Ie(e){const t={};return e.forEach((r,n)=>{t[n.toLowerCase()]=r}),t}export{nt as buildFetchBridge,ke as getDefaultAgent,Fe as readCappedBody};
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
export declare const REDACTED = "\u00AB[redacted]\u00BB";
|
|
2
|
+
/**
|
|
3
|
+
* Every form in which an upstream can echo an injected credential: the full header value, the
|
|
4
|
+
* token without its auth scheme, the decoded `user:password` of a Basic value, and the cookie
|
|
5
|
+
* value without its name, each also JSON-escaped and URL-encoded. Parts under 8 characters are
|
|
6
|
+
* left out so they do not redact unrelated text.
|
|
7
|
+
*/
|
|
8
|
+
export declare function secretEchoForms({ header, value }: {
|
|
9
|
+
header: string;
|
|
10
|
+
value: string;
|
|
11
|
+
}): string[];
|
|
2
12
|
export declare function redactSecretsFromString(text: string, secretValues: readonly string[]): string;
|
|
13
|
+
export declare function redactSecretValuesFromHeaders(headers: Record<string, string>, secretValues: readonly string[]): Record<string, string>;
|
|
3
14
|
export declare function redactHeaders(headers: Record<string, string>, extraSensitiveNames?: readonly string[]): Record<string, string>;
|
|
4
15
|
//# sourceMappingURL=redact.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const i="\xAB[redacted]\xBB",f=["authorization","proxy-authorization","cookie","set-cookie"],m=8,a=/^([A-Za-z][\w-]*) +(\S+)$/;function p({header:t,value:e}){if(!e.trim())return[];const n=u(t,e).filter(r=>r.trim().length>=8);return[...new Set([e,...n].flatMap(E))]}function u(t,e){if(t.toLowerCase()==="cookie"){const c=e.indexOf("=");return c>0?[e.slice(c+1)]:[]}const n=a.exec(e);if(!n)return[];const[,r,o]=n,s=r.toLowerCase()==="basic"?d(o):void 0;return s?[o,s]:[o]}function d(t){const e=Buffer.from(t,"base64").toString("utf8");return Buffer.from(e,"utf8").toString("base64")===t&&e.includes(":")?e:void 0}function E(t){const e=JSON.stringify(t).slice(1,-1);return[t,e,e.replace(/\//g,"\\/"),encodeURIComponent(t)]}function l(t,e){const n=e.filter(o=>o.trim()).sort((o,s)=>s.length-o.length);let r=t;for(const o of n)r=r.split(o).join(i);return r}function S(t,e){const n={};for(const[r,o]of Object.entries(t))n[r]=l(o,e);return n}function x(t,e=[]){const n=new Set([...f,...e].map(o=>o.toLowerCase())),r={};for(const[o,s]of Object.entries(t))r[o]=n.has(o.toLowerCase())?i:s;return r}export{i as REDACTED,x as redactHeaders,S as redactSecretValuesFromHeaders,l as redactSecretsFromString,p as secretEchoForms};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{isDefined as l}from"../../../../utils/guards/is-defined.js";import{filterApiDescriptionByAccess as c,readApiDescriptionFromFs as u}from"../docs-mcp/tools/openapi/utils.js";import{isDefinitionHidden as d}from"../utils/xmcp-utils.js";import{getAllowedHttpMethods as m}from"./allowed-http-methods.js";import{getCallableApis as f,getExcludedOperations as h}from"./callable-apis.js";import{apiCallOperationsOf as p}from"./api-call-operations.js";import{buildAllowedHostMatchers as n}from"./fetch/allowed-hosts.js";import{buildFetchBridge as w}from"./fetch/fetch-bridge.js";import{GatewayFetchUserError as b}from"./fetch/fetch-errors.js";import{sendFetchCount as g}from"./rate-limit/fetch-limiter/fetch-count-client.js";import{credentialHeadersOf as v,orderedSchemesOf as y,resolveUpstreamApiAuth as P}from"./upstream-auth-scheme.js";async function z(e,t){const i=await Promise.all(f(e).map(async r=>{const a=await F(e,r.relativePath),o=a?.visible;if(o&&d(o.info,"apiCalls"))return;const s=await P(e,r.relativePath,async()=>o);return{slug:r.relativePath,name:r.name,matchers:n(r.servers??[]),secrets:e.upstreamSecrets?.[r.relativePath],defaultScheme:s.defaultScheme,routes:s.routes,credentialHeaders:v(s.schemes),credentialQueryParams:s.credentialQueryParams,schemePriority:y(s.schemes),operations:a?.visible?p(a.full,a.visible,r.servers??[],h(e,r.relativePath)):[]}}));return w({allowedHosts:n([]),apis:i.filter(l),authorization:e.upstreamAuthorization,onAuthRequired:t,maxFetchesPerExec:e.gatewayFetchBudgetPerExec,beforeSend:A(e),clientIp:e.clientIp,allowedHttpMethods:m(e.config?.mcp)})}function A(e){const{fetchCountRequest:t,internalBaseUrl:i}=e;if(!(!t||!i))return async()=>{const r=await g(i,t);if(r)throw new b(r)}}async function F(e,t){try{const i=await u(t,e.outdir||"");if(!i)return;const r=c(i,{user:e.user,rbac:e.config?.access?.rbac||{},requiresLogin:!!e.config?.access?.requiresLogin});return{full:i,visible:r}}catch{return}}export{z as buildExecuteHostFetch};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { FetchCountRequest } from '../../../types.js';
|
|
2
|
+
/** The refusal when a limit is reached; when the check itself fails, the fetch is allowed. */
|
|
3
|
+
export declare function sendFetchCount(serverBaseUrl: string, { caller, secret }: FetchCountRequest): Promise<string | undefined>;
|
|
4
|
+
//# sourceMappingURL=fetch-count-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{withPathPrefix as i}from"@redocly/theme/core/utils";import{ServerRoutes as a}from"../../../../../../constants/api.js";import{logger as e}from"../../../../../tools/notifiers/logger.js";const c=5e3;async function d(r,{caller:n,secret:o}){try{const t=await fetch(`${r}${i(a.MCP_FETCH_COUNT)}`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({caller:n}),signal:AbortSignal.timeout(c)});if(t.status===429)return await t.text();t.ok||e.warn(`Fetch limit check answered ${t.status}; the fetch was allowed uncounted`);return}catch(t){e.warn(`Fetch limit check failed; the fetch was allowed uncounted: ${String(t)}`);return}}export{d as sendFetchCount};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** The secret of this server process; only its own execute workers receive it. */
|
|
2
|
+
export declare function fetchCountSecret(): string;
|
|
3
|
+
export declare function isFetchCountSecret(value: string | null | undefined): boolean;
|
|
4
|
+
//# sourceMappingURL=fetch-count-secret.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{randomBytes as o,timingSafeEqual as f}from"node:crypto";const n=o(32).toString("base64url");function i(){return n}function u(t){if(typeof t!="string")return!1;const e=Buffer.from(t),r=Buffer.from(n);return e.length===r.length&&f(e,r)}export{i as fetchCountSecret,u as isFetchCountSecret};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FetchCountRequest } from '../../../types.js';
|
|
2
|
+
import type { FetchCounterRepository } from './fetch-counter-repository.js';
|
|
3
|
+
import type { GatewayFetchLimits } from './fetch-limits.js';
|
|
4
|
+
type FetchLimits = Pick<GatewayFetchLimits, 'ratePerMin' | 'quotaPerDay'>;
|
|
5
|
+
/**
|
|
6
|
+
* Counts one fetch atomically and returns the refusal when a window is full. A signed-in user
|
|
7
|
+
* is counted by subject, an anonymous caller by IP; a caller with neither is not counted.
|
|
8
|
+
*/
|
|
9
|
+
export declare function applyFetchCount(store: FetchCounterRepository, caller: FetchCountRequest['caller'], limits: FetchLimits, now: number): Promise<string | undefined>;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=fetch-count.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{logger as u}from"../../../../../tools/notifiers/logger.js";const d=[{seconds:60,limitOf:"ratePerMin",per:"minute"},{seconds:86400,limitOf:"quotaPerDay",per:"day"}];function a(c,r,i){const n=Math.floor(i/1e3);return d.map(e=>{const t=Math.floor(n/e.seconds);return{key:["mcp","fetch-rl",c,String(e.seconds),String(t)],per:e.per,limit:r[e.limitOf],retryAfterSeconds:Math.max(1,(t+1)*e.seconds-n),ttlInSeconds:e.seconds}})}async function l(c,r){const i=await c.getMany(r.map(e=>e.key)),n=r.filter((e,t)=>(i[t]?.value?.count??0)>=e.limit);return n.length>0?n.reduce((e,t)=>e.retryAfterSeconds>=t.retryAfterSeconds?e:t):r.reduce((e,t)=>e.retryAfterSeconds<=t.retryAfterSeconds?e:t)}async function y(c,r,i,n){const e=r.subject??(r.ip&&`ip:${r.ip}`);if(!e)return;const t=a(e,i,n);try{if(await c.incrementBelowLimits(t))return;const{limit:o,per:s,retryAfterSeconds:f}=await l(c,t);return`Outbound fetch limit reached (${o} fetches per ${s}); retry in ${f}s`}catch(o){u.warn(`Fetch limit store failed; the fetch was allowed uncounted: ${String(o)}`);return}}export{y as applyFetchCount};
|
package/dist/server/plugins/mcp/gateway-mcp/rate-limit/fetch-limiter/fetch-counter-repository.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { KvKey } from '@redocly/config';
|
|
2
|
+
import type { RepositoryInstanceOptions } from '../../../../../providers/database/types.js';
|
|
3
|
+
import { KvRepository } from '../../../../../persistence/kv/repositories/kv-repository.js';
|
|
4
|
+
export declare class FetchCounterRepository extends KvRepository {
|
|
5
|
+
static create(options: RepositoryInstanceOptions): Promise<FetchCounterRepository>;
|
|
6
|
+
/** Adds one to every slot in one statement unless a slot is at its limit; tells whether it did. */
|
|
7
|
+
incrementBelowLimits(slots: readonly {
|
|
8
|
+
key: KvKey;
|
|
9
|
+
limit: number;
|
|
10
|
+
ttlInSeconds: number;
|
|
11
|
+
}[]): Promise<boolean>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=fetch-counter-repository.d.ts.map
|