@daniel.stefan/metalink 1.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 +160 -0
- package/package.json +96 -0
- package/packages/cli/dist/bin/cli.d.ts +3 -0
- package/packages/cli/dist/bin/cli.d.ts.map +1 -0
- package/packages/cli/dist/bin/cli.js +4 -0
- package/packages/cli/dist/bin/cli.js.map +1 -0
- package/packages/cli/dist/commands/config/init.d.ts +9 -0
- package/packages/cli/dist/commands/config/init.d.ts.map +1 -0
- package/packages/cli/dist/commands/config/init.js +38 -0
- package/packages/cli/dist/commands/config/init.js.map +1 -0
- package/packages/cli/dist/commands/config/validate.d.ts +9 -0
- package/packages/cli/dist/commands/config/validate.d.ts.map +1 -0
- package/packages/cli/dist/commands/config/validate.js +34 -0
- package/packages/cli/dist/commands/config/validate.js.map +1 -0
- package/packages/cli/dist/commands/daemon/restart.d.ts +15 -0
- package/packages/cli/dist/commands/daemon/restart.d.ts.map +1 -0
- package/packages/cli/dist/commands/daemon/restart.js +184 -0
- package/packages/cli/dist/commands/daemon/restart.js.map +1 -0
- package/packages/cli/dist/commands/daemon/start.d.ts +7 -0
- package/packages/cli/dist/commands/daemon/start.d.ts.map +1 -0
- package/packages/cli/dist/commands/daemon/start.js +85 -0
- package/packages/cli/dist/commands/daemon/start.js.map +1 -0
- package/packages/cli/dist/commands/daemon/status.d.ts +7 -0
- package/packages/cli/dist/commands/daemon/status.d.ts.map +1 -0
- package/packages/cli/dist/commands/daemon/status.js +69 -0
- package/packages/cli/dist/commands/daemon/status.js.map +1 -0
- package/packages/cli/dist/commands/daemon/stop.d.ts +8 -0
- package/packages/cli/dist/commands/daemon/stop.d.ts.map +1 -0
- package/packages/cli/dist/commands/daemon/stop.js +77 -0
- package/packages/cli/dist/commands/daemon/stop.js.map +1 -0
- package/packages/cli/dist/commands/import/mcpm.d.ts +10 -0
- package/packages/cli/dist/commands/import/mcpm.d.ts.map +1 -0
- package/packages/cli/dist/commands/import/mcpm.js +58 -0
- package/packages/cli/dist/commands/import/mcpm.js.map +1 -0
- package/packages/cli/dist/commands/safety/add-risky-pattern.d.ts +16 -0
- package/packages/cli/dist/commands/safety/add-risky-pattern.d.ts.map +1 -0
- package/packages/cli/dist/commands/safety/add-risky-pattern.js +72 -0
- package/packages/cli/dist/commands/safety/add-risky-pattern.js.map +1 -0
- package/packages/cli/dist/commands/safety/add-risky.d.ts +12 -0
- package/packages/cli/dist/commands/safety/add-risky.d.ts.map +1 -0
- package/packages/cli/dist/commands/safety/add-risky.js +52 -0
- package/packages/cli/dist/commands/safety/add-risky.js.map +1 -0
- package/packages/cli/dist/commands/safety/add-safe-pattern.d.ts +16 -0
- package/packages/cli/dist/commands/safety/add-safe-pattern.d.ts.map +1 -0
- package/packages/cli/dist/commands/safety/add-safe-pattern.js +72 -0
- package/packages/cli/dist/commands/safety/add-safe-pattern.js.map +1 -0
- package/packages/cli/dist/commands/safety/add-safe.d.ts +12 -0
- package/packages/cli/dist/commands/safety/add-safe.d.ts.map +1 -0
- package/packages/cli/dist/commands/safety/add-safe.js +52 -0
- package/packages/cli/dist/commands/safety/add-safe.js.map +1 -0
- package/packages/cli/dist/commands/safety/check.d.ts +9 -0
- package/packages/cli/dist/commands/safety/check.d.ts.map +1 -0
- package/packages/cli/dist/commands/safety/check.js +36 -0
- package/packages/cli/dist/commands/safety/check.js.map +1 -0
- package/packages/cli/dist/commands/safety/export.d.ts +10 -0
- package/packages/cli/dist/commands/safety/export.d.ts.map +1 -0
- package/packages/cli/dist/commands/safety/export.js +48 -0
- package/packages/cli/dist/commands/safety/export.js.map +1 -0
- package/packages/cli/dist/commands/safety/import.d.ts +12 -0
- package/packages/cli/dist/commands/safety/import.d.ts.map +1 -0
- package/packages/cli/dist/commands/safety/import.js +78 -0
- package/packages/cli/dist/commands/safety/import.js.map +1 -0
- package/packages/cli/dist/commands/safety/index.d.ts +8 -0
- package/packages/cli/dist/commands/safety/index.d.ts.map +1 -0
- package/packages/cli/dist/commands/safety/index.js +46 -0
- package/packages/cli/dist/commands/safety/index.js.map +1 -0
- package/packages/cli/dist/commands/safety/list.d.ts +6 -0
- package/packages/cli/dist/commands/safety/list.d.ts.map +1 -0
- package/packages/cli/dist/commands/safety/list.js +77 -0
- package/packages/cli/dist/commands/safety/list.js.map +1 -0
- package/packages/cli/dist/commands/safety/remove.d.ts +9 -0
- package/packages/cli/dist/commands/safety/remove.d.ts.map +1 -0
- package/packages/cli/dist/commands/safety/remove.js +46 -0
- package/packages/cli/dist/commands/safety/remove.js.map +1 -0
- package/packages/cli/dist/commands/safety/reset.d.ts +9 -0
- package/packages/cli/dist/commands/safety/reset.d.ts.map +1 -0
- package/packages/cli/dist/commands/safety/reset.js +46 -0
- package/packages/cli/dist/commands/safety/reset.js.map +1 -0
- package/packages/cli/dist/commands/safety/validate.d.ts +9 -0
- package/packages/cli/dist/commands/safety/validate.d.ts.map +1 -0
- package/packages/cli/dist/commands/safety/validate.js +51 -0
- package/packages/cli/dist/commands/safety/validate.js.map +1 -0
- package/packages/cli/dist/commands/secret/get.d.ts +9 -0
- package/packages/cli/dist/commands/secret/get.d.ts.map +1 -0
- package/packages/cli/dist/commands/secret/get.js +26 -0
- package/packages/cli/dist/commands/secret/get.js.map +1 -0
- package/packages/cli/dist/commands/secret/set.d.ts +10 -0
- package/packages/cli/dist/commands/secret/set.d.ts.map +1 -0
- package/packages/cli/dist/commands/secret/set.js +22 -0
- package/packages/cli/dist/commands/secret/set.js.map +1 -0
- package/packages/cli/dist/commands/server/__tests__/server-management-e2e.test.d.ts +2 -0
- package/packages/cli/dist/commands/server/__tests__/server-management-e2e.test.d.ts.map +1 -0
- package/packages/cli/dist/commands/server/__tests__/server-management-e2e.test.js +234 -0
- package/packages/cli/dist/commands/server/__tests__/server-management-e2e.test.js.map +1 -0
- package/packages/cli/dist/commands/server/add.d.ts +14 -0
- package/packages/cli/dist/commands/server/add.d.ts.map +1 -0
- package/packages/cli/dist/commands/server/add.js +86 -0
- package/packages/cli/dist/commands/server/add.js.map +1 -0
- package/packages/cli/dist/commands/server/available.d.ts +10 -0
- package/packages/cli/dist/commands/server/available.d.ts.map +1 -0
- package/packages/cli/dist/commands/server/available.js +62 -0
- package/packages/cli/dist/commands/server/available.js.map +1 -0
- package/packages/cli/dist/commands/server/debug.d.ts +18 -0
- package/packages/cli/dist/commands/server/debug.d.ts.map +1 -0
- package/packages/cli/dist/commands/server/debug.js +165 -0
- package/packages/cli/dist/commands/server/debug.js.map +1 -0
- package/packages/cli/dist/commands/server/info.d.ts +13 -0
- package/packages/cli/dist/commands/server/info.d.ts.map +1 -0
- package/packages/cli/dist/commands/server/info.js +62 -0
- package/packages/cli/dist/commands/server/info.js.map +1 -0
- package/packages/cli/dist/commands/server/list.d.ts +10 -0
- package/packages/cli/dist/commands/server/list.d.ts.map +1 -0
- package/packages/cli/dist/commands/server/list.js +105 -0
- package/packages/cli/dist/commands/server/list.js.map +1 -0
- package/packages/cli/dist/commands/server/refresh-tools.d.ts +13 -0
- package/packages/cli/dist/commands/server/refresh-tools.d.ts.map +1 -0
- package/packages/cli/dist/commands/server/refresh-tools.js +46 -0
- package/packages/cli/dist/commands/server/refresh-tools.js.map +1 -0
- package/packages/cli/dist/commands/server/remove.d.ts +12 -0
- package/packages/cli/dist/commands/server/remove.d.ts.map +1 -0
- package/packages/cli/dist/commands/server/remove.js +39 -0
- package/packages/cli/dist/commands/server/remove.js.map +1 -0
- package/packages/cli/dist/commands/server/restart.d.ts +9 -0
- package/packages/cli/dist/commands/server/restart.d.ts.map +1 -0
- package/packages/cli/dist/commands/server/restart.js +30 -0
- package/packages/cli/dist/commands/server/restart.js.map +1 -0
- package/packages/cli/dist/commands/server/start.d.ts +9 -0
- package/packages/cli/dist/commands/server/start.d.ts.map +1 -0
- package/packages/cli/dist/commands/server/start.js +37 -0
- package/packages/cli/dist/commands/server/start.js.map +1 -0
- package/packages/cli/dist/commands/server/status.d.ts +9 -0
- package/packages/cli/dist/commands/server/status.d.ts.map +1 -0
- package/packages/cli/dist/commands/server/status.js +30 -0
- package/packages/cli/dist/commands/server/status.js.map +1 -0
- package/packages/cli/dist/commands/server/stop.d.ts +9 -0
- package/packages/cli/dist/commands/server/stop.d.ts.map +1 -0
- package/packages/cli/dist/commands/server/stop.js +31 -0
- package/packages/cli/dist/commands/server/stop.js.map +1 -0
- package/packages/cli/dist/commands/server/validate.d.ts +15 -0
- package/packages/cli/dist/commands/server/validate.d.ts.map +1 -0
- package/packages/cli/dist/commands/server/validate.js +87 -0
- package/packages/cli/dist/commands/server/validate.js.map +1 -0
- package/packages/cli/dist/commands/stdio.d.ts +36 -0
- package/packages/cli/dist/commands/stdio.d.ts.map +1 -0
- package/packages/cli/dist/commands/stdio.js +85 -0
- package/packages/cli/dist/commands/stdio.js.map +1 -0
- package/packages/cli/dist/commands/tool/execute-confirm.d.ts +12 -0
- package/packages/cli/dist/commands/tool/execute-confirm.d.ts.map +1 -0
- package/packages/cli/dist/commands/tool/execute-confirm.js +98 -0
- package/packages/cli/dist/commands/tool/execute-confirm.js.map +1 -0
- package/packages/cli/dist/commands/tool/execute.d.ts +12 -0
- package/packages/cli/dist/commands/tool/execute.d.ts.map +1 -0
- package/packages/cli/dist/commands/tool/execute.js +55 -0
- package/packages/cli/dist/commands/tool/execute.js.map +1 -0
- package/packages/cli/dist/commands/tool/list.d.ts +13 -0
- package/packages/cli/dist/commands/tool/list.d.ts.map +1 -0
- package/packages/cli/dist/commands/tool/list.js +91 -0
- package/packages/cli/dist/commands/tool/list.js.map +1 -0
- package/packages/cli/dist/commands/tool/search.d.ts +12 -0
- package/packages/cli/dist/commands/tool/search.d.ts.map +1 -0
- package/packages/cli/dist/commands/tool/search.js +87 -0
- package/packages/cli/dist/commands/tool/search.js.map +1 -0
- package/packages/cli/dist/index.d.ts +2 -0
- package/packages/cli/dist/index.d.ts.map +1 -0
- package/packages/cli/dist/index.js +9 -0
- package/packages/cli/dist/index.js.map +1 -0
- package/packages/cli/dist/utils/daemon-checker.d.ts +18 -0
- package/packages/cli/dist/utils/daemon-checker.d.ts.map +1 -0
- package/packages/cli/dist/utils/daemon-checker.js +69 -0
- package/packages/cli/dist/utils/daemon-checker.js.map +1 -0
- package/packages/cli/dist/utils/daemon-endpoint.d.ts +7 -0
- package/packages/cli/dist/utils/daemon-endpoint.d.ts.map +1 -0
- package/packages/cli/dist/utils/daemon-endpoint.js +13 -0
- package/packages/cli/dist/utils/daemon-endpoint.js.map +1 -0
- package/packages/cli/dist/utils/get-configured-port.d.ts +43 -0
- package/packages/cli/dist/utils/get-configured-port.d.ts.map +1 -0
- package/packages/cli/dist/utils/get-configured-port.js +141 -0
- package/packages/cli/dist/utils/get-configured-port.js.map +1 -0
- package/packages/cli/dist/utils/stdio-bridge.d.ts +48 -0
- package/packages/cli/dist/utils/stdio-bridge.d.ts.map +1 -0
- package/packages/cli/dist/utils/stdio-bridge.js +181 -0
- package/packages/cli/dist/utils/stdio-bridge.js.map +1 -0
- package/packages/cli/package.json +48 -0
- package/packages/core/dist/config/defaults.d.ts +36 -0
- package/packages/core/dist/config/defaults.d.ts.map +1 -0
- package/packages/core/dist/config/defaults.js +324 -0
- package/packages/core/dist/config/defaults.js.map +1 -0
- package/packages/core/dist/config/index.d.ts +9 -0
- package/packages/core/dist/config/index.d.ts.map +1 -0
- package/packages/core/dist/config/index.js +14 -0
- package/packages/core/dist/config/index.js.map +1 -0
- package/packages/core/dist/config/loader.d.ts +269 -0
- package/packages/core/dist/config/loader.d.ts.map +1 -0
- package/packages/core/dist/config/loader.js +777 -0
- package/packages/core/dist/config/loader.js.map +1 -0
- package/packages/core/dist/config/registry.d.ts +212 -0
- package/packages/core/dist/config/registry.d.ts.map +1 -0
- package/packages/core/dist/config/registry.js +754 -0
- package/packages/core/dist/config/registry.js.map +1 -0
- package/packages/core/dist/config/schema.d.ts +4352 -0
- package/packages/core/dist/config/schema.d.ts.map +1 -0
- package/packages/core/dist/config/schema.js +267 -0
- package/packages/core/dist/config/schema.js.map +1 -0
- package/packages/core/dist/daemon.d.ts +7 -0
- package/packages/core/dist/daemon.d.ts.map +1 -0
- package/packages/core/dist/daemon.js +116 -0
- package/packages/core/dist/daemon.js.map +1 -0
- package/packages/core/dist/http-client-retry.d.ts +67 -0
- package/packages/core/dist/http-client-retry.d.ts.map +1 -0
- package/packages/core/dist/http-client-retry.js +133 -0
- package/packages/core/dist/http-client-retry.js.map +1 -0
- package/packages/core/dist/http-client-updated.d.ts +147 -0
- package/packages/core/dist/http-client-updated.d.ts.map +1 -0
- package/packages/core/dist/http-client-updated.js +452 -0
- package/packages/core/dist/http-client-updated.js.map +1 -0
- package/packages/core/dist/http-client.d.ts +207 -0
- package/packages/core/dist/http-client.d.ts.map +1 -0
- package/packages/core/dist/http-client.js +704 -0
- package/packages/core/dist/http-client.js.map +1 -0
- package/packages/core/dist/index.d.ts +13 -0
- package/packages/core/dist/index.d.ts.map +1 -0
- package/packages/core/dist/index.js +23 -0
- package/packages/core/dist/index.js.map +1 -0
- package/packages/core/dist/logging/index.d.ts +46 -0
- package/packages/core/dist/logging/index.d.ts.map +1 -0
- package/packages/core/dist/logging/index.js +74 -0
- package/packages/core/dist/logging/index.js.map +1 -0
- package/packages/core/dist/metrics/index.d.ts +339 -0
- package/packages/core/dist/metrics/index.d.ts.map +1 -0
- package/packages/core/dist/metrics/index.js +792 -0
- package/packages/core/dist/metrics/index.js.map +1 -0
- package/packages/core/dist/plugins/index.d.ts +49 -0
- package/packages/core/dist/plugins/index.d.ts.map +1 -0
- package/packages/core/dist/plugins/index.js +82 -0
- package/packages/core/dist/plugins/index.js.map +1 -0
- package/packages/core/dist/secrets/index.d.ts +6 -0
- package/packages/core/dist/secrets/index.d.ts.map +1 -0
- package/packages/core/dist/secrets/index.js +5 -0
- package/packages/core/dist/secrets/index.js.map +1 -0
- package/packages/core/dist/secrets/keyring.d.ts +54 -0
- package/packages/core/dist/secrets/keyring.d.ts.map +1 -0
- package/packages/core/dist/secrets/keyring.js +141 -0
- package/packages/core/dist/secrets/keyring.js.map +1 -0
- package/packages/core/dist/server/batch-executor.d.ts +83 -0
- package/packages/core/dist/server/batch-executor.d.ts.map +1 -0
- package/packages/core/dist/server/batch-executor.js +291 -0
- package/packages/core/dist/server/batch-executor.js.map +1 -0
- package/packages/core/dist/server/circuit-breaker.d.ts +215 -0
- package/packages/core/dist/server/circuit-breaker.d.ts.map +1 -0
- package/packages/core/dist/server/circuit-breaker.js +330 -0
- package/packages/core/dist/server/circuit-breaker.js.map +1 -0
- package/packages/core/dist/server/client-detection.d.ts +40 -0
- package/packages/core/dist/server/client-detection.d.ts.map +1 -0
- package/packages/core/dist/server/client-detection.js +242 -0
- package/packages/core/dist/server/client-detection.js.map +1 -0
- package/packages/core/dist/server/client-profiles.d.ts +102 -0
- package/packages/core/dist/server/client-profiles.d.ts.map +1 -0
- package/packages/core/dist/server/client-profiles.js +254 -0
- package/packages/core/dist/server/client-profiles.js.map +1 -0
- package/packages/core/dist/server/http.d.ts +386 -0
- package/packages/core/dist/server/http.d.ts.map +1 -0
- package/packages/core/dist/server/http.js +4253 -0
- package/packages/core/dist/server/http.js.map +1 -0
- package/packages/core/dist/server/index.d.ts +7 -0
- package/packages/core/dist/server/index.d.ts.map +1 -0
- package/packages/core/dist/server/index.js +6 -0
- package/packages/core/dist/server/index.js.map +1 -0
- package/packages/core/dist/server/manager.d.ts +458 -0
- package/packages/core/dist/server/manager.d.ts.map +1 -0
- package/packages/core/dist/server/manager.js +3255 -0
- package/packages/core/dist/server/manager.js.map +1 -0
- package/packages/core/dist/server/managers/HttpConnectionManager.d.ts +69 -0
- package/packages/core/dist/server/managers/HttpConnectionManager.d.ts.map +1 -0
- package/packages/core/dist/server/managers/HttpConnectionManager.js +214 -0
- package/packages/core/dist/server/managers/HttpConnectionManager.js.map +1 -0
- package/packages/core/dist/server/managers/ProcessManager.d.ts +128 -0
- package/packages/core/dist/server/managers/ProcessManager.d.ts.map +1 -0
- package/packages/core/dist/server/managers/ProcessManager.js +443 -0
- package/packages/core/dist/server/managers/ProcessManager.js.map +1 -0
- package/packages/core/dist/server/managers/SchemaCacheManager.d.ts +152 -0
- package/packages/core/dist/server/managers/SchemaCacheManager.d.ts.map +1 -0
- package/packages/core/dist/server/managers/SchemaCacheManager.js +426 -0
- package/packages/core/dist/server/managers/SchemaCacheManager.js.map +1 -0
- package/packages/core/dist/server/managers/index.d.ts +9 -0
- package/packages/core/dist/server/managers/index.d.ts.map +1 -0
- package/packages/core/dist/server/managers/index.js +9 -0
- package/packages/core/dist/server/managers/index.js.map +1 -0
- package/packages/core/dist/server/metrics.d.ts +134 -0
- package/packages/core/dist/server/metrics.d.ts.map +1 -0
- package/packages/core/dist/server/metrics.js +273 -0
- package/packages/core/dist/server/metrics.js.map +1 -0
- package/packages/core/dist/server/prompts.d.ts +58 -0
- package/packages/core/dist/server/prompts.d.ts.map +1 -0
- package/packages/core/dist/server/prompts.js +405 -0
- package/packages/core/dist/server/prompts.js.map +1 -0
- package/packages/core/dist/server/protocol-versions.d.ts +49 -0
- package/packages/core/dist/server/protocol-versions.d.ts.map +1 -0
- package/packages/core/dist/server/protocol-versions.js +173 -0
- package/packages/core/dist/server/protocol-versions.js.map +1 -0
- package/packages/core/dist/server/resources.d.ts +64 -0
- package/packages/core/dist/server/resources.d.ts.map +1 -0
- package/packages/core/dist/server/resources.js +243 -0
- package/packages/core/dist/server/resources.js.map +1 -0
- package/packages/core/dist/server/schema-store.d.ts +84 -0
- package/packages/core/dist/server/schema-store.d.ts.map +1 -0
- package/packages/core/dist/server/schema-store.js +234 -0
- package/packages/core/dist/server/schema-store.js.map +1 -0
- package/packages/core/dist/server/schema-validator.d.ts +51 -0
- package/packages/core/dist/server/schema-validator.d.ts.map +1 -0
- package/packages/core/dist/server/schema-validator.js +208 -0
- package/packages/core/dist/server/schema-validator.js.map +1 -0
- package/packages/core/dist/server/token-calculator.d.ts +44 -0
- package/packages/core/dist/server/token-calculator.d.ts.map +1 -0
- package/packages/core/dist/server/token-calculator.js +53 -0
- package/packages/core/dist/server/token-calculator.js.map +1 -0
- package/packages/core/dist/server/types.d.ts +45 -0
- package/packages/core/dist/server/types.d.ts.map +1 -0
- package/packages/core/dist/server/types.js +5 -0
- package/packages/core/dist/server/types.js.map +1 -0
- package/packages/core/dist/utils/file-lock.d.ts +73 -0
- package/packages/core/dist/utils/file-lock.d.ts.map +1 -0
- package/packages/core/dist/utils/file-lock.js +235 -0
- package/packages/core/dist/utils/file-lock.js.map +1 -0
- package/packages/core/package.json +36 -0
- package/packages/dashboard/dist/assets/index-B7hvkCMu.css +1 -0
- package/packages/dashboard/dist/assets/index-yZhLPpzr.js +1 -0
- package/packages/dashboard/dist/index.html +14 -0
- package/packages/dashboard/package.json +24 -0
- package/packages/shared/dist/version.d.ts +2 -0
- package/packages/shared/dist/version.d.ts.map +1 -0
- package/packages/shared/dist/version.js +2 -0
- package/packages/shared/dist/version.js.map +1 -0
- package/packages/shared/package.json +18 -0
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Client for MCP Streamable HTTP Protocol
|
|
3
|
+
*
|
|
4
|
+
* Implements MCP 2025-06-18 Streamable HTTP transport for communicating with
|
|
5
|
+
* remote HTTP-based MCP servers (e.g., Windmill, custom HTTP endpoints).
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Session management with Mcp-Session-Id header
|
|
9
|
+
* - SSE (Server-Sent Events) streaming support
|
|
10
|
+
* - HTTP-stream response batching
|
|
11
|
+
* - Authentication (Bearer, API-Key, OAuth2)
|
|
12
|
+
* - Automatic session resumption via Last-Event-ID
|
|
13
|
+
* - Request/response correlation with JSON-RPC IDs
|
|
14
|
+
* - Request concurrency limits (max 10 concurrent requests)
|
|
15
|
+
*/
|
|
16
|
+
import { EventEmitter } from 'events';
|
|
17
|
+
import { Agent, fetch as undiciFetch } from 'undici';
|
|
18
|
+
/**
|
|
19
|
+
* HTTP status codes that indicate retryable errors
|
|
20
|
+
*/
|
|
21
|
+
const RETRYABLE_STATUS_CODES = new Set([502, 503, 504]);
|
|
22
|
+
/**
|
|
23
|
+
* HTTP status codes that should never be retried
|
|
24
|
+
* Note: 404 is handled specially for session-related errors (auto-reinitialize)
|
|
25
|
+
*/
|
|
26
|
+
const NON_RETRYABLE_STATUS_CODES = new Set([400, 401, 403, 404]);
|
|
27
|
+
/**
|
|
28
|
+
* Patterns in response body that indicate session-related 404 errors
|
|
29
|
+
* Per MCP spec, client MUST reinitialize when receiving 404 for invalid session
|
|
30
|
+
*/
|
|
31
|
+
const SESSION_ERROR_PATTERNS = [
|
|
32
|
+
'session',
|
|
33
|
+
'Session',
|
|
34
|
+
'SESSION',
|
|
35
|
+
'not found',
|
|
36
|
+
'Not Found',
|
|
37
|
+
'invalid',
|
|
38
|
+
'Invalid',
|
|
39
|
+
'expired',
|
|
40
|
+
'Expired',
|
|
41
|
+
];
|
|
42
|
+
/**
|
|
43
|
+
* Network error patterns that are retryable
|
|
44
|
+
*/
|
|
45
|
+
const RETRYABLE_ERROR_PATTERNS = [
|
|
46
|
+
'ECONNREFUSED',
|
|
47
|
+
'ECONNRESET',
|
|
48
|
+
'ETIMEDOUT',
|
|
49
|
+
'EHOSTUNREACH',
|
|
50
|
+
'ENETUNREACH',
|
|
51
|
+
'ERR_HTTP2_STREAM_RESET',
|
|
52
|
+
];
|
|
53
|
+
/**
|
|
54
|
+
* HTTP Client for remote MCP servers
|
|
55
|
+
* Handles Streamable HTTP protocol with session management and request concurrency limits
|
|
56
|
+
*/
|
|
57
|
+
export class HttpClient extends EventEmitter {
|
|
58
|
+
constructor(config, circuitBreaker) {
|
|
59
|
+
super();
|
|
60
|
+
this.session = null;
|
|
61
|
+
this.pendingRequests = new Map();
|
|
62
|
+
this.requestId = 1;
|
|
63
|
+
this.sseEventBuffer = '';
|
|
64
|
+
this.isConnected = false;
|
|
65
|
+
this.isConnecting = false;
|
|
66
|
+
// Concurrency control
|
|
67
|
+
this.requestQueue = [];
|
|
68
|
+
this.activeRequests = 0;
|
|
69
|
+
this.MAX_CONCURRENT_REQUESTS = 10;
|
|
70
|
+
// Connection pooling
|
|
71
|
+
this.agent = null;
|
|
72
|
+
this.poolMetrics = {
|
|
73
|
+
activeSockets: 0,
|
|
74
|
+
freeSockets: 0,
|
|
75
|
+
pendingRequests: 0,
|
|
76
|
+
totalConnections: 0,
|
|
77
|
+
};
|
|
78
|
+
// Retry configuration
|
|
79
|
+
this.retryConfig = {
|
|
80
|
+
maxRetries: 3,
|
|
81
|
+
initialDelayMs: 100,
|
|
82
|
+
maxDelayMs: 30000,
|
|
83
|
+
jitterMs: 1000,
|
|
84
|
+
};
|
|
85
|
+
// P1: Stream management for SSE/HTTP-stream
|
|
86
|
+
this.activeStreams = new Map();
|
|
87
|
+
this.streamTimeouts = new Map();
|
|
88
|
+
this.config = config;
|
|
89
|
+
this.circuitBreaker = circuitBreaker;
|
|
90
|
+
this.initializeSession();
|
|
91
|
+
this.initializeAgent();
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Initialize or restore session
|
|
95
|
+
*/
|
|
96
|
+
initializeSession() {
|
|
97
|
+
// Check if we need to restore from previous session
|
|
98
|
+
const sessionId = this.session?.id || this.generateSessionId();
|
|
99
|
+
this.session = {
|
|
100
|
+
id: sessionId,
|
|
101
|
+
createdAt: Date.now(),
|
|
102
|
+
lastActivity: Date.now(),
|
|
103
|
+
};
|
|
104
|
+
this.emit('session:created', { sessionId });
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Generate unique session ID (UUID v4)
|
|
108
|
+
*/
|
|
109
|
+
generateSessionId() {
|
|
110
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
111
|
+
const r = Math.random() * 16 | 0;
|
|
112
|
+
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
113
|
+
return v.toString(16);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Initialize undici Agent with connection pooling
|
|
118
|
+
*/
|
|
119
|
+
initializeAgent() {
|
|
120
|
+
const poolConfig = this.config.connectionPool || {};
|
|
121
|
+
const agentOptions = {
|
|
122
|
+
keepAliveTimeout: poolConfig.keepAliveMsecs || 30000,
|
|
123
|
+
keepAliveMaxTimeout: poolConfig.keepAliveMsecs || 30000,
|
|
124
|
+
keepAliveTimeoutThreshold: 1000,
|
|
125
|
+
connections: poolConfig.maxSockets || 256,
|
|
126
|
+
pipelining: 0, // Disable pipelining for MCP (request/response pairs)
|
|
127
|
+
connect: {
|
|
128
|
+
timeout: poolConfig.timeout || 60000,
|
|
129
|
+
keepAlive: poolConfig.keepAlive !== false,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
this.agent = new Agent(agentOptions);
|
|
133
|
+
this.emit('agent:initialized', {
|
|
134
|
+
config: poolConfig,
|
|
135
|
+
serverName: this.config.name
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Set retry configuration
|
|
140
|
+
*/
|
|
141
|
+
setRetryConfig(config) {
|
|
142
|
+
this.retryConfig = { ...this.retryConfig, ...config };
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Check if an error or response is retryable
|
|
146
|
+
*/
|
|
147
|
+
isRetryable(error) {
|
|
148
|
+
// Handle Response objects (failed fetch)
|
|
149
|
+
if (error instanceof Response) {
|
|
150
|
+
if (NON_RETRYABLE_STATUS_CODES.has(error.status)) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
return RETRYABLE_STATUS_CODES.has(error.status);
|
|
154
|
+
}
|
|
155
|
+
// Handle Error objects
|
|
156
|
+
if (error instanceof Error) {
|
|
157
|
+
const message = error.message;
|
|
158
|
+
const httpError = error;
|
|
159
|
+
// Check if status code is set (from HTTP response)
|
|
160
|
+
if (httpError.statusCode) {
|
|
161
|
+
if (NON_RETRYABLE_STATUS_CODES.has(httpError.statusCode)) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
return RETRYABLE_STATUS_CODES.has(httpError.statusCode);
|
|
165
|
+
}
|
|
166
|
+
// Check for retryable network error patterns
|
|
167
|
+
return RETRYABLE_ERROR_PATTERNS.some(pattern => message.includes(pattern));
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Calculate exponential backoff delay with jitter
|
|
173
|
+
*/
|
|
174
|
+
calculateBackoffDelay(attempt) {
|
|
175
|
+
const baseDelay = this.retryConfig.initialDelayMs * Math.pow(2, attempt);
|
|
176
|
+
const jitter = Math.random() * this.retryConfig.jitterMs;
|
|
177
|
+
const delayWithJitter = baseDelay + jitter;
|
|
178
|
+
return Math.min(delayWithJitter, this.retryConfig.maxDelayMs);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Sleep for specified duration
|
|
182
|
+
*/
|
|
183
|
+
sleep(ms) {
|
|
184
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Execute function with concurrency limit
|
|
188
|
+
* Queues requests when at max concurrency, releases from queue on completion
|
|
189
|
+
*
|
|
190
|
+
* @param fn - Async function to execute
|
|
191
|
+
* @returns Promise resolving to function result
|
|
192
|
+
*/
|
|
193
|
+
async executeWithConcurrencyLimit(fn) {
|
|
194
|
+
// If under limit, execute immediately
|
|
195
|
+
if (this.activeRequests < this.MAX_CONCURRENT_REQUESTS) {
|
|
196
|
+
this.activeRequests++;
|
|
197
|
+
try {
|
|
198
|
+
return await fn();
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
this.activeRequests--;
|
|
202
|
+
this.processQueue();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// At limit, queue the request
|
|
206
|
+
return new Promise((resolve, reject) => {
|
|
207
|
+
const queuedFn = async () => {
|
|
208
|
+
this.activeRequests++;
|
|
209
|
+
try {
|
|
210
|
+
const result = await fn();
|
|
211
|
+
resolve(result);
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
reject(error);
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
this.activeRequests--;
|
|
218
|
+
this.processQueue();
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
this.requestQueue.push(queuedFn);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Process queued requests, executing them in FIFO order
|
|
226
|
+
*/
|
|
227
|
+
processQueue() {
|
|
228
|
+
// Only process if we have capacity and pending requests
|
|
229
|
+
if (this.activeRequests >= this.MAX_CONCURRENT_REQUESTS || this.requestQueue.length === 0) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const nextFn = this.requestQueue.shift();
|
|
233
|
+
if (nextFn) {
|
|
234
|
+
// Execute without awaiting to allow further queue processing
|
|
235
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
236
|
+
nextFn().catch((error) => {
|
|
237
|
+
this.emit('queue:error', { error, queueLength: this.requestQueue.length });
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Build HTTP headers with auth and session info
|
|
243
|
+
*/
|
|
244
|
+
buildHeaders() {
|
|
245
|
+
const headers = {
|
|
246
|
+
'Accept': 'application/json, text/event-stream',
|
|
247
|
+
'Content-Type': 'application/json',
|
|
248
|
+
'MCP-Protocol-Version': '2025-06-18',
|
|
249
|
+
};
|
|
250
|
+
// Add session header
|
|
251
|
+
if (this.session?.id) {
|
|
252
|
+
const sessionHeaderName = this.config.session?.headerName || 'Mcp-Session-Id';
|
|
253
|
+
headers[sessionHeaderName] = this.session.id;
|
|
254
|
+
}
|
|
255
|
+
// Add Last-Event-ID for stream resumption
|
|
256
|
+
if (this.session?.lastEventId) {
|
|
257
|
+
headers['Last-Event-ID'] = String(this.session.lastEventId);
|
|
258
|
+
}
|
|
259
|
+
// Add authentication
|
|
260
|
+
if (this.config.auth?.token) {
|
|
261
|
+
const token = this.resolveSecret(this.config.auth.token);
|
|
262
|
+
const headerName = this.config.auth.headerName || 'Authorization';
|
|
263
|
+
if (this.config.auth.type === 'bearer') {
|
|
264
|
+
headers[headerName] = `Bearer ${token}`;
|
|
265
|
+
}
|
|
266
|
+
else if (this.config.auth.type === 'api-key') {
|
|
267
|
+
headers[headerName] = token;
|
|
268
|
+
}
|
|
269
|
+
else if (this.config.auth.type === 'oauth2') {
|
|
270
|
+
headers[headerName] = `Bearer ${token}`;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// Add CORS headers if configured
|
|
274
|
+
if (this.config.cors?.enabled) {
|
|
275
|
+
headers['Origin'] = this.config.cors.allowOrigin || '*';
|
|
276
|
+
}
|
|
277
|
+
return headers;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Resolve ${secret:name} syntax to actual values
|
|
281
|
+
* (In production, this would fetch from secure storage)
|
|
282
|
+
*/
|
|
283
|
+
resolveSecret(value) {
|
|
284
|
+
const match = value.match(/\$\{secret:([^}]+)\}/);
|
|
285
|
+
if (match) {
|
|
286
|
+
const secretName = match[1];
|
|
287
|
+
return process.env[`METALINK_SECRET_${secretName}`] || value;
|
|
288
|
+
}
|
|
289
|
+
return value;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Execute a tool via HTTP call with concurrency limit
|
|
293
|
+
*/
|
|
294
|
+
async call(method, params) {
|
|
295
|
+
// P1 FIX: Check circuit breaker before request
|
|
296
|
+
if (this.circuitBreaker) {
|
|
297
|
+
if (!this.circuitBreaker.canExecute()) {
|
|
298
|
+
const state = this.circuitBreaker.getState();
|
|
299
|
+
throw new Error(`Circuit breaker is ${state} for ${this.config.url}. ` +
|
|
300
|
+
`Requests temporarily blocked to failing server.`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (!this.isConnected && !this.isConnecting) {
|
|
304
|
+
await this.connect();
|
|
305
|
+
}
|
|
306
|
+
// Execute with concurrency limit
|
|
307
|
+
return this.executeWithConcurrencyLimit(async () => {
|
|
308
|
+
const requestId = this.requestId++;
|
|
309
|
+
const request = {
|
|
310
|
+
jsonrpc: '2.0',
|
|
311
|
+
method,
|
|
312
|
+
params,
|
|
313
|
+
id: requestId,
|
|
314
|
+
};
|
|
315
|
+
return new Promise((resolve, reject) => {
|
|
316
|
+
// Set up timeout
|
|
317
|
+
const timeout = setTimeout(() => {
|
|
318
|
+
this.pendingRequests.delete(requestId);
|
|
319
|
+
reject(new Error(`Request timeout after ${this.config.requestTimeoutMs || 30000}ms for ${method} (ID: ${requestId})`));
|
|
320
|
+
}, this.config.requestTimeoutMs || 30000);
|
|
321
|
+
// Store pending request
|
|
322
|
+
this.pendingRequests.set(requestId, { resolve, reject, timeout });
|
|
323
|
+
// Send request
|
|
324
|
+
this.sendRequest(request).catch(err => {
|
|
325
|
+
clearTimeout(timeout);
|
|
326
|
+
this.pendingRequests.delete(requestId);
|
|
327
|
+
reject(err);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Send HTTP request to server with exponential backoff retry
|
|
334
|
+
* Includes automatic session reinitialize on 404 session errors (MCP spec compliance)
|
|
335
|
+
*
|
|
336
|
+
* @param request - The MCP request to send
|
|
337
|
+
* @param sessionReinitAttempted - Flag to prevent infinite reinitialize loops (max 1 per request)
|
|
338
|
+
*/
|
|
339
|
+
async sendRequest(request, sessionReinitAttempted = false) {
|
|
340
|
+
const headers = this.buildHeaders();
|
|
341
|
+
let lastError = null;
|
|
342
|
+
const requestId = request.id || 'notification';
|
|
343
|
+
// Retry loop with exponential backoff
|
|
344
|
+
for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
|
|
345
|
+
// P1 FIX: Create AbortController for this request
|
|
346
|
+
const abortController = new AbortController();
|
|
347
|
+
this.activeStreams.set(requestId, abortController);
|
|
348
|
+
// P1 FIX: Set stream timeout
|
|
349
|
+
const streamTimeout = setTimeout(() => {
|
|
350
|
+
abortController.abort();
|
|
351
|
+
this.activeStreams.delete(requestId);
|
|
352
|
+
}, this.config.streamTimeoutMs || 120000);
|
|
353
|
+
this.streamTimeouts.set(requestId, streamTimeout);
|
|
354
|
+
try {
|
|
355
|
+
const response = await undiciFetch(this.config.url, {
|
|
356
|
+
method: 'POST',
|
|
357
|
+
headers,
|
|
358
|
+
body: JSON.stringify(request),
|
|
359
|
+
dispatcher: this.agent || undefined,
|
|
360
|
+
signal: abortController.signal,
|
|
361
|
+
});
|
|
362
|
+
// Success: process response
|
|
363
|
+
if (response.ok) {
|
|
364
|
+
// Extract session ID from response headers (MCP servers return this)
|
|
365
|
+
const serverSessionId = response.headers.get('mcp-session-id') || response.headers.get('Mcp-Session-Id');
|
|
366
|
+
if (serverSessionId && this.session) {
|
|
367
|
+
// Use the server's session ID instead of our generated one
|
|
368
|
+
this.session.id = serverSessionId;
|
|
369
|
+
}
|
|
370
|
+
// Update session activity
|
|
371
|
+
if (this.session) {
|
|
372
|
+
this.session.lastActivity = Date.now();
|
|
373
|
+
}
|
|
374
|
+
// Handle response based on transport type
|
|
375
|
+
if (this.config.transport === 'sse' || this.config.transport === 'sse-first') {
|
|
376
|
+
await this.handleSSEResponse(response);
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
// http or http-stream
|
|
380
|
+
await this.handleStreamResponse(response);
|
|
381
|
+
}
|
|
382
|
+
return; // Success - exit retry loop
|
|
383
|
+
}
|
|
384
|
+
// Non-retryable status code: check for special cases first
|
|
385
|
+
if (!this.isRetryable(response)) {
|
|
386
|
+
// Special handling for 404: check if it's a session-related error
|
|
387
|
+
// Per MCP spec, client MUST reinitialize when receiving 404 for invalid session
|
|
388
|
+
if (response.status === 404 && !sessionReinitAttempted) {
|
|
389
|
+
const responseBody = await response.text();
|
|
390
|
+
const isSessionError = SESSION_ERROR_PATTERNS.some(pattern => responseBody.includes(pattern));
|
|
391
|
+
if (isSessionError) {
|
|
392
|
+
const expiredSessionId = this.session?.id;
|
|
393
|
+
this.emit('session:expired', {
|
|
394
|
+
sessionId: expiredSessionId,
|
|
395
|
+
responseBody,
|
|
396
|
+
});
|
|
397
|
+
// Clear session and reinitialize
|
|
398
|
+
this.session = null;
|
|
399
|
+
this.isConnected = false;
|
|
400
|
+
this.isConnecting = false;
|
|
401
|
+
// Reinitialize session
|
|
402
|
+
this.initializeSession();
|
|
403
|
+
// Reconnect (sends initialize request)
|
|
404
|
+
await this.connect();
|
|
405
|
+
// Retry the original request with new session (only once)
|
|
406
|
+
// Note: session is guaranteed to exist after connect() succeeds
|
|
407
|
+
const newSession = this.session;
|
|
408
|
+
this.emit('session:reinitialized', {
|
|
409
|
+
newSessionId: newSession?.id,
|
|
410
|
+
originalRequest: request.method,
|
|
411
|
+
});
|
|
412
|
+
// Retry with flag set to prevent infinite loops
|
|
413
|
+
return this.sendRequest(request, true);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
417
|
+
error.statusCode = response.status;
|
|
418
|
+
throw error;
|
|
419
|
+
}
|
|
420
|
+
// Retryable status code - will retry below
|
|
421
|
+
lastError = response;
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
425
|
+
// Handle AbortError specifically
|
|
426
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
427
|
+
lastError = new Error(`Stream cancelled/timeout for ${this.config.url}`);
|
|
428
|
+
}
|
|
429
|
+
// Non-retryable error: throw immediately
|
|
430
|
+
if (!this.isRetryable(lastError)) {
|
|
431
|
+
throw lastError;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
finally {
|
|
435
|
+
// P1 FIX: Always clean up stream resources after each attempt
|
|
436
|
+
clearTimeout(this.streamTimeouts.get(requestId));
|
|
437
|
+
this.streamTimeouts.delete(requestId);
|
|
438
|
+
this.activeStreams.delete(requestId);
|
|
439
|
+
}
|
|
440
|
+
// If this was the last attempt, throw
|
|
441
|
+
if (attempt === this.retryConfig.maxRetries) {
|
|
442
|
+
const message = lastError instanceof Response
|
|
443
|
+
? `HTTP ${lastError.status} after ${this.retryConfig.maxRetries} retries`
|
|
444
|
+
: lastError instanceof Error
|
|
445
|
+
? lastError.message
|
|
446
|
+
: 'Unknown error';
|
|
447
|
+
throw new Error(`Failed to call ${this.config.url}: ${message}`);
|
|
448
|
+
}
|
|
449
|
+
// Calculate backoff and wait before retry
|
|
450
|
+
const delayMs = this.calculateBackoffDelay(attempt);
|
|
451
|
+
this.emit('retry', {
|
|
452
|
+
attempt: attempt + 1,
|
|
453
|
+
maxRetries: this.retryConfig.maxRetries,
|
|
454
|
+
delayMs,
|
|
455
|
+
error: lastError,
|
|
456
|
+
});
|
|
457
|
+
await this.sleep(delayMs);
|
|
458
|
+
}
|
|
459
|
+
// Should not reach here, but satisfy TypeScript
|
|
460
|
+
throw lastError || new Error('Unknown error in retry loop');
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Handle SSE (Server-Sent Events) streaming response
|
|
464
|
+
*/
|
|
465
|
+
async handleSSEResponse(response) {
|
|
466
|
+
const reader = response.body?.getReader();
|
|
467
|
+
if (!reader) {
|
|
468
|
+
throw new Error('No response body for SSE stream');
|
|
469
|
+
}
|
|
470
|
+
const decoder = new TextDecoder();
|
|
471
|
+
try {
|
|
472
|
+
while (true) {
|
|
473
|
+
const { done, value } = await reader.read();
|
|
474
|
+
if (done)
|
|
475
|
+
break;
|
|
476
|
+
const text = decoder.decode(value, { stream: true });
|
|
477
|
+
this.sseEventBuffer += text;
|
|
478
|
+
// Process complete events in buffer
|
|
479
|
+
const lines = this.sseEventBuffer.split('\n');
|
|
480
|
+
this.sseEventBuffer = lines.pop() || ''; // Keep incomplete line
|
|
481
|
+
let currentEvent = {};
|
|
482
|
+
for (const line of lines) {
|
|
483
|
+
if (line === '') {
|
|
484
|
+
// Empty line = event boundary
|
|
485
|
+
if (currentEvent.data) {
|
|
486
|
+
this.handleSSEEvent(currentEvent);
|
|
487
|
+
}
|
|
488
|
+
currentEvent = {};
|
|
489
|
+
}
|
|
490
|
+
else if (line.startsWith('id: ')) {
|
|
491
|
+
currentEvent.id = line.slice(4).trim();
|
|
492
|
+
}
|
|
493
|
+
else if (line.startsWith('data: ')) {
|
|
494
|
+
currentEvent.data = line.slice(6);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
finally {
|
|
500
|
+
reader.releaseLock();
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Handle individual SSE event
|
|
505
|
+
*/
|
|
506
|
+
handleSSEEvent(event) {
|
|
507
|
+
if (!event.data)
|
|
508
|
+
return;
|
|
509
|
+
// Update last event ID for resumption
|
|
510
|
+
if (event.id && this.session) {
|
|
511
|
+
this.session.lastEventId = parseInt(event.id, 10);
|
|
512
|
+
}
|
|
513
|
+
try {
|
|
514
|
+
const response = JSON.parse(event.data);
|
|
515
|
+
this.resolveRequest(response);
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
console.error('Failed to parse SSE data:', error);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Handle HTTP-stream response (batch of JSON objects)
|
|
523
|
+
*/
|
|
524
|
+
async handleStreamResponse(response) {
|
|
525
|
+
const text = await response.text();
|
|
526
|
+
const lines = text.trim().split('\n');
|
|
527
|
+
for (const line of lines) {
|
|
528
|
+
if (!line.trim())
|
|
529
|
+
continue;
|
|
530
|
+
try {
|
|
531
|
+
const response = JSON.parse(line);
|
|
532
|
+
this.resolveRequest(response);
|
|
533
|
+
}
|
|
534
|
+
catch (error) {
|
|
535
|
+
console.error('Failed to parse stream response:', error);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Resolve pending request with response
|
|
541
|
+
*/
|
|
542
|
+
resolveRequest(response) {
|
|
543
|
+
const requestId = response.id;
|
|
544
|
+
if (requestId === null || requestId === undefined) {
|
|
545
|
+
// Notification (no response expected)
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
const pending = this.pendingRequests.get(requestId);
|
|
549
|
+
if (!pending) {
|
|
550
|
+
console.warn(`Received response for unknown request ID: ${requestId}`);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
this.pendingRequests.delete(requestId);
|
|
554
|
+
clearTimeout(pending.timeout);
|
|
555
|
+
if (response.error) {
|
|
556
|
+
// P1 FIX: Record failure in circuit breaker
|
|
557
|
+
if (this.circuitBreaker) {
|
|
558
|
+
this.circuitBreaker.recordFailure();
|
|
559
|
+
}
|
|
560
|
+
pending.reject(new Error(`RPC Error (${response.error.code}): ${response.error.message}`));
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
// P1 FIX: Record success in circuit breaker
|
|
564
|
+
if (this.circuitBreaker) {
|
|
565
|
+
this.circuitBreaker.recordSuccess();
|
|
566
|
+
}
|
|
567
|
+
pending.resolve(response);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Connect to HTTP server
|
|
572
|
+
*/
|
|
573
|
+
async connect() {
|
|
574
|
+
if (this.isConnected || this.isConnecting)
|
|
575
|
+
return;
|
|
576
|
+
this.isConnecting = true;
|
|
577
|
+
try {
|
|
578
|
+
// Send initialize request to validate connection
|
|
579
|
+
const response = await this.call('initialize', {
|
|
580
|
+
protocolVersion: '2025-06-18',
|
|
581
|
+
capabilities: {},
|
|
582
|
+
clientInfo: {
|
|
583
|
+
name: 'metalink-http-client',
|
|
584
|
+
version: '1.0.0',
|
|
585
|
+
},
|
|
586
|
+
});
|
|
587
|
+
if (!response.result) {
|
|
588
|
+
throw new Error('Initialize response missing result');
|
|
589
|
+
}
|
|
590
|
+
this.isConnected = true;
|
|
591
|
+
this.isConnecting = false;
|
|
592
|
+
this.emit('connected', { session: this.session });
|
|
593
|
+
}
|
|
594
|
+
catch (error) {
|
|
595
|
+
this.isConnecting = false;
|
|
596
|
+
this.isConnected = false;
|
|
597
|
+
throw error;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Disconnect from HTTP server
|
|
602
|
+
*/
|
|
603
|
+
async disconnect() {
|
|
604
|
+
this.isConnected = false;
|
|
605
|
+
// P1 FIX: Cancel all active streams
|
|
606
|
+
for (const [requestId, controller] of this.activeStreams) {
|
|
607
|
+
controller.abort();
|
|
608
|
+
clearTimeout(this.streamTimeouts.get(requestId));
|
|
609
|
+
}
|
|
610
|
+
this.activeStreams.clear();
|
|
611
|
+
this.streamTimeouts.clear();
|
|
612
|
+
// Destroy agent and close all connections
|
|
613
|
+
if (this.agent) {
|
|
614
|
+
await this.agent.close();
|
|
615
|
+
this.agent = null;
|
|
616
|
+
this.emit('agent:destroyed', { serverName: this.config.name });
|
|
617
|
+
}
|
|
618
|
+
// Cancel all pending requests
|
|
619
|
+
for (const [, pending] of this.pendingRequests) {
|
|
620
|
+
clearTimeout(pending.timeout);
|
|
621
|
+
pending.reject(new Error('Disconnected'));
|
|
622
|
+
}
|
|
623
|
+
this.pendingRequests.clear();
|
|
624
|
+
// Clear queued requests
|
|
625
|
+
this.requestQueue = [];
|
|
626
|
+
this.activeRequests = 0;
|
|
627
|
+
// Send notifications for any open sessions
|
|
628
|
+
this.emit('disconnected');
|
|
629
|
+
// P1 FIX: Clean up all event listeners to prevent memory leaks
|
|
630
|
+
const eventNames = this.eventNames();
|
|
631
|
+
for (const event of eventNames) {
|
|
632
|
+
this.removeAllListeners(event);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Health check - ping the server
|
|
637
|
+
*/
|
|
638
|
+
async healthCheck() {
|
|
639
|
+
try {
|
|
640
|
+
// Try to call a simple method that servers typically support
|
|
641
|
+
const response = await this.call('ping');
|
|
642
|
+
return !!response.result || !response.error;
|
|
643
|
+
}
|
|
644
|
+
catch {
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Check if client is connected
|
|
650
|
+
*/
|
|
651
|
+
getConnected() {
|
|
652
|
+
return this.isConnected;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Get session info
|
|
656
|
+
*/
|
|
657
|
+
getSession() {
|
|
658
|
+
return this.session;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Get concurrency metrics for monitoring
|
|
662
|
+
*/
|
|
663
|
+
getConcurrencyMetrics() {
|
|
664
|
+
return {
|
|
665
|
+
activeRequests: this.activeRequests,
|
|
666
|
+
queuedRequests: this.requestQueue.length,
|
|
667
|
+
maxConcurrentRequests: this.MAX_CONCURRENT_REQUESTS,
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Collect connection pool metrics from agent
|
|
672
|
+
*/
|
|
673
|
+
collectPoolMetrics() {
|
|
674
|
+
if (!this.agent)
|
|
675
|
+
return;
|
|
676
|
+
const stats = this.agent.stats?.() || {};
|
|
677
|
+
this.poolMetrics = {
|
|
678
|
+
activeSockets: stats.connected || 0,
|
|
679
|
+
freeSockets: stats.free || 0,
|
|
680
|
+
pendingRequests: stats.pending || 0,
|
|
681
|
+
totalConnections: (stats.connected || 0) + (stats.free || 0),
|
|
682
|
+
};
|
|
683
|
+
this.emit('pool:metrics', {
|
|
684
|
+
serverName: this.config.name,
|
|
685
|
+
metrics: this.poolMetrics,
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Get connection pool metrics
|
|
690
|
+
*/
|
|
691
|
+
getPoolMetrics() {
|
|
692
|
+
this.collectPoolMetrics();
|
|
693
|
+
return { ...this.poolMetrics };
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Factory for creating HTTP clients from config
|
|
698
|
+
* @param config - HTTP server configuration
|
|
699
|
+
* @param circuitBreaker - Optional circuit breaker for resilience
|
|
700
|
+
*/
|
|
701
|
+
export function createHttpClient(config, circuitBreaker) {
|
|
702
|
+
return new HttpClient(config, circuitBreaker);
|
|
703
|
+
}
|
|
704
|
+
//# sourceMappingURL=http-client.js.map
|