mcp4openapi 0.2.8 → 0.3.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/README.md +143 -63
- package/dist/scripts/validate-profile.js +3 -3
- package/dist/scripts/validate-profile.js.map +1 -1
- package/dist/src/argument-normalizer.d.ts +5 -0
- package/dist/src/argument-normalizer.d.ts.map +1 -0
- package/dist/src/argument-normalizer.js +61 -0
- package/dist/src/argument-normalizer.js.map +1 -0
- package/dist/src/auth/oauth-provider.d.ts +131 -0
- package/dist/src/auth/oauth-provider.d.ts.map +1 -0
- package/dist/src/auth/oauth-provider.js +839 -0
- package/dist/src/auth/oauth-provider.js.map +1 -0
- package/dist/src/cli-config.d.ts +9 -0
- package/dist/src/cli-config.d.ts.map +1 -0
- package/dist/src/cli-config.js +111 -0
- package/dist/src/cli-config.js.map +1 -0
- package/dist/src/core/cli-config.d.ts +9 -0
- package/dist/src/core/cli-config.d.ts.map +1 -0
- package/dist/src/core/cli-config.js +125 -0
- package/dist/src/core/cli-config.js.map +1 -0
- package/dist/src/core/constants.d.ts +86 -0
- package/dist/src/core/constants.d.ts.map +1 -0
- package/dist/src/core/constants.js +86 -0
- package/dist/src/core/constants.js.map +1 -0
- package/dist/src/core/errors.d.ts +59 -0
- package/dist/src/core/errors.d.ts.map +1 -0
- package/dist/src/core/errors.js +119 -0
- package/dist/src/core/errors.js.map +1 -0
- package/dist/src/core/filtering.d.ts +19 -0
- package/dist/src/core/filtering.d.ts.map +1 -0
- package/dist/src/core/filtering.js +292 -0
- package/dist/src/core/filtering.js.map +1 -0
- package/dist/src/core/index.d.ts +26 -0
- package/dist/src/core/index.d.ts.map +1 -0
- package/dist/src/core/index.js +276 -0
- package/dist/src/core/index.js.map +1 -0
- package/dist/src/core/lib.d.ts +8 -0
- package/dist/src/core/lib.d.ts.map +1 -0
- package/dist/src/core/lib.js +7 -0
- package/dist/src/core/lib.js.map +1 -0
- package/dist/src/core/logger.d.ts +59 -0
- package/dist/src/core/logger.d.ts.map +1 -0
- package/dist/src/core/logger.js +197 -0
- package/dist/src/core/logger.js.map +1 -0
- package/dist/src/core/metrics.d.ts +97 -0
- package/dist/src/core/metrics.d.ts.map +1 -0
- package/dist/src/core/metrics.js +273 -0
- package/dist/src/core/metrics.js.map +1 -0
- package/dist/src/core/naming-warnings.d.ts +23 -0
- package/dist/src/core/naming-warnings.d.ts.map +1 -0
- package/dist/src/core/naming-warnings.js +83 -0
- package/dist/src/core/naming-warnings.js.map +1 -0
- package/dist/src/core/naming.d.ts +58 -0
- package/dist/src/core/naming.d.ts.map +1 -0
- package/dist/src/core/naming.js +510 -0
- package/dist/src/core/naming.js.map +1 -0
- package/dist/src/errors.d.ts +6 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +15 -6
- package/dist/src/errors.js.map +1 -1
- package/dist/src/filtering.d.ts +19 -0
- package/dist/src/filtering.d.ts.map +1 -0
- package/dist/src/filtering.js +292 -0
- package/dist/src/filtering.js.map +1 -0
- package/dist/src/generated-schemas.d.ts +290 -79
- package/dist/src/generated-schemas.d.ts.map +1 -1
- package/dist/src/generated-schemas.js +17 -2
- package/dist/src/generated-schemas.js.map +1 -1
- package/dist/src/http-transport-config.d.ts +6 -0
- package/dist/src/http-transport-config.d.ts.map +1 -0
- package/dist/src/http-transport-config.js +47 -0
- package/dist/src/http-transport-config.js.map +1 -0
- package/dist/src/http-transport.d.ts +63 -13
- package/dist/src/http-transport.d.ts.map +1 -1
- package/dist/src/http-transport.js +1045 -482
- package/dist/src/http-transport.js.map +1 -1
- package/dist/src/index.d.ts +1 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -170
- package/dist/src/index.js.map +1 -1
- package/dist/src/interceptors.d.ts +1 -0
- package/dist/src/interceptors.d.ts.map +1 -1
- package/dist/src/interceptors.js +73 -63
- package/dist/src/interceptors.js.map +1 -1
- package/dist/src/lib.d.ts +1 -7
- package/dist/src/lib.d.ts.map +1 -1
- package/dist/src/lib.js +1 -6
- package/dist/src/lib.js.map +1 -1
- package/dist/src/logger.d.ts +5 -0
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/logger.js +9 -1
- package/dist/src/logger.js.map +1 -1
- package/dist/src/mcp/mcp-server-manager.d.ts +20 -0
- package/dist/src/mcp/mcp-server-manager.d.ts.map +1 -0
- package/dist/src/mcp/mcp-server-manager.js +38 -0
- package/dist/src/mcp/mcp-server-manager.js.map +1 -0
- package/dist/src/mcp/mcp-server.d.ts +205 -0
- package/dist/src/mcp/mcp-server.d.ts.map +1 -0
- package/dist/src/mcp/mcp-server.js +1473 -0
- package/dist/src/mcp/mcp-server.js.map +1 -0
- package/dist/src/mcp-server-manager.d.ts +20 -0
- package/dist/src/mcp-server-manager.d.ts.map +1 -0
- package/dist/src/mcp-server-manager.js +38 -0
- package/dist/src/mcp-server-manager.js.map +1 -0
- package/dist/src/mcp-server.d.ts +28 -0
- package/dist/src/mcp-server.d.ts.map +1 -1
- package/dist/src/mcp-server.js +406 -109
- package/dist/src/mcp-server.js.map +1 -1
- package/dist/src/metrics.d.ts +11 -0
- package/dist/src/metrics.d.ts.map +1 -1
- package/dist/src/metrics.js +61 -0
- package/dist/src/metrics.js.map +1 -1
- package/dist/src/oauth-provider.d.ts +5 -0
- package/dist/src/oauth-provider.d.ts.map +1 -1
- package/dist/src/oauth-provider.js +29 -1
- package/dist/src/oauth-provider.js.map +1 -1
- package/dist/src/openapi/openapi-parser.d.ts +70 -0
- package/dist/src/openapi/openapi-parser.d.ts.map +1 -0
- package/dist/src/openapi/openapi-parser.js +458 -0
- package/dist/src/openapi/openapi-parser.js.map +1 -0
- package/dist/src/profile/profile-loader.d.ts +78 -0
- package/dist/src/profile/profile-loader.d.ts.map +1 -0
- package/dist/src/profile/profile-loader.js +490 -0
- package/dist/src/profile/profile-loader.js.map +1 -0
- package/dist/src/profile/profile-registry.d.ts +19 -0
- package/dist/src/profile/profile-registry.d.ts.map +1 -0
- package/dist/src/profile/profile-registry.js +43 -0
- package/dist/src/profile/profile-registry.js.map +1 -0
- package/dist/src/profile/profile-resolver.d.ts +41 -0
- package/dist/src/profile/profile-resolver.d.ts.map +1 -0
- package/dist/src/profile/profile-resolver.js +324 -0
- package/dist/src/profile/profile-resolver.js.map +1 -0
- package/dist/src/profile/startup-profile.d.ts +17 -0
- package/dist/src/profile/startup-profile.d.ts.map +1 -0
- package/dist/src/profile/startup-profile.js +30 -0
- package/dist/src/profile/startup-profile.js.map +1 -0
- package/dist/src/profile/startup-validation.d.ts +11 -0
- package/dist/src/profile/startup-validation.d.ts.map +1 -0
- package/dist/src/profile/startup-validation.js +21 -0
- package/dist/src/profile/startup-validation.js.map +1 -0
- package/dist/src/profile-loader.d.ts +1 -0
- package/dist/src/profile-loader.d.ts.map +1 -1
- package/dist/src/profile-loader.js +14 -3
- package/dist/src/profile-loader.js.map +1 -1
- package/dist/src/profile-registry.d.ts +18 -0
- package/dist/src/profile-registry.d.ts.map +1 -0
- package/dist/src/profile-registry.js +26 -0
- package/dist/src/profile-registry.js.map +1 -0
- package/dist/src/profile-resolver.d.ts +19 -0
- package/dist/src/profile-resolver.d.ts.map +1 -0
- package/dist/src/profile-resolver.js +167 -0
- package/dist/src/profile-resolver.js.map +1 -0
- package/dist/src/proxy-executor.d.ts.map +1 -1
- package/dist/src/proxy-executor.js +7 -0
- package/dist/src/proxy-executor.js.map +1 -1
- package/dist/src/startup-profile.d.ts +17 -0
- package/dist/src/startup-profile.d.ts.map +1 -0
- package/dist/src/startup-profile.js +30 -0
- package/dist/src/startup-profile.js.map +1 -0
- package/dist/src/startup-validation.d.ts +11 -0
- package/dist/src/startup-validation.d.ts.map +1 -0
- package/dist/src/startup-validation.js +21 -0
- package/dist/src/startup-validation.js.map +1 -0
- package/dist/src/testing/dynamic-mock-server.d.ts +24 -0
- package/dist/src/testing/dynamic-mock-server.d.ts.map +1 -0
- package/dist/src/testing/dynamic-mock-server.js +138 -0
- package/dist/src/testing/dynamic-mock-server.js.map +1 -0
- package/dist/src/testing/request-assertions.d.ts +5 -0
- package/dist/src/testing/request-assertions.d.ts.map +1 -0
- package/dist/src/testing/request-assertions.js +165 -0
- package/dist/src/testing/request-assertions.js.map +1 -0
- package/dist/src/testing/template-utils.d.ts +10 -0
- package/dist/src/testing/template-utils.d.ts.map +1 -0
- package/dist/src/testing/template-utils.js +72 -0
- package/dist/src/testing/template-utils.js.map +1 -0
- package/dist/src/testing/test-http-utils.d.ts +1 -1
- package/dist/src/testing/test-http-utils.d.ts.map +1 -1
- package/dist/src/testing/test-http-utils.js +1 -1
- package/dist/src/testing/test-http-utils.js.map +1 -1
- package/dist/src/testing/test-loader.d.ts +6 -0
- package/dist/src/testing/test-loader.d.ts.map +1 -0
- package/dist/src/testing/test-loader.js +212 -0
- package/dist/src/testing/test-loader.js.map +1 -0
- package/dist/src/testing/test-schema.d.ts +1270 -0
- package/dist/src/testing/test-schema.d.ts.map +1 -0
- package/dist/src/testing/test-schema.js +76 -0
- package/dist/src/testing/test-schema.js.map +1 -0
- package/dist/src/tool-filter/compat.d.ts +49 -0
- package/dist/src/tool-filter/compat.d.ts.map +1 -0
- package/dist/src/tool-filter/compat.js +72 -0
- package/dist/src/tool-filter/compat.js.map +1 -0
- package/dist/src/tool-filter/config/env-config-parser.d.ts +38 -0
- package/dist/src/tool-filter/config/env-config-parser.d.ts.map +1 -0
- package/dist/src/tool-filter/config/env-config-parser.js +103 -0
- package/dist/src/tool-filter/config/env-config-parser.js.map +1 -0
- package/dist/src/tool-filter/config/header-config-parser.d.ts +37 -0
- package/dist/src/tool-filter/config/header-config-parser.d.ts.map +1 -0
- package/dist/src/tool-filter/config/header-config-parser.js +118 -0
- package/dist/src/tool-filter/config/header-config-parser.js.map +1 -0
- package/dist/src/tool-filter/errors.d.ts +18 -0
- package/dist/src/tool-filter/errors.d.ts.map +1 -0
- package/dist/src/tool-filter/errors.js +21 -0
- package/dist/src/tool-filter/errors.js.map +1 -0
- package/dist/src/tool-filter/filter/filter-engine.d.ts +45 -0
- package/dist/src/tool-filter/filter/filter-engine.d.ts.map +1 -0
- package/dist/src/tool-filter/filter/filter-engine.js +94 -0
- package/dist/src/tool-filter/filter/filter-engine.js.map +1 -0
- package/dist/src/tool-filter/filter/filter-rules.d.ts +44 -0
- package/dist/src/tool-filter/filter/filter-rules.d.ts.map +1 -0
- package/dist/src/tool-filter/filter/filter-rules.js +72 -0
- package/dist/src/tool-filter/filter/filter-rules.js.map +1 -0
- package/dist/src/tool-filter/filter/global-tool-filter.d.ts +40 -0
- package/dist/src/tool-filter/filter/global-tool-filter.d.ts.map +1 -0
- package/dist/src/tool-filter/filter/global-tool-filter.js +92 -0
- package/dist/src/tool-filter/filter/global-tool-filter.js.map +1 -0
- package/dist/src/tool-filter/filter/session-tool-filter.d.ts +29 -0
- package/dist/src/tool-filter/filter/session-tool-filter.d.ts.map +1 -0
- package/dist/src/tool-filter/filter/session-tool-filter.js +69 -0
- package/dist/src/tool-filter/filter/session-tool-filter.js.map +1 -0
- package/dist/src/tool-filter/index.d.ts +25 -0
- package/dist/src/tool-filter/index.d.ts.map +1 -0
- package/dist/src/tool-filter/index.js +30 -0
- package/dist/src/tool-filter/index.js.map +1 -0
- package/dist/src/tool-filter/integration/tool-filter-service.d.ts +44 -0
- package/dist/src/tool-filter/integration/tool-filter-service.d.ts.map +1 -0
- package/dist/src/tool-filter/integration/tool-filter-service.js +68 -0
- package/dist/src/tool-filter/integration/tool-filter-service.js.map +1 -0
- package/dist/src/tool-filter/operation/operation-classifier.d.ts +20 -0
- package/dist/src/tool-filter/operation/operation-classifier.d.ts.map +1 -0
- package/dist/src/tool-filter/operation/operation-classifier.js +26 -0
- package/dist/src/tool-filter/operation/operation-classifier.js.map +1 -0
- package/dist/src/tool-filter/operation/operation-detector.d.ts +30 -0
- package/dist/src/tool-filter/operation/operation-detector.d.ts.map +1 -0
- package/dist/src/tool-filter/operation/operation-detector.js +96 -0
- package/dist/src/tool-filter/operation/operation-detector.js.map +1 -0
- package/dist/src/tool-filter/operation/operation-resolver.d.ts +22 -0
- package/dist/src/tool-filter/operation/operation-resolver.d.ts.map +1 -0
- package/dist/src/tool-filter/operation/operation-resolver.js +32 -0
- package/dist/src/tool-filter/operation/operation-resolver.js.map +1 -0
- package/dist/src/tool-filter/regex/regex-compiler.d.ts +22 -0
- package/dist/src/tool-filter/regex/regex-compiler.d.ts.map +1 -0
- package/dist/src/tool-filter/regex/regex-compiler.js +56 -0
- package/dist/src/tool-filter/regex/regex-compiler.js.map +1 -0
- package/dist/src/tool-filter/regex/regex-validator.d.ts +24 -0
- package/dist/src/tool-filter/regex/regex-validator.d.ts.map +1 -0
- package/dist/src/tool-filter/regex/regex-validator.js +58 -0
- package/dist/src/tool-filter/regex/regex-validator.js.map +1 -0
- package/dist/src/tool-filter/types.d.ts +92 -0
- package/dist/src/tool-filter/types.d.ts.map +1 -0
- package/dist/src/tool-filter/types.js +5 -0
- package/dist/src/tool-filter/types.js.map +1 -0
- package/dist/src/tool-filter/utils.d.ts +11 -0
- package/dist/src/tool-filter/utils.d.ts.map +1 -0
- package/dist/src/tool-filter/utils.js +13 -0
- package/dist/src/tool-filter/utils.js.map +1 -0
- package/dist/src/tool-filter.d.ts +65 -0
- package/dist/src/tool-filter.d.ts.map +1 -0
- package/dist/src/tool-filter.js +471 -0
- package/dist/src/tool-filter.js.map +1 -0
- package/dist/src/tool-generator.d.ts +1 -0
- package/dist/src/tool-generator.d.ts.map +1 -1
- package/dist/src/tool-generator.js +15 -6
- package/dist/src/tool-generator.js.map +1 -1
- package/dist/src/tooling/composite-executor.d.ts +77 -0
- package/dist/src/tooling/composite-executor.d.ts.map +1 -0
- package/dist/src/tooling/composite-executor.js +198 -0
- package/dist/src/tooling/composite-executor.js.map +1 -0
- package/dist/src/tooling/dag-executor.d.ts +49 -0
- package/dist/src/tooling/dag-executor.d.ts.map +1 -0
- package/dist/src/tooling/dag-executor.js +138 -0
- package/dist/src/tooling/dag-executor.js.map +1 -0
- package/dist/src/tooling/proxy-executor.d.ts +86 -0
- package/dist/src/tooling/proxy-executor.d.ts.map +1 -0
- package/dist/src/tooling/proxy-executor.js +501 -0
- package/dist/src/tooling/proxy-executor.js.map +1 -0
- package/dist/src/tooling/tool-generator.d.ts +67 -0
- package/dist/src/tooling/tool-generator.d.ts.map +1 -0
- package/dist/src/tooling/tool-generator.js +222 -0
- package/dist/src/tooling/tool-generator.js.map +1 -0
- package/dist/src/transport/http-client-factory.d.ts +65 -0
- package/dist/src/transport/http-client-factory.d.ts.map +1 -0
- package/dist/src/transport/http-client-factory.js +143 -0
- package/dist/src/transport/http-client-factory.js.map +1 -0
- package/dist/src/transport/http-transport-config.d.ts +6 -0
- package/dist/src/transport/http-transport-config.d.ts.map +1 -0
- package/dist/src/transport/http-transport-config.js +63 -0
- package/dist/src/transport/http-transport-config.js.map +1 -0
- package/dist/src/transport/http-transport.d.ts +329 -0
- package/dist/src/transport/http-transport.d.ts.map +1 -0
- package/dist/src/transport/http-transport.js +2584 -0
- package/dist/src/transport/http-transport.js.map +1 -0
- package/dist/src/transport/interceptors.d.ts +119 -0
- package/dist/src/transport/interceptors.d.ts.map +1 -0
- package/dist/src/transport/interceptors.js +413 -0
- package/dist/src/transport/interceptors.js.map +1 -0
- package/dist/src/transport/profile-index.d.ts +84 -0
- package/dist/src/transport/profile-index.d.ts.map +1 -0
- package/dist/src/transport/profile-index.js +405 -0
- package/dist/src/transport/profile-index.js.map +1 -0
- package/dist/src/types/http-transport.d.ts +26 -0
- package/dist/src/types/http-transport.d.ts.map +1 -1
- package/dist/src/types/openapi.d.ts +3 -0
- package/dist/src/types/openapi.d.ts.map +1 -1
- package/dist/src/types/profile.d.ts +16 -1
- package/dist/src/types/profile.d.ts.map +1 -1
- package/dist/src/validation/argument-normalizer.d.ts +6 -0
- package/dist/src/validation/argument-normalizer.d.ts.map +1 -0
- package/dist/src/validation/argument-normalizer.js +70 -0
- package/dist/src/validation/argument-normalizer.js.map +1 -0
- package/dist/src/validation/jsonrpc-validator.d.ts +27 -0
- package/dist/src/validation/jsonrpc-validator.d.ts.map +1 -0
- package/dist/src/validation/jsonrpc-validator.js +58 -0
- package/dist/src/validation/jsonrpc-validator.js.map +1 -0
- package/dist/src/validation/schema-validator.d.ts +30 -0
- package/dist/src/validation/schema-validator.d.ts.map +1 -0
- package/dist/src/validation/schema-validator.js +128 -0
- package/dist/src/validation/schema-validator.js.map +1 -0
- package/dist/src/validation/validation-utils.d.ts +49 -0
- package/dist/src/validation/validation-utils.d.ts.map +1 -0
- package/dist/src/validation/validation-utils.js +139 -0
- package/dist/src/validation/validation-utils.js.map +1 -0
- package/html/profile-index.html +386 -0
- package/package.json +10 -3
- package/profile-schema.json +77 -3
- package/profiles/gitlab/developer-profile-oauth.json +1520 -0
- package/profiles/gitlab/developer-profile-oauth.test.json +3432 -0
- package/profiles/gitlab/developer-profile.json +1508 -0
- package/profiles/gitlab/developer-profile.test.json +3432 -0
- package/profiles/gitlab/openapi.yaml +6891 -0
- package/profiles/n8n/openapi.yaml +2441 -0
- package/profiles/n8n/profile-optimized.json +965 -0
- package/profiles/n8n/profile-optimized.test.json +1078 -0
- package/profiles/n8n/profile.json +1033 -0
- package/profiles/n8n/profile.test.json +983 -0
- package/profiles/n8n-nodes/openapi.yaml +24 -0
- package/profiles/n8n-nodes/profile-nodes.json +44 -0
- package/profiles/n8n-nodes/profile-nodes.test.json +91 -0
- package/profiles/semgrep/openapi.yaml +4706 -0
- package/profiles/semgrep/profile.json +692 -0
- package/profiles/semgrep/profile.test.json +471 -0
- package/profiles/youtrack/openapi.json +16976 -0
- package/profiles/youtrack/profile.json +608 -0
- package/profiles/youtrack/profile.test.json +1926 -0
- package/dist/src/testing/fixtures.d.ts +0 -684
- package/dist/src/testing/fixtures.d.ts.map +0 -1
- package/dist/src/testing/fixtures.js +0 -528
- package/dist/src/testing/fixtures.js.map +0 -1
- package/dist/src/testing/mock-gitlab-server.d.ts +0 -43
- package/dist/src/testing/mock-gitlab-server.d.ts.map +0 -1
- package/dist/src/testing/mock-gitlab-server.js +0 -1026
- package/dist/src/testing/mock-gitlab-server.js.map +0 -1
- package/dist/src/testing/mock-semgrep-server.d.ts +0 -32
- package/dist/src/testing/mock-semgrep-server.d.ts.map +0 -1
- package/dist/src/testing/mock-semgrep-server.js +0 -213
- package/dist/src/testing/mock-semgrep-server.js.map +0 -1
- package/dist/src/testing/mock-youtrack-server.d.ts +0 -11
- package/dist/src/testing/mock-youtrack-server.d.ts.map +0 -1
- package/dist/src/testing/mock-youtrack-server.js +0 -152
- package/dist/src/testing/mock-youtrack-server.js.map +0 -1
package/dist/src/mcp-server.js
CHANGED
|
@@ -10,10 +10,12 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextpro
|
|
|
10
10
|
import { OpenAPIParser } from './openapi-parser.js';
|
|
11
11
|
import { ProfileLoader } from './profile-loader.js';
|
|
12
12
|
import { ToolGenerator } from './tool-generator.js';
|
|
13
|
+
import { normalizeArguments } from './argument-normalizer.js';
|
|
13
14
|
import { CompositeExecutor } from './composite-executor.js';
|
|
14
15
|
import { ProxyDownloadExecutor } from './proxy-executor.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
16
|
+
import { enforceFiltering, parseFilteringHeader } from './filtering.js';
|
|
17
|
+
import { ConfigurationError, OperationNotFoundError, ResourceNotFoundError, ValidationError, AuthenticationError, AuthorizationError, RateLimitError, NetworkError, generateCorrelationId } from './errors.js';
|
|
18
|
+
import { OAUTH_RATE_LIMIT } from './constants.js';
|
|
17
19
|
import { HttpClientFactory } from './http-client-factory.js';
|
|
18
20
|
import { SchemaValidator } from './schema-validator.js';
|
|
19
21
|
import { ConsoleLogger, JsonLogger } from './logger.js';
|
|
@@ -21,7 +23,25 @@ import { isInitializeRequest, isToolCallRequest } from './jsonrpc-validator.js';
|
|
|
21
23
|
import { generateNameWarnings } from './naming-warnings.js';
|
|
22
24
|
import { NamingStrategy } from './naming.js';
|
|
23
25
|
import { isSafePropertyName } from './validation-utils.js';
|
|
26
|
+
import { ToolFilterService, EnvConfigParser, HeaderConfigParser, RegexCompiler, RegexValidator, OperationClassifier, OpenAPIOperationResolver, OperationDetector, applySessionToolFilter, } from './tool-filter/index.js';
|
|
27
|
+
import { buildHttpTransportBaseConfig } from './http-transport-config.js';
|
|
24
28
|
export class MCPServer {
|
|
29
|
+
/**
|
|
30
|
+
* Execute a tools/call request via the JSON-RPC handler.
|
|
31
|
+
* Intended for internal use and tests to avoid accessing private methods.
|
|
32
|
+
*/
|
|
33
|
+
async callToolRpc(name, args, sessionId, requestId = 1) {
|
|
34
|
+
const message = {
|
|
35
|
+
jsonrpc: '2.0',
|
|
36
|
+
id: requestId,
|
|
37
|
+
method: 'tools/call',
|
|
38
|
+
params: {
|
|
39
|
+
name,
|
|
40
|
+
arguments: args,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
return this.handleToolCall(message, sessionId);
|
|
44
|
+
}
|
|
25
45
|
/**
|
|
26
46
|
* Filter response payload to include only specified fields.
|
|
27
47
|
*
|
|
@@ -177,6 +197,9 @@ export class MCPServer {
|
|
|
177
197
|
if (error instanceof OperationNotFoundError) {
|
|
178
198
|
return `Operation not found: ${error.message} (correlation ID: ${correlationId})`;
|
|
179
199
|
}
|
|
200
|
+
if (error instanceof ResourceNotFoundError) {
|
|
201
|
+
return `${error.message} (correlation ID: ${correlationId})`;
|
|
202
|
+
}
|
|
180
203
|
// Configuration errors - safe to show (helps admin fix setup)
|
|
181
204
|
if (error instanceof ConfigurationError) {
|
|
182
205
|
return `Configuration error: ${error.message} (correlation ID: ${correlationId})`;
|
|
@@ -223,6 +246,7 @@ export class MCPServer {
|
|
|
223
246
|
// Check if we should warn about long names
|
|
224
247
|
this.checkToolNameLengths();
|
|
225
248
|
}
|
|
249
|
+
this.applyGlobalToolFiltering();
|
|
226
250
|
// Re-create logger with auth config for token redaction
|
|
227
251
|
const authConfigs = this.getAuthConfigs();
|
|
228
252
|
if (authConfigs.length > 0) {
|
|
@@ -346,6 +370,59 @@ export class MCPServer {
|
|
|
346
370
|
const oauthConfig = configs.find(c => c.type === 'oauth');
|
|
347
371
|
return oauthConfig?.oauth_config;
|
|
348
372
|
}
|
|
373
|
+
buildOAuthConfigWithAllowedRedirectHosts(oauthConfig) {
|
|
374
|
+
if (!oauthConfig) {
|
|
375
|
+
return undefined;
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
...oauthConfig,
|
|
379
|
+
allowed_redirect_hosts: oauthConfig.allowed_redirect_hosts
|
|
380
|
+
|| (process.env.MCP4_ALLOWED_ORIGINS
|
|
381
|
+
? this.extractHostsFromOrigins(process.env.MCP4_ALLOWED_ORIGINS)
|
|
382
|
+
: undefined),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
getProfileIdValue() {
|
|
386
|
+
if (!this.profile) {
|
|
387
|
+
throw new ConfigurationError('Profile not initialized. Call initialize() first.');
|
|
388
|
+
}
|
|
389
|
+
const profileId = this.profile.profile_id?.trim() || this.profile.profile_name;
|
|
390
|
+
if (!profileId) {
|
|
391
|
+
throw new ConfigurationError('Profile is missing profile_id and profile_name.');
|
|
392
|
+
}
|
|
393
|
+
return profileId;
|
|
394
|
+
}
|
|
395
|
+
getOAuthRateLimitConfig() {
|
|
396
|
+
const authConfigs = this.getAuthConfigs();
|
|
397
|
+
const oauthAuthConfig = authConfigs.find(c => c.type === 'oauth');
|
|
398
|
+
const oauthRateLimit = oauthAuthConfig?.oauth_rate_limit;
|
|
399
|
+
const max = oauthRateLimit?.max_requests
|
|
400
|
+
|| parseInt(process.env.MCP4_OAUTH_RATE_LIMIT_MAX || String(OAUTH_RATE_LIMIT.MAX_REQUESTS), 10);
|
|
401
|
+
const windowMs = oauthRateLimit?.window_ms
|
|
402
|
+
|| parseInt(process.env.MCP4_OAUTH_RATE_LIMIT_WINDOW_MS || String(OAUTH_RATE_LIMIT.WINDOW_MS), 10);
|
|
403
|
+
return { max, windowMs };
|
|
404
|
+
}
|
|
405
|
+
getHttpProfileContext() {
|
|
406
|
+
if (!this.profile) {
|
|
407
|
+
throw new ConfigurationError('Profile not initialized. Call initialize() first.');
|
|
408
|
+
}
|
|
409
|
+
const authConfigs = this.getAuthConfigs();
|
|
410
|
+
const baseUrl = this.getBaseUrl();
|
|
411
|
+
const oauthConfig = this.buildOAuthConfigWithAllowedRedirectHosts(this.getOAuthConfig());
|
|
412
|
+
const resourceMetadata = this.parser.getResourceMetadata();
|
|
413
|
+
const oauthRateLimit = this.getOAuthRateLimitConfig();
|
|
414
|
+
return {
|
|
415
|
+
profileId: this.getProfileIdValue(),
|
|
416
|
+
oauthConfig,
|
|
417
|
+
authConfigs,
|
|
418
|
+
baseUrl,
|
|
419
|
+
rateLimitOAuthMax: oauthRateLimit.max,
|
|
420
|
+
rateLimitOAuthWindowMs: oauthRateLimit.windowMs,
|
|
421
|
+
resourceName: this.profile.resource_name || resourceMetadata.name || 'MCP Server',
|
|
422
|
+
resourceDocumentation: this.profile.resource_documentation || resourceMetadata.documentation,
|
|
423
|
+
parser: this.parser,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
349
426
|
/**
|
|
350
427
|
* Extract hostnames from origin patterns for OAuth redirect validation
|
|
351
428
|
* e.g., "http://localhost:*,https://app.example.com" -> ["localhost", "app.example.com"]
|
|
@@ -385,7 +462,7 @@ export class MCPServer {
|
|
|
385
462
|
/**
|
|
386
463
|
* Get or create HTTP client for session
|
|
387
464
|
*/
|
|
388
|
-
async getHttpClientForSession(sessionId) {
|
|
465
|
+
async getHttpClientForSession(sessionId, profileId) {
|
|
389
466
|
if (!sessionId) {
|
|
390
467
|
// Fallback to global client for stdio transport
|
|
391
468
|
if (!this.httpClientFactory.hasGlobalClient()) {
|
|
@@ -408,7 +485,7 @@ export class MCPServer {
|
|
|
408
485
|
throw new ConfigurationError('Profile not initialized. Call initialize() first.');
|
|
409
486
|
}
|
|
410
487
|
// Get auth token from session (ensures token is valid/refreshed)
|
|
411
|
-
const authToken = await this.getAuthTokenFromSession(sessionId);
|
|
488
|
+
const authToken = await this.getAuthTokenFromSession(sessionId, profileId);
|
|
412
489
|
// Create or get session client using factory
|
|
413
490
|
return this.httpClientFactory.getOrCreateSessionClient(sessionId, {
|
|
414
491
|
profile: this.profile,
|
|
@@ -420,7 +497,7 @@ export class MCPServer {
|
|
|
420
497
|
* Get auth token from HTTP transport session
|
|
421
498
|
* Ensures token is valid (refreshes if expired) before returning
|
|
422
499
|
*/
|
|
423
|
-
async getAuthTokenFromSession(sessionId) {
|
|
500
|
+
async getAuthTokenFromSession(sessionId, profileId) {
|
|
424
501
|
// Early return if sessionId is missing/empty
|
|
425
502
|
// Prevents misleading warn logs with empty sessionId
|
|
426
503
|
if (!sessionId) {
|
|
@@ -430,23 +507,24 @@ export class MCPServer {
|
|
|
430
507
|
return undefined;
|
|
431
508
|
}
|
|
432
509
|
// Ensure token is valid (refresh if expired)
|
|
433
|
-
const
|
|
510
|
+
const effectiveProfileId = profileId || this.getProfileIdValue();
|
|
511
|
+
const isValid = await this.httpTransport.ensureValidSessionToken(effectiveProfileId, sessionId);
|
|
434
512
|
if (!isValid) {
|
|
435
|
-
this.logger.warn('Session token validation/refresh failed', { sessionId });
|
|
513
|
+
this.logger.warn('Session token validation/refresh failed', { profileId: effectiveProfileId, sessionId });
|
|
436
514
|
// Still return token if available - let the API call fail with proper error
|
|
437
515
|
}
|
|
438
516
|
// Use public API instead of type casting
|
|
439
|
-
return this.httpTransport.getSessionToken(sessionId);
|
|
517
|
+
return this.httpTransport.getSessionToken(effectiveProfileId, sessionId);
|
|
440
518
|
}
|
|
441
519
|
/**
|
|
442
520
|
* Cleanup HTTP client for destroyed session
|
|
443
521
|
*
|
|
444
522
|
* Why: Prevent memory leak - sessions expire but cached clients stay forever
|
|
445
523
|
*/
|
|
446
|
-
cleanupSessionClient(sessionId) {
|
|
524
|
+
cleanupSessionClient(profileId, sessionId) {
|
|
447
525
|
const removed = this.httpClientFactory.cleanupSessionClient(sessionId);
|
|
448
526
|
if (removed) {
|
|
449
|
-
this.logger.info('Cleaned up session HTTP client', { sessionId });
|
|
527
|
+
this.logger.info('Cleaned up session HTTP client', { profileId, sessionId });
|
|
450
528
|
}
|
|
451
529
|
}
|
|
452
530
|
/**
|
|
@@ -530,15 +608,16 @@ export class MCPServer {
|
|
|
530
608
|
* Why separate: Simple tools map directly to single OpenAPI operation.
|
|
531
609
|
* No result aggregation needed.
|
|
532
610
|
*/
|
|
533
|
-
async executeSimpleTool(toolDef, args, sessionId) {
|
|
611
|
+
async executeSimpleTool(toolDef, args, sessionId, profileId) {
|
|
612
|
+
const normalizedArgs = normalizeArguments(toolDef, args);
|
|
534
613
|
this.logger.debug('Executing simple tool', {
|
|
535
614
|
toolName: toolDef.name,
|
|
536
|
-
action:
|
|
537
|
-
resourceType:
|
|
615
|
+
action: normalizedArgs['action'],
|
|
616
|
+
resourceType: normalizedArgs['resource_type'],
|
|
538
617
|
sessionId
|
|
539
618
|
});
|
|
540
619
|
// Get operation definition (can be string or ProxyDownloadOperation)
|
|
541
|
-
const operationDef = this.toolGenerator.getOperationDefinition(toolDef,
|
|
620
|
+
const operationDef = this.toolGenerator.getOperationDefinition(toolDef, normalizedArgs);
|
|
542
621
|
if (!operationDef) {
|
|
543
622
|
throw new ValidationError(`Could not map tool action to operation`, {
|
|
544
623
|
toolName: toolDef.name,
|
|
@@ -549,7 +628,7 @@ export class MCPServer {
|
|
|
549
628
|
}
|
|
550
629
|
// Check if this is a proxy download operation
|
|
551
630
|
if (typeof operationDef === 'object' && operationDef.type === 'proxy_download') {
|
|
552
|
-
return this.executeProxyDownload(operationDef,
|
|
631
|
+
return this.executeProxyDownload(operationDef, normalizedArgs, sessionId, profileId);
|
|
553
632
|
}
|
|
554
633
|
// Regular string operation
|
|
555
634
|
const operationId = operationDef;
|
|
@@ -558,9 +637,9 @@ export class MCPServer {
|
|
|
558
637
|
throw new OperationNotFoundError(operationId);
|
|
559
638
|
}
|
|
560
639
|
// Build request
|
|
561
|
-
const path = this.resolvePath(operation.path,
|
|
562
|
-
const queryParams = this.extractQueryParams(operation,
|
|
563
|
-
const body = this.extractBody(operation,
|
|
640
|
+
const path = this.resolvePath(operation.path, normalizedArgs);
|
|
641
|
+
const queryParams = this.extractQueryParams(operation, normalizedArgs);
|
|
642
|
+
const body = this.extractBody(operation, normalizedArgs, toolDef);
|
|
564
643
|
this.logger.debug('Executing HTTP request', {
|
|
565
644
|
operationId,
|
|
566
645
|
method: operation.method,
|
|
@@ -579,9 +658,9 @@ export class MCPServer {
|
|
|
579
658
|
}
|
|
580
659
|
}
|
|
581
660
|
// Execute with session-specific client
|
|
582
|
-
const httpClient = await this.getHttpClientForSession(sessionId);
|
|
661
|
+
const httpClient = await this.getHttpClientForSession(sessionId, profileId);
|
|
583
662
|
// Set fields parameter if response_fields are configured for this action AND enabled
|
|
584
|
-
const action =
|
|
663
|
+
const action = normalizedArgs.action;
|
|
585
664
|
if (toolDef.send_response_fields_as_param && toolDef.response_fields && action && toolDef.response_fields[action]) {
|
|
586
665
|
const fields = toolDef.response_fields[action];
|
|
587
666
|
queryParams.fields = fields.join(',');
|
|
@@ -594,7 +673,7 @@ export class MCPServer {
|
|
|
594
673
|
// Apply response field filtering if configured
|
|
595
674
|
let result = response.body;
|
|
596
675
|
if (toolDef.response_fields) {
|
|
597
|
-
const action =
|
|
676
|
+
const action = normalizedArgs.action;
|
|
598
677
|
if (action && toolDef.response_fields[action]) {
|
|
599
678
|
const fields = toolDef.response_fields[action];
|
|
600
679
|
result = this.filterFields(result, fields);
|
|
@@ -608,7 +687,7 @@ export class MCPServer {
|
|
|
608
687
|
* Why: Some APIs return authenticated URLs that LLMs cannot fetch directly.
|
|
609
688
|
* This proxies the download through the MCP server.
|
|
610
689
|
*/
|
|
611
|
-
async executeProxyDownload(operation, args, sessionId) {
|
|
690
|
+
async executeProxyDownload(operation, args, sessionId, profileId) {
|
|
612
691
|
this.logger.debug('Executing proxy download', {
|
|
613
692
|
metadataEndpoint: operation.metadata_endpoint,
|
|
614
693
|
urlField: operation.url_field,
|
|
@@ -634,7 +713,7 @@ export class MCPServer {
|
|
|
634
713
|
};
|
|
635
714
|
}
|
|
636
715
|
// Get auth credentials for download
|
|
637
|
-
const httpClient = await this.getHttpClientForSession(sessionId);
|
|
716
|
+
const httpClient = await this.getHttpClientForSession(sessionId, profileId);
|
|
638
717
|
const authCredentials = httpClient.getAuthCredentials();
|
|
639
718
|
// Execute proxy download
|
|
640
719
|
const proxyExecutor = new ProxyDownloadExecutor(httpClient, this.logger);
|
|
@@ -781,75 +860,25 @@ export class MCPServer {
|
|
|
781
860
|
*/
|
|
782
861
|
async runHttp(host, port) {
|
|
783
862
|
const { HttpTransport } = await import('./http-transport.js');
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
if (oauthConfig) {
|
|
863
|
+
const profileContext = this.getHttpProfileContext();
|
|
864
|
+
if (profileContext.oauthConfig) {
|
|
787
865
|
this.logger.info('OAuth authentication enabled for HTTP transport');
|
|
788
866
|
}
|
|
789
|
-
|
|
790
|
-
const authConfigs = this.getAuthConfigs();
|
|
791
|
-
const baseUrl = this.getBaseUrl();
|
|
792
|
-
// Extract OAuth rate limit from profile (if configured)
|
|
793
|
-
const oauthAuthConfig = authConfigs.find(c => c.type === 'oauth');
|
|
794
|
-
const oauthRateLimit = oauthAuthConfig?.oauth_rate_limit;
|
|
795
|
-
// Extract resource metadata from OpenAPI spec or profile
|
|
796
|
-
const resourceMetadata = this.parser.getResourceMetadata();
|
|
867
|
+
const baseConfig = buildHttpTransportBaseConfig(host, port);
|
|
797
868
|
const config = {
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
heartbeatEnabled: process.env.MCP4_HEARTBEAT_ENABLED === 'true',
|
|
802
|
-
heartbeatIntervalMs: parseInt(process.env.MCP4_HEARTBEAT_INTERVAL_MS || String(TIMEOUTS.HEARTBEAT_INTERVAL_MS), 10),
|
|
803
|
-
metricsEnabled: process.env.MCP4_METRICS_ENABLED === 'true',
|
|
804
|
-
metricsPath: process.env.MCP4_METRICS_PATH || '/metrics',
|
|
805
|
-
allowedOrigins: process.env.MCP4_ALLOWED_ORIGINS
|
|
806
|
-
? process.env.MCP4_ALLOWED_ORIGINS.split(',').map(o => o.trim())
|
|
807
|
-
: undefined,
|
|
808
|
-
rateLimitEnabled: process.env.MCP4_HTTP_RATE_LIMIT_ENABLED !== 'false', // default: true
|
|
809
|
-
rateLimitWindowMs: parseInt(process.env.MCP4_HTTP_RATE_LIMIT_WINDOW_MS || String(TIMEOUTS.RATE_LIMIT_WINDOW_MS), 10),
|
|
810
|
-
rateLimitMaxRequests: parseInt(process.env.MCP4_HTTP_RATE_LIMIT_MAX_REQUESTS || '100', 10),
|
|
811
|
-
rateLimitMetricsMax: parseInt(process.env.MCP4_HTTP_RATE_LIMIT_METRICS_MAX || '10', 10),
|
|
869
|
+
...baseConfig,
|
|
870
|
+
profileRoutingEnabled: false,
|
|
871
|
+
defaultProfileId: profileContext.profileId,
|
|
812
872
|
// OAuth rate limiting (priority: profile > env vars > defaults)
|
|
813
|
-
rateLimitOAuthMax:
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
...oauthConfig,
|
|
823
|
-
allowed_redirect_hosts: oauthConfig.allowed_redirect_hosts
|
|
824
|
-
|| (process.env.MCP4_ALLOWED_ORIGINS
|
|
825
|
-
? this.extractHostsFromOrigins(process.env.MCP4_ALLOWED_ORIGINS)
|
|
826
|
-
: undefined),
|
|
827
|
-
} : undefined,
|
|
828
|
-
baseUrl, // Pass base URL for token validation
|
|
829
|
-
authConfigs, // Pass auth configs for token validation
|
|
830
|
-
// OAuth resource metadata (priority: profile > OpenAPI > fallback)
|
|
831
|
-
resourceName: this.profile?.resource_name || resourceMetadata.name || 'MCP Server',
|
|
832
|
-
resourceDocumentation: this.profile?.resource_documentation || resourceMetadata.documentation,
|
|
833
|
-
sslCertFile: process.env.MCP4_SSL_CERT_FILE,
|
|
834
|
-
sslKeyFile: process.env.MCP4_SSL_KEY_FILE,
|
|
835
|
-
oauthSessionTimeoutMs: (() => {
|
|
836
|
-
if (process.env.MCP4_OAUTH_SESSION_TIMEOUT_MS === undefined)
|
|
837
|
-
return undefined;
|
|
838
|
-
const parsed = parseInt(process.env.MCP4_OAUTH_SESSION_TIMEOUT_MS, 10);
|
|
839
|
-
if (Number.isNaN(parsed)) {
|
|
840
|
-
throw new ConfigurationError(`Invalid MCP4_OAUTH_SESSION_TIMEOUT_MS: expected integer milliseconds, got '${process.env.MCP4_OAUTH_SESSION_TIMEOUT_MS}'`);
|
|
841
|
-
}
|
|
842
|
-
return parsed;
|
|
843
|
-
})(),
|
|
844
|
-
oauthRefreshThresholdMs: (() => {
|
|
845
|
-
if (process.env.MCP4_OAUTH_REFRESH_THRESHOLD_MS === undefined)
|
|
846
|
-
return undefined;
|
|
847
|
-
const parsed = parseInt(process.env.MCP4_OAUTH_REFRESH_THRESHOLD_MS, 10);
|
|
848
|
-
if (Number.isNaN(parsed)) {
|
|
849
|
-
throw new ConfigurationError(`Invalid MCP4_OAUTH_REFRESH_THRESHOLD_MS: expected integer milliseconds, got '${process.env.MCP4_OAUTH_REFRESH_THRESHOLD_MS}'`);
|
|
850
|
-
}
|
|
851
|
-
return parsed;
|
|
852
|
-
})(),
|
|
873
|
+
rateLimitOAuthMax: profileContext.rateLimitOAuthMax,
|
|
874
|
+
rateLimitOAuthWindowMs: profileContext.rateLimitOAuthWindowMs,
|
|
875
|
+
// OAuth config already merged with allowed_redirect_hosts
|
|
876
|
+
oauthConfig: profileContext.oauthConfig,
|
|
877
|
+
baseUrl: profileContext.baseUrl,
|
|
878
|
+
authConfigs: profileContext.authConfigs,
|
|
879
|
+
resourceName: profileContext.resourceName,
|
|
880
|
+
resourceDocumentation: profileContext.resourceDocumentation,
|
|
881
|
+
parser: profileContext.parser,
|
|
853
882
|
};
|
|
854
883
|
// Warn if binding to non-localhost without explicit MCP4_ALLOWED_ORIGINS
|
|
855
884
|
const isLocalhost = host === 'localhost' || host === '127.0.0.1' || host === '::1';
|
|
@@ -858,37 +887,58 @@ export class MCPServer {
|
|
|
858
887
|
this.logger.warn('Binding to non-localhost with empty MCP4_ALLOWED_ORIGINS. Set MCP4_ALLOWED_ORIGINS or bind to localhost.');
|
|
859
888
|
}
|
|
860
889
|
this.httpTransport = new HttpTransport(config, this.logger);
|
|
890
|
+
this.recordGlobalToolFilterMetrics();
|
|
861
891
|
// Set message handler to process JSON-RPC messages
|
|
862
|
-
this.httpTransport.setMessageHandler(async (message, sessionId) => {
|
|
863
|
-
return await this.handleJsonRpcMessage(message, sessionId);
|
|
892
|
+
this.httpTransport.setMessageHandler(async (message, sessionId, profileId) => {
|
|
893
|
+
return await this.handleJsonRpcMessage(message, sessionId, profileId);
|
|
864
894
|
});
|
|
865
895
|
// Register cleanup listener for session destruction (memory leak prevention)
|
|
866
|
-
this.httpTransport.onSessionDestroyed((sessionId) => {
|
|
867
|
-
this.cleanupSessionClient(sessionId);
|
|
896
|
+
this.httpTransport.onSessionDestroyed((profileId, sessionId) => {
|
|
897
|
+
this.cleanupSessionClient(profileId, sessionId);
|
|
868
898
|
});
|
|
869
899
|
await this.httpTransport.start();
|
|
870
900
|
this.logger.info('MCP server running on HTTP', { host, port });
|
|
871
901
|
}
|
|
902
|
+
attachHttpTransport(transport) {
|
|
903
|
+
this.httpTransport = transport;
|
|
904
|
+
}
|
|
905
|
+
handleSessionDestroyed(profileId, sessionId) {
|
|
906
|
+
this.cleanupSessionClient(profileId, sessionId);
|
|
907
|
+
}
|
|
872
908
|
/**
|
|
873
909
|
* Handle JSON-RPC message from HTTP transport
|
|
874
910
|
*
|
|
875
911
|
* Why: Unified message handling for both stdio and HTTP transports
|
|
876
912
|
*/
|
|
877
|
-
async handleJsonRpcMessage(message, sessionId) {
|
|
913
|
+
async handleJsonRpcMessage(message, sessionId, profileId) {
|
|
878
914
|
// Handle initialize
|
|
879
915
|
if (isInitializeRequest(message)) {
|
|
880
|
-
return this.handleInitialize(message, sessionId);
|
|
916
|
+
return this.handleInitialize(message, sessionId, profileId);
|
|
881
917
|
}
|
|
882
918
|
// Handle tool calls
|
|
883
919
|
if (isToolCallRequest(message)) {
|
|
884
|
-
return await this.handleToolCall(message, sessionId);
|
|
920
|
+
return await this.handleToolCall(message, sessionId, profileId);
|
|
885
921
|
}
|
|
886
922
|
// Handle other JSON-RPC requests
|
|
887
923
|
// (tools/list, prompts/list, etc.)
|
|
888
|
-
return await this.handleOtherRequest(message, sessionId);
|
|
924
|
+
return await this.handleOtherRequest(message, sessionId, profileId);
|
|
889
925
|
}
|
|
890
|
-
|
|
926
|
+
async handleHttpMessage(message, sessionId, profileId) {
|
|
927
|
+
return this.handleJsonRpcMessage(message, sessionId, profileId);
|
|
928
|
+
}
|
|
929
|
+
handleInitialize(message, sessionId, profileId) {
|
|
891
930
|
const req = message;
|
|
931
|
+
const params = req.params;
|
|
932
|
+
if (!this.httpTransport && params?.filtering !== undefined) {
|
|
933
|
+
if (typeof params.filtering !== 'string') {
|
|
934
|
+
throw new ValidationError('Invalid X-Mcp4-Params header. Expected comma-separated key=value pairs.');
|
|
935
|
+
}
|
|
936
|
+
const parsed = parseFilteringHeader(params.filtering);
|
|
937
|
+
this.stdioFiltering = parsed.filtering;
|
|
938
|
+
}
|
|
939
|
+
if (this.httpTransport && sessionId) {
|
|
940
|
+
this.applySessionToolFiltering(sessionId, profileId);
|
|
941
|
+
}
|
|
892
942
|
const result = {
|
|
893
943
|
protocolVersion: '2025-03-26',
|
|
894
944
|
serverInfo: {
|
|
@@ -911,14 +961,14 @@ export class MCPServer {
|
|
|
911
961
|
result,
|
|
912
962
|
};
|
|
913
963
|
}
|
|
914
|
-
async handleToolCall(message, sessionId) {
|
|
964
|
+
async handleToolCall(message, sessionId, profileId) {
|
|
915
965
|
const req = message;
|
|
916
966
|
const params = req.params;
|
|
917
967
|
const toolName = params.name;
|
|
918
968
|
const args = params.arguments;
|
|
919
969
|
// Check OAuth authentication for tool operations
|
|
920
|
-
if (this.httpTransport && this.httpTransport.hasOAuthProvider()) {
|
|
921
|
-
const authToken = await this.getAuthTokenFromSession(sessionId || '');
|
|
970
|
+
if (this.httpTransport && this.httpTransport.hasOAuthProvider(profileId)) {
|
|
971
|
+
const authToken = await this.getAuthTokenFromSession(sessionId || '', profileId);
|
|
922
972
|
if (!authToken) {
|
|
923
973
|
// Return OAuth required error with WWW-Authenticate header
|
|
924
974
|
// This should trigger the OAuth flow in the client
|
|
@@ -930,7 +980,7 @@ export class MCPServer {
|
|
|
930
980
|
message: 'Authentication required. Please authorize via OAuth.',
|
|
931
981
|
data: {
|
|
932
982
|
oauth_required: true,
|
|
933
|
-
resource_metadata:
|
|
983
|
+
resource_metadata: this.httpTransport.getOAuthProtectedResourceUrl(profileId),
|
|
934
984
|
scope: 'api'
|
|
935
985
|
}
|
|
936
986
|
}
|
|
@@ -942,12 +992,30 @@ export class MCPServer {
|
|
|
942
992
|
// Find tool definition
|
|
943
993
|
const toolDef = this.profile?.tools.find(t => t.name === toolName);
|
|
944
994
|
if (!toolDef) {
|
|
945
|
-
throw new
|
|
995
|
+
throw new ResourceNotFoundError(toolName, 'Tool');
|
|
996
|
+
}
|
|
997
|
+
const toolFilter = this.getToolFilterForSession(sessionId, profileId);
|
|
998
|
+
if (toolFilter && !toolFilter.allowedToolNames.has(toolName)) {
|
|
999
|
+
this.recordToolFilterRejection(toolName, 'session');
|
|
1000
|
+
const reason = toolFilter.reasons.get(toolName)?.[0];
|
|
1001
|
+
const reasonSuffix = reason ? ` Blocked by: ${reason}.` : '';
|
|
1002
|
+
throw new AuthorizationError(`Tool '${toolName}' not allowed by X-Mcp4-Tools filter.${reasonSuffix}`);
|
|
1003
|
+
}
|
|
1004
|
+
const filtering = this.getFilteringForSession(sessionId, profileId);
|
|
1005
|
+
if (filtering) {
|
|
1006
|
+
const operation = this.getFilteringOperationInfo(toolDef, args);
|
|
1007
|
+
enforceFiltering({
|
|
1008
|
+
filtering,
|
|
1009
|
+
toolDef,
|
|
1010
|
+
args,
|
|
1011
|
+
parameterAliases: this.profile?.parameter_aliases,
|
|
1012
|
+
operation,
|
|
1013
|
+
});
|
|
946
1014
|
}
|
|
947
1015
|
// Execute tool (reuse existing execution logic)
|
|
948
1016
|
let result;
|
|
949
1017
|
if (toolDef.composite && toolDef.steps) {
|
|
950
|
-
const httpClient = await this.getHttpClientForSession(sessionId);
|
|
1018
|
+
const httpClient = await this.getHttpClientForSession(sessionId, profileId);
|
|
951
1019
|
const compositeResult = await this.compositeExecutor.execute(toolDef.steps, args, toolDef.partial_results || false, httpClient);
|
|
952
1020
|
result = {
|
|
953
1021
|
data: compositeResult.data,
|
|
@@ -958,7 +1026,7 @@ export class MCPServer {
|
|
|
958
1026
|
};
|
|
959
1027
|
}
|
|
960
1028
|
else {
|
|
961
|
-
result = await this.executeSimpleTool(toolDef, args, sessionId);
|
|
1029
|
+
result = await this.executeSimpleTool(toolDef, args, sessionId, profileId);
|
|
962
1030
|
}
|
|
963
1031
|
return {
|
|
964
1032
|
jsonrpc: '2.0',
|
|
@@ -1003,6 +1071,9 @@ export class MCPServer {
|
|
|
1003
1071
|
else if (error instanceof OperationNotFoundError) {
|
|
1004
1072
|
errorCode = -32601; // Method not found
|
|
1005
1073
|
}
|
|
1074
|
+
else if (error instanceof ResourceNotFoundError) {
|
|
1075
|
+
errorCode = -32601; // Method not found
|
|
1076
|
+
}
|
|
1006
1077
|
return {
|
|
1007
1078
|
jsonrpc: '2.0',
|
|
1008
1079
|
id: req.id,
|
|
@@ -1013,11 +1084,35 @@ export class MCPServer {
|
|
|
1013
1084
|
};
|
|
1014
1085
|
}
|
|
1015
1086
|
}
|
|
1016
|
-
|
|
1087
|
+
getFilteringForSession(sessionId, profileId) {
|
|
1088
|
+
if (this.httpTransport && sessionId) {
|
|
1089
|
+
const effectiveProfileId = profileId || this.getProfileIdValue();
|
|
1090
|
+
return this.httpTransport.getSessionFiltering(effectiveProfileId, sessionId);
|
|
1091
|
+
}
|
|
1092
|
+
return this.stdioFiltering;
|
|
1093
|
+
}
|
|
1094
|
+
getToolFilterForSession(sessionId, profileId) {
|
|
1095
|
+
if (this.httpTransport && sessionId && typeof this.httpTransport.getSessionToolFilter === 'function') {
|
|
1096
|
+
const effectiveProfileId = profileId || this.getProfileIdValue();
|
|
1097
|
+
return this.httpTransport.getSessionToolFilter(effectiveProfileId, sessionId);
|
|
1098
|
+
}
|
|
1099
|
+
return undefined;
|
|
1100
|
+
}
|
|
1101
|
+
getFilteringOperationInfo(toolDef, args) {
|
|
1102
|
+
if (toolDef.composite) {
|
|
1103
|
+
return undefined;
|
|
1104
|
+
}
|
|
1105
|
+
const operationId = this.toolGenerator.mapActionToOperation(toolDef, args);
|
|
1106
|
+
if (!operationId) {
|
|
1107
|
+
return undefined;
|
|
1108
|
+
}
|
|
1109
|
+
return this.parser.getOperation(operationId);
|
|
1110
|
+
}
|
|
1111
|
+
async handleOtherRequest(message, sessionId, profileId) {
|
|
1017
1112
|
const req = message;
|
|
1018
1113
|
// Check OAuth authentication for other operations (like tools/list)
|
|
1019
|
-
if (this.httpTransport && this.httpTransport.hasOAuthProvider()) {
|
|
1020
|
-
const authToken = await this.getAuthTokenFromSession(sessionId || '');
|
|
1114
|
+
if (this.httpTransport && this.httpTransport.hasOAuthProvider(profileId)) {
|
|
1115
|
+
const authToken = await this.getAuthTokenFromSession(sessionId || '', profileId);
|
|
1021
1116
|
if (!authToken) {
|
|
1022
1117
|
// Return OAuth required error with WWW-Authenticate header
|
|
1023
1118
|
// This should trigger the OAuth flow in the client
|
|
@@ -1029,7 +1124,7 @@ export class MCPServer {
|
|
|
1029
1124
|
message: 'Authentication required. Please authorize via OAuth.',
|
|
1030
1125
|
data: {
|
|
1031
1126
|
oauth_required: true,
|
|
1032
|
-
resource_metadata:
|
|
1127
|
+
resource_metadata: this.httpTransport.getOAuthProtectedResourceUrl(profileId),
|
|
1033
1128
|
scope: 'api'
|
|
1034
1129
|
}
|
|
1035
1130
|
}
|
|
@@ -1039,7 +1134,11 @@ export class MCPServer {
|
|
|
1039
1134
|
}
|
|
1040
1135
|
// Handle tools/list
|
|
1041
1136
|
if (req.method === 'tools/list') {
|
|
1042
|
-
const
|
|
1137
|
+
const sessionFilter = this.getToolFilterForSession(sessionId, profileId);
|
|
1138
|
+
const allowedSet = sessionFilter?.allowedToolNames;
|
|
1139
|
+
const tools = this.profile?.tools
|
|
1140
|
+
.filter(toolDef => !allowedSet || allowedSet.has(toolDef.name))
|
|
1141
|
+
.map(toolDef => this.toolGenerator.generateTool(toolDef)) || [];
|
|
1043
1142
|
return {
|
|
1044
1143
|
jsonrpc: '2.0',
|
|
1045
1144
|
id: req.id,
|
|
@@ -1058,6 +1157,204 @@ export class MCPServer {
|
|
|
1058
1157
|
},
|
|
1059
1158
|
};
|
|
1060
1159
|
}
|
|
1160
|
+
applyGlobalToolFiltering() {
|
|
1161
|
+
if (!this.profile) {
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
// Initialize ToolFilterService if not already done
|
|
1165
|
+
if (!this.toolFilterService) {
|
|
1166
|
+
const validator = new RegexValidator();
|
|
1167
|
+
const compiler = new RegexCompiler(validator);
|
|
1168
|
+
const envParser = new EnvConfigParser(compiler);
|
|
1169
|
+
const headerParser = new HeaderConfigParser(compiler);
|
|
1170
|
+
// Create OperationDetector for category filtering
|
|
1171
|
+
const classifier = new OperationClassifier();
|
|
1172
|
+
const resolver = new OpenAPIOperationResolver(this.parser);
|
|
1173
|
+
const detector = new OperationDetector(classifier, resolver);
|
|
1174
|
+
this.toolFilterService = new ToolFilterService(envParser, headerParser, this.logger, detector);
|
|
1175
|
+
}
|
|
1176
|
+
const originalTools = this.profile.tools;
|
|
1177
|
+
const originalCount = originalTools.length;
|
|
1178
|
+
// Apply filtering using new service
|
|
1179
|
+
const filteredTools = this.toolFilterService.applyGlobalFilter(originalTools, process.env);
|
|
1180
|
+
const allowedCount = filteredTools.length;
|
|
1181
|
+
const removedCount = originalCount - allowedCount;
|
|
1182
|
+
// Early return if no filtering config present (service returned same tools)
|
|
1183
|
+
if (filteredTools === originalTools) {
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
// Validation: check if filter has no effect
|
|
1187
|
+
if (originalCount > 0 && allowedCount === originalCount && removedCount === 0) {
|
|
1188
|
+
throw new ConfigurationError(`Tool filter configuration has no effect. Original tool count: ${originalCount}, filtered: ${allowedCount}. Check MCP4_TOOL_FILTER_* patterns.`);
|
|
1189
|
+
}
|
|
1190
|
+
// Validation: check if all tools filtered
|
|
1191
|
+
if (originalCount > 0 && allowedCount === 0) {
|
|
1192
|
+
throw new ConfigurationError(`All tools filtered out (original: ${originalCount}). Check MCP4_TOOL_FILTER_* settings.`);
|
|
1193
|
+
}
|
|
1194
|
+
// Validate composite tools against filtered operations
|
|
1195
|
+
const resolver = this.buildToolFilterResolver();
|
|
1196
|
+
this.validateCompositeToolsAgainstFilteredOperations(originalTools, filteredTools, resolver);
|
|
1197
|
+
// Update profile
|
|
1198
|
+
this.profile.tools = filteredTools;
|
|
1199
|
+
// Record summary for metrics
|
|
1200
|
+
this.globalToolFilterSummary = {
|
|
1201
|
+
originalCount,
|
|
1202
|
+
allowedCount,
|
|
1203
|
+
removedCount,
|
|
1204
|
+
patternCounts: {
|
|
1205
|
+
// Note: counts not available from new service, using simplified version
|
|
1206
|
+
filtered: removedCount
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
// Warn if high percentage filtered
|
|
1210
|
+
const warnThreshold = this.getToolFilterWarnThresholdPct();
|
|
1211
|
+
if (originalCount > 0) {
|
|
1212
|
+
const percentFiltered = (removedCount / originalCount) * 100;
|
|
1213
|
+
if (percentFiltered >= warnThreshold) {
|
|
1214
|
+
this.logger.warn('Tool filter removed high percentage of tools', {
|
|
1215
|
+
original: originalCount,
|
|
1216
|
+
surviving: allowedCount,
|
|
1217
|
+
threshold_pct: warnThreshold,
|
|
1218
|
+
removed_count: removedCount
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
if (this.httpTransport) {
|
|
1223
|
+
this.recordGlobalToolFilterMetrics();
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
applySessionToolFiltering(sessionId, profileId) {
|
|
1227
|
+
if (!this.httpTransport || !this.profile) {
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
if (typeof this.httpTransport.getSessionToolFilterRequest !== 'function') {
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
const effectiveProfileId = profileId || this.getProfileIdValue();
|
|
1234
|
+
const request = this.httpTransport.getSessionToolFilterRequest(effectiveProfileId, sessionId);
|
|
1235
|
+
if (!request) {
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
const originalCount = this.profile.tools.length;
|
|
1239
|
+
const resolver = this.buildToolFilterResolver();
|
|
1240
|
+
const sessionFilter = applySessionToolFilter(this.profile.tools, request, resolver);
|
|
1241
|
+
const allowedCount = sessionFilter.allowedToolNames.size;
|
|
1242
|
+
if (allowedCount === originalCount) {
|
|
1243
|
+
throw new ValidationError(`X-Mcp4-Tools filter has no effect for this session. Available tools: ${originalCount}, after filter: ${allowedCount}. Check patterns.`);
|
|
1244
|
+
}
|
|
1245
|
+
if (originalCount > 0 && allowedCount === 0) {
|
|
1246
|
+
const sources = request.rawEntries.length > 0 ? request.rawEntries.join(', ') : 'none';
|
|
1247
|
+
throw new ValidationError(`X-Mcp4-Tools filtered out all tools (original: ${originalCount}). Removed by: ${sources}. Check session filter configuration.`);
|
|
1248
|
+
}
|
|
1249
|
+
this.httpTransport.setSessionToolFilter(effectiveProfileId, sessionId, sessionFilter);
|
|
1250
|
+
this.logger.info('Session tool filter applied', {
|
|
1251
|
+
sessionId,
|
|
1252
|
+
originalCount,
|
|
1253
|
+
allowedCount,
|
|
1254
|
+
patterns: request.rawEntries,
|
|
1255
|
+
});
|
|
1256
|
+
this.recordSessionToolFilterMetrics(sessionId, allowedCount, request);
|
|
1257
|
+
}
|
|
1258
|
+
buildToolFilterResolver() {
|
|
1259
|
+
return {
|
|
1260
|
+
getOperationById: (operationId) => this.parser.getOperation(operationId),
|
|
1261
|
+
getOperationForCall: (call) => {
|
|
1262
|
+
const [method, path] = call.split(' ');
|
|
1263
|
+
if (!method || !path) {
|
|
1264
|
+
return undefined;
|
|
1265
|
+
}
|
|
1266
|
+
const pathInfo = this.parser.getPath(path);
|
|
1267
|
+
return pathInfo?.operations[method.toLowerCase()];
|
|
1268
|
+
},
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
validateCompositeToolsAgainstFilteredOperations(originalTools, allowedTools, resolver) {
|
|
1272
|
+
const operationToTools = new Map();
|
|
1273
|
+
for (const tool of originalTools) {
|
|
1274
|
+
if (!tool.operations) {
|
|
1275
|
+
continue;
|
|
1276
|
+
}
|
|
1277
|
+
for (const operationId of Object.values(tool.operations)) {
|
|
1278
|
+
if (typeof operationId !== 'string') {
|
|
1279
|
+
continue;
|
|
1280
|
+
}
|
|
1281
|
+
const names = operationToTools.get(operationId) ?? [];
|
|
1282
|
+
names.push(tool.name);
|
|
1283
|
+
operationToTools.set(operationId, names);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
const allowedOperationIds = new Set();
|
|
1287
|
+
for (const tool of allowedTools) {
|
|
1288
|
+
if (!tool.operations) {
|
|
1289
|
+
continue;
|
|
1290
|
+
}
|
|
1291
|
+
for (const operationId of Object.values(tool.operations)) {
|
|
1292
|
+
if (typeof operationId !== 'string') {
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
allowedOperationIds.add(operationId);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
for (const tool of allowedTools) {
|
|
1299
|
+
if (!tool.composite || !tool.steps) {
|
|
1300
|
+
continue;
|
|
1301
|
+
}
|
|
1302
|
+
for (const step of tool.steps) {
|
|
1303
|
+
const operation = resolver.getOperationForCall(step.call);
|
|
1304
|
+
if (!operation) {
|
|
1305
|
+
continue;
|
|
1306
|
+
}
|
|
1307
|
+
if (allowedOperationIds.has(operation.operationId)) {
|
|
1308
|
+
continue;
|
|
1309
|
+
}
|
|
1310
|
+
const removedTools = operationToTools.get(operation.operationId);
|
|
1311
|
+
if (!removedTools || removedTools.length === 0) {
|
|
1312
|
+
continue;
|
|
1313
|
+
}
|
|
1314
|
+
const removedList = removedTools.join(', ');
|
|
1315
|
+
throw new ConfigurationError(`Composite tool '${tool.name}' step '${step.call}' calls filtered tool '${removedList}'. ` +
|
|
1316
|
+
`Add '${removedList}' to filter or include _allow_list or _allow_read if it is a list or read operation.`);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
getToolFilterWarnThresholdPct() {
|
|
1321
|
+
const raw = process.env.MCP4_TOOL_FILTER_WARN_THRESHOLD_PCT;
|
|
1322
|
+
if (raw === undefined) {
|
|
1323
|
+
return 90;
|
|
1324
|
+
}
|
|
1325
|
+
const parsed = Number(raw);
|
|
1326
|
+
if (Number.isNaN(parsed) || parsed <= 0) {
|
|
1327
|
+
throw new ConfigurationError(`Invalid MCP4_TOOL_FILTER_WARN_THRESHOLD_PCT: expected positive number, got '${raw}'.`);
|
|
1328
|
+
}
|
|
1329
|
+
return parsed;
|
|
1330
|
+
}
|
|
1331
|
+
recordGlobalToolFilterMetrics() {
|
|
1332
|
+
if (!this.httpTransport || !this.globalToolFilterSummary) {
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
if (typeof this.httpTransport.recordGlobalToolFilterMetrics !== 'function') {
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
this.httpTransport.recordGlobalToolFilterMetrics(this.globalToolFilterSummary);
|
|
1339
|
+
}
|
|
1340
|
+
recordSessionToolFilterMetrics(sessionId, allowedCount, request) {
|
|
1341
|
+
if (!this.httpTransport) {
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
if (typeof this.httpTransport.recordSessionToolFilterMetrics !== 'function') {
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
this.httpTransport.recordSessionToolFilterMetrics(sessionId, allowedCount, request);
|
|
1348
|
+
}
|
|
1349
|
+
recordToolFilterRejection(toolName, source) {
|
|
1350
|
+
if (!this.httpTransport) {
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
if (typeof this.httpTransport.recordToolFilterRejection !== 'function') {
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
this.httpTransport.recordToolFilterRejection(toolName, source);
|
|
1357
|
+
}
|
|
1061
1358
|
/**
|
|
1062
1359
|
* Stop the MCP server gracefully
|
|
1063
1360
|
*
|