This is page 3 of 10. Use http://codebase.md/jakedismo/master-mcp-server?format=md&lines=true&page={x} to view the full context.

# Directory Structure

```
├── .env.example
├── .eslintignore
├── .eslintrc.cjs
├── .eslintrc.js
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .prettierrc.json
├── CHANGELOG.md
├── config
│   ├── default.json
│   ├── development.json
│   ├── production.json
│   └── schema.json
├── config.json
├── CONTRIBUTING.md
├── debug-stdio.cjs
├── debug-stdio.js
├── deploy
│   ├── cloudflare
│   │   ├── .gitkeep
│   │   ├── README.md
│   │   └── wrangler.toml
│   ├── docker
│   │   ├── .gitkeep
│   │   ├── docker-compose.yml
│   │   ├── Dockerfile
│   │   └── entrypoint.sh
│   ├── koyeb
│   │   ├── .gitkeep
│   │   └── koyeb.yaml
│   └── README.md
├── docker-compose.yml
├── Dockerfile
├── docs
│   ├── .DS_Store
│   ├── .vitepress
│   │   ├── cache
│   │   │   └── deps
│   │   │       ├── _metadata.json
│   │   │       ├── chunk-HVR2FF6M.js
│   │   │       ├── chunk-HVR2FF6M.js.map
│   │   │       ├── chunk-P2XGSYO7.js
│   │   │       ├── chunk-P2XGSYO7.js.map
│   │   │       ├── package.json
│   │   │       ├── vitepress___@vue_devtools-api.js
│   │   │       ├── vitepress___@vue_devtools-api.js.map
│   │   │       ├── vitepress___@vueuse_core.js
│   │   │       ├── vitepress___@vueuse_core.js.map
│   │   │       ├── vitepress___@vueuse_integrations_useFocusTrap.js
│   │   │       ├── vitepress___@vueuse_integrations_useFocusTrap.js.map
│   │   │       ├── vitepress___mark__js_src_vanilla__js.js
│   │   │       ├── vitepress___mark__js_src_vanilla__js.js.map
│   │   │       ├── vitepress___minisearch.js
│   │   │       ├── vitepress___minisearch.js.map
│   │   │       ├── vue.js
│   │   │       └── vue.js.map
│   │   ├── config.ts
│   │   ├── dist
│   │   │   ├── 404.html
│   │   │   ├── advanced
│   │   │   │   ├── extensibility.html
│   │   │   │   ├── index.html
│   │   │   │   ├── monitoring.html
│   │   │   │   ├── performance.html
│   │   │   │   └── security.html
│   │   │   ├── api
│   │   │   │   ├── index.html
│   │   │   │   └── README.html
│   │   │   ├── assets
│   │   │   │   ├── advanced_extensibility.md.TrXUn5w5.js
│   │   │   │   ├── advanced_extensibility.md.TrXUn5w5.lean.js
│   │   │   │   ├── advanced_index.md.CPcpUlw_.js
│   │   │   │   ├── advanced_index.md.CPcpUlw_.lean.js
│   │   │   │   ├── advanced_monitoring.md.DTybdNg-.js
│   │   │   │   ├── advanced_monitoring.md.DTybdNg-.lean.js
│   │   │   │   ├── advanced_performance.md.DKmzK0ia.js
│   │   │   │   ├── advanced_performance.md.DKmzK0ia.lean.js
│   │   │   │   ├── advanced_security.md.B-oBD7IB.js
│   │   │   │   ├── advanced_security.md.B-oBD7IB.lean.js
│   │   │   │   ├── api_index.md.Dl1JB08_.js
│   │   │   │   ├── api_index.md.Dl1JB08_.lean.js
│   │   │   │   ├── chunks
│   │   │   │   │   └── framework.CHl2ywxc.js
│   │   │   │   ├── configuration_environment-variables.md.Ddy3P_Wz.js
│   │   │   │   ├── configuration_environment-variables.md.Ddy3P_Wz.lean.js
│   │   │   │   ├── configuration_environment.md.DxcTQ623.js
│   │   │   │   ├── configuration_environment.md.DxcTQ623.lean.js
│   │   │   │   ├── configuration_overview.md.DIkVDv7V.js
│   │   │   │   ├── configuration_overview.md.DIkVDv7V.lean.js
│   │   │   │   ├── configuration_performance.md.DbJdmLrW.js
│   │   │   │   ├── configuration_performance.md.DbJdmLrW.lean.js
│   │   │   │   ├── configuration_reference.md.27IKWqtk.js
│   │   │   │   ├── configuration_reference.md.27IKWqtk.lean.js
│   │   │   │   ├── configuration_security.md.-OOlkzN4.js
│   │   │   │   ├── configuration_security.md.-OOlkzN4.lean.js
│   │   │   │   ├── contributing_dev-setup.md.Ceqh4w-R.js
│   │   │   │   ├── contributing_dev-setup.md.Ceqh4w-R.lean.js
│   │   │   │   ├── contributing_guidelines.md.ZEAX2yVh.js
│   │   │   │   ├── contributing_guidelines.md.ZEAX2yVh.lean.js
│   │   │   │   ├── contributing_index.md.DYq9R6wr.js
│   │   │   │   ├── contributing_index.md.DYq9R6wr.lean.js
│   │   │   │   ├── contributing_maintenance.md.k2bR0IaR.js
│   │   │   │   ├── contributing_maintenance.md.k2bR0IaR.lean.js
│   │   │   │   ├── deployment_cicd.md.Ci2T0UYC.js
│   │   │   │   ├── deployment_cicd.md.Ci2T0UYC.lean.js
│   │   │   │   ├── deployment_cloudflare-workers.md.D2WHsfep.js
│   │   │   │   ├── deployment_cloudflare-workers.md.D2WHsfep.lean.js
│   │   │   │   ├── deployment_docker.md.B8bQDQTo.js
│   │   │   │   ├── deployment_docker.md.B8bQDQTo.lean.js
│   │   │   │   ├── deployment_index.md.ClYeOkpy.js
│   │   │   │   ├── deployment_index.md.ClYeOkpy.lean.js
│   │   │   │   ├── deployment_koyeb.md.B_wJhvF7.js
│   │   │   │   ├── deployment_koyeb.md.B_wJhvF7.lean.js
│   │   │   │   ├── examples_advanced-routing.md.B3CqhLZ7.js
│   │   │   │   ├── examples_advanced-routing.md.B3CqhLZ7.lean.js
│   │   │   │   ├── examples_basic-node.md.CaDZzGlO.js
│   │   │   │   ├── examples_basic-node.md.CaDZzGlO.lean.js
│   │   │   │   ├── examples_cloudflare-worker.md.DwVSz-c7.js
│   │   │   │   ├── examples_cloudflare-worker.md.DwVSz-c7.lean.js
│   │   │   │   ├── examples_index.md.CBF_BLkl.js
│   │   │   │   ├── examples_index.md.CBF_BLkl.lean.js
│   │   │   │   ├── examples_oauth-delegation.md.1hZxoqDl.js
│   │   │   │   ├── examples_oauth-delegation.md.1hZxoqDl.lean.js
│   │   │   │   ├── examples_overview.md.CZN0JbZ7.js
│   │   │   │   ├── examples_overview.md.CZN0JbZ7.lean.js
│   │   │   │   ├── examples_testing.md.Dek4GpNs.js
│   │   │   │   ├── examples_testing.md.Dek4GpNs.lean.js
│   │   │   │   ├── getting-started_concepts.md.D7ON9iGB.js
│   │   │   │   ├── getting-started_concepts.md.D7ON9iGB.lean.js
│   │   │   │   ├── getting-started_installation.md.BKnVqAGg.js
│   │   │   │   ├── getting-started_installation.md.BKnVqAGg.lean.js
│   │   │   │   ├── getting-started_overview.md.DvJDFL2N.js
│   │   │   │   ├── getting-started_overview.md.DvJDFL2N.lean.js
│   │   │   │   ├── getting-started_quickstart-node.md.GOO4aGas.js
│   │   │   │   ├── getting-started_quickstart-node.md.GOO4aGas.lean.js
│   │   │   │   ├── getting-started_quickstart-workers.md.Cpofh8Mj.js
│   │   │   │   ├── getting-started_quickstart-workers.md.Cpofh8Mj.lean.js
│   │   │   │   ├── getting-started.md.DG9ndneo.js
│   │   │   │   ├── getting-started.md.DG9ndneo.lean.js
│   │   │   │   ├── guides_configuration-management.md.B-jwYMbA.js
│   │   │   │   ├── guides_configuration-management.md.B-jwYMbA.lean.js
│   │   │   │   ├── guides_configuration.md.Ci3zYDFA.js
│   │   │   │   ├── guides_configuration.md.Ci3zYDFA.lean.js
│   │   │   │   ├── guides_index.md.CIlq2fmx.js
│   │   │   │   ├── guides_index.md.CIlq2fmx.lean.js
│   │   │   │   ├── guides_module-loading.md.BkJvuRnQ.js
│   │   │   │   ├── guides_module-loading.md.BkJvuRnQ.lean.js
│   │   │   │   ├── guides_oauth-delegation.md.DEOZ-_G0.js
│   │   │   │   ├── guides_oauth-delegation.md.DEOZ-_G0.lean.js
│   │   │   │   ├── guides_request-routing.md.Bdzf0VLg.js
│   │   │   │   ├── guides_request-routing.md.Bdzf0VLg.lean.js
│   │   │   │   ├── guides_testing.md.kYfHqJLu.js
│   │   │   │   ├── guides_testing.md.kYfHqJLu.lean.js
│   │   │   │   ├── inter-italic-cyrillic-ext.r48I6akx.woff2
│   │   │   │   ├── inter-italic-cyrillic.By2_1cv3.woff2
│   │   │   │   ├── inter-italic-greek-ext.1u6EdAuj.woff2
│   │   │   │   ├── inter-italic-greek.DJ8dCoTZ.woff2
│   │   │   │   ├── inter-italic-latin-ext.CN1xVJS-.woff2
│   │   │   │   ├── inter-italic-latin.C2AdPX0b.woff2
│   │   │   │   ├── inter-italic-vietnamese.BSbpV94h.woff2
│   │   │   │   ├── inter-roman-cyrillic-ext.BBPuwvHQ.woff2
│   │   │   │   ├── inter-roman-cyrillic.C5lxZ8CY.woff2
│   │   │   │   ├── inter-roman-greek-ext.CqjqNYQ-.woff2
│   │   │   │   ├── inter-roman-greek.BBVDIX6e.woff2
│   │   │   │   ├── inter-roman-latin-ext.4ZJIpNVo.woff2
│   │   │   │   ├── inter-roman-latin.Di8DUHzh.woff2
│   │   │   │   ├── inter-roman-vietnamese.BjW4sHH5.woff2
│   │   │   │   ├── README.md.BO5r5M9u.js
│   │   │   │   ├── README.md.BO5r5M9u.lean.js
│   │   │   │   ├── style.BQrfSMzK.css
│   │   │   │   ├── troubleshooting_common-issues.md.CScvzWM1.js
│   │   │   │   ├── troubleshooting_common-issues.md.CScvzWM1.lean.js
│   │   │   │   ├── troubleshooting_deployment.md.DUhpqnLE.js
│   │   │   │   ├── troubleshooting_deployment.md.DUhpqnLE.lean.js
│   │   │   │   ├── troubleshooting_errors.md.BSCsEmGc.js
│   │   │   │   ├── troubleshooting_errors.md.BSCsEmGc.lean.js
│   │   │   │   ├── troubleshooting_oauth.md.Cw60Eka3.js
│   │   │   │   ├── troubleshooting_oauth.md.Cw60Eka3.lean.js
│   │   │   │   ├── troubleshooting_performance.md.DxY6LJcT.js
│   │   │   │   ├── troubleshooting_performance.md.DxY6LJcT.lean.js
│   │   │   │   ├── troubleshooting_routing.md.BHN-MDhs.js
│   │   │   │   ├── troubleshooting_routing.md.BHN-MDhs.lean.js
│   │   │   │   ├── troubleshooting_security-best-practices.md.Yiu8E-zt.js
│   │   │   │   ├── troubleshooting_security-best-practices.md.Yiu8E-zt.lean.js
│   │   │   │   ├── tutorials_beginner-getting-started.md.BXObgobW.js
│   │   │   │   ├── tutorials_beginner-getting-started.md.BXObgobW.lean.js
│   │   │   │   ├── tutorials_cloudflare-workers-tutorial.md.MPHsc0aT.js
│   │   │   │   ├── tutorials_cloudflare-workers-tutorial.md.MPHsc0aT.lean.js
│   │   │   │   ├── tutorials_load-balancing-and-resilience.md.Dv9r9jyW.js
│   │   │   │   ├── tutorials_load-balancing-and-resilience.md.Dv9r9jyW.lean.js
│   │   │   │   ├── tutorials_oauth-delegation-github.md.Nq4glqCe.js
│   │   │   │   └── tutorials_oauth-delegation-github.md.Nq4glqCe.lean.js
│   │   │   ├── configuration
│   │   │   │   ├── environment-variables.html
│   │   │   │   ├── environment.html
│   │   │   │   ├── examples.html
│   │   │   │   ├── overview.html
│   │   │   │   ├── performance.html
│   │   │   │   ├── reference.html
│   │   │   │   └── security.html
│   │   │   ├── contributing
│   │   │   │   ├── dev-setup.html
│   │   │   │   ├── guidelines.html
│   │   │   │   ├── index.html
│   │   │   │   └── maintenance.html
│   │   │   ├── deployment
│   │   │   │   ├── cicd.html
│   │   │   │   ├── cloudflare-workers.html
│   │   │   │   ├── docker.html
│   │   │   │   ├── index.html
│   │   │   │   └── koyeb.html
│   │   │   ├── diagrams
│   │   │   │   └── architecture.svg
│   │   │   ├── examples
│   │   │   │   ├── advanced-routing.html
│   │   │   │   ├── basic-node.html
│   │   │   │   ├── cloudflare-worker.html
│   │   │   │   ├── index.html
│   │   │   │   ├── oauth-delegation.html
│   │   │   │   ├── overview.html
│   │   │   │   └── testing.html
│   │   │   ├── getting-started
│   │   │   │   ├── concepts.html
│   │   │   │   ├── installation.html
│   │   │   │   ├── overview.html
│   │   │   │   ├── quick-start.html
│   │   │   │   ├── quickstart-node.html
│   │   │   │   └── quickstart-workers.html
│   │   │   ├── getting-started.html
│   │   │   ├── guides
│   │   │   │   ├── authentication.html
│   │   │   │   ├── client-integration.html
│   │   │   │   ├── configuration-management.html
│   │   │   │   ├── configuration.html
│   │   │   │   ├── index.html
│   │   │   │   ├── module-loading.html
│   │   │   │   ├── oauth-delegation.html
│   │   │   │   ├── request-routing.html
│   │   │   │   ├── server-management.html
│   │   │   │   ├── server-sharing.html
│   │   │   │   └── testing.html
│   │   │   ├── hashmap.json
│   │   │   ├── index.html
│   │   │   ├── logo.svg
│   │   │   ├── README.html
│   │   │   ├── reports
│   │   │   │   └── mcp-compliance-audit.html
│   │   │   ├── troubleshooting
│   │   │   │   ├── common-issues.html
│   │   │   │   ├── deployment.html
│   │   │   │   ├── errors.html
│   │   │   │   ├── index.html
│   │   │   │   ├── oauth.html
│   │   │   │   ├── performance.html
│   │   │   │   ├── routing.html
│   │   │   │   └── security-best-practices.html
│   │   │   ├── tutorials
│   │   │   │   ├── beginner-getting-started.html
│   │   │   │   ├── cloudflare-workers-tutorial.html
│   │   │   │   ├── load-balancing-and-resilience.html
│   │   │   │   └── oauth-delegation-github.html
│   │   │   └── vp-icons.css
│   │   └── theme
│   │       ├── components
│   │       │   ├── ApiPlayground.vue
│   │       │   ├── AuthFlowDemo.vue
│   │       │   ├── CodeTabs.vue
│   │       │   └── ConfigGenerator.vue
│   │       ├── index.ts
│   │       └── style.css
│   ├── advanced
│   │   ├── extensibility.md
│   │   ├── index.md
│   │   ├── monitoring.md
│   │   ├── performance.md
│   │   └── security.md
│   ├── api
│   │   ├── functions
│   │   │   └── createServer.md
│   │   ├── index.md
│   │   ├── interfaces
│   │   │   └── RunningServer.md
│   │   └── README.md
│   ├── architecture
│   │   └── images
│   │       └── mcp_master_architecture.svg
│   ├── configuration
│   │   ├── environment-variables.md
│   │   ├── environment.md
│   │   ├── examples.md
│   │   ├── overview.md
│   │   ├── performance.md
│   │   ├── reference.md
│   │   └── security.md
│   ├── contributing
│   │   ├── dev-setup.md
│   │   ├── guidelines.md
│   │   ├── index.md
│   │   └── maintenance.md
│   ├── deployment
│   │   ├── cicd.md
│   │   ├── cloudflare-workers.md
│   │   ├── docker.md
│   │   ├── docs-site.md
│   │   ├── index.md
│   │   └── koyeb.md
│   ├── examples
│   │   ├── advanced-routing.md
│   │   ├── basic-node.md
│   │   ├── cloudflare-worker.md
│   │   ├── index.md
│   │   ├── oauth-delegation.md
│   │   ├── overview.md
│   │   └── testing.md
│   ├── getting-started
│   │   ├── concepts.md
│   │   ├── installation.md
│   │   ├── overview.md
│   │   ├── quick-start.md
│   │   ├── quickstart-node.md
│   │   └── quickstart-workers.md
│   ├── getting-started.md
│   ├── guides
│   │   ├── authentication.md
│   │   ├── client-integration.md
│   │   ├── configuration-management.md
│   │   ├── configuration.md
│   │   ├── index.md
│   │   ├── module-loading.md
│   │   ├── oauth-delegation.md
│   │   ├── request-routing.md
│   │   ├── server-management.md
│   │   ├── server-sharing.md
│   │   └── testing.md
│   ├── index.html
│   ├── public
│   │   ├── diagrams
│   │   │   └── architecture.svg
│   │   ├── github-social.png
│   │   │   └── image.png
│   │   ├── logo.png
│   │   └── logo.svg
│   ├── README.md
│   ├── stdio-servers.md
│   ├── testing
│   │   └── phase-9-testing-architecture.md
│   ├── troubleshooting
│   │   ├── common-issues.md
│   │   ├── deployment.md
│   │   ├── errors.md
│   │   ├── index.md
│   │   ├── oauth.md
│   │   ├── performance.md
│   │   ├── routing.md
│   │   └── security-best-practices.md
│   └── tutorials
│       ├── beginner-getting-started.md
│       ├── cloudflare-workers-tutorial.md
│       ├── load-balancing-and-resilience.md
│       └── oauth-delegation-github.md
├── examples
│   ├── advanced-routing
│   │   ├── config.yaml
│   │   └── README.md
│   ├── basic-node
│   │   ├── config.yaml
│   │   ├── README.md
│   │   └── server.ts
│   ├── cloudflare-worker
│   │   ├── README.md
│   │   └── worker.ts
│   ├── custom-auth
│   │   ├── config.yaml
│   │   ├── index.ts
│   │   └── README.md
│   ├── multi-server
│   │   ├── config.yaml
│   │   └── README.md
│   ├── oauth-delegation
│   │   └── README.md
│   ├── oauth-node
│   │   ├── config.yaml
│   │   └── README.md
│   ├── performance
│   │   ├── config.yaml
│   │   └── README.md
│   ├── sample-configs
│   │   ├── basic.yaml
│   │   └── simple-setup.yaml
│   ├── security-hardening
│   │   └── README.md
│   ├── stdio-mcp-server.cjs
│   ├── test-mcp-server.js
│   └── test-stdio-server.js
├── LICENSE
├── master-mcp-definition.md
├── package-lock.json
├── package.json
├── README.md
├── reports
│   └── claude_report_20250815_222153.html
├── scripts
│   └── generate-config-docs.ts
├── src
│   ├── auth
│   │   ├── multi-auth-manager.ts
│   │   ├── oauth-providers.ts
│   │   └── token-manager.ts
│   ├── config
│   │   ├── config-loader.ts
│   │   ├── environment-manager.ts
│   │   ├── schema-validator.ts
│   │   └── secret-manager.ts
│   ├── index.ts
│   ├── mcp-server.ts
│   ├── modules
│   │   ├── capability-aggregator.ts
│   │   ├── module-loader.ts
│   │   ├── request-router.ts
│   │   ├── stdio-capability-discovery.ts
│   │   └── stdio-manager.ts
│   ├── oauth
│   │   ├── callback-handler.ts
│   │   ├── flow-controller.ts
│   │   ├── flow-validator.ts
│   │   ├── pkce-manager.ts
│   │   ├── state-manager.ts
│   │   └── web-interface.ts
│   ├── routing
│   │   ├── circuit-breaker.ts
│   │   ├── load-balancer.ts
│   │   ├── retry-handler.ts
│   │   └── route-registry.ts
│   ├── runtime
│   │   ├── node.ts
│   │   └── worker.ts
│   ├── server
│   │   ├── config-manager.ts
│   │   ├── dependency-container.ts
│   │   ├── master-server.ts
│   │   └── protocol-handler.ts
│   ├── types
│   │   ├── auth.ts
│   │   ├── config.ts
│   │   ├── jose-shim.d.ts
│   │   ├── mcp.ts
│   │   └── server.ts
│   └── utils
│       ├── cache.ts
│       ├── crypto.ts
│       ├── dev.ts
│       ├── errors.ts
│       ├── http.ts
│       ├── logger.ts
│       ├── monitoring.ts
│       ├── string.ts
│       ├── time.ts
│       ├── validation.ts
│       └── validators.ts
├── static
│   └── oauth
│       ├── consent.html
│       ├── error.html
│       ├── script.js
│       ├── style.css
│       └── success.html
├── tests
│   ├── _setup
│   │   ├── miniflare.setup.ts
│   │   └── vitest.setup.ts
│   ├── _utils
│   │   ├── log-capture.ts
│   │   ├── mock-fetch.ts
│   │   └── test-server.ts
│   ├── .gitkeep
│   ├── e2e
│   │   ├── flow-controller.express.test.ts
│   │   └── flow-controller.worker.test.ts
│   ├── factories
│   │   ├── configFactory.ts
│   │   ├── mcpFactory.ts
│   │   └── oauthFactory.ts
│   ├── fixtures
│   │   ├── capabilities.json
│   │   └── stdio-server.js
│   ├── integration
│   │   ├── modules.capability-aggregator.test.ts
│   │   ├── modules.module-loader-health.test.ts
│   │   ├── oauth.callback-handler.test.ts
│   │   └── request-router.test.ts
│   ├── mocks
│   │   ├── mcp
│   │   │   └── fake-backend.ts
│   │   └── oauth
│   │       └── mock-oidc-provider.ts
│   ├── perf
│   │   ├── artillery
│   │   │   └── auth-routing.yaml
│   │   └── perf.auth-and-routing.test.ts
│   ├── security
│   │   └── security.oauth-and-input.test.ts
│   ├── servers
│   │   ├── test-auth-simple.js
│   │   ├── test-debug.js
│   │   ├── test-master-mcp.js
│   │   ├── test-mcp-client.js
│   │   ├── test-streaming-both-complete.js
│   │   ├── test-streaming-both-full.js
│   │   ├── test-streaming-both-simple.js
│   │   ├── test-streaming-both.js
│   │   └── test-streaming.js
│   ├── setup
│   │   └── test-setup.ts
│   ├── unit
│   │   ├── auth.multi-auth-manager.test.ts
│   │   ├── auth.token-manager.test.ts
│   │   ├── config.environment-manager.test.ts
│   │   ├── config.schema-validator.test.ts
│   │   ├── config.secret-manager.test.ts
│   │   ├── modules
│   │   │   ├── stdio-capability-discovery.test.ts
│   │   │   └── stdio-manager.test.ts
│   │   ├── modules.route-registry.test.ts
│   │   ├── oauth.pkce-state.test.ts
│   │   ├── routing
│   │   │   └── circuit-breaker.test.ts
│   │   ├── routing.core.test.ts
│   │   ├── stdio-capability-discovery.test.ts
│   │   ├── utils.crypto.test.ts
│   │   ├── utils.logger.test.ts
│   │   └── utils.monitoring.test.ts
│   └── utils
│       ├── fake-express.ts
│       ├── mock-http.ts
│       ├── oauth-mocks.ts
│       └── token-storages.ts
├── tsconfig.base.json
├── tsconfig.json
├── tsconfig.node.json
├── tsconfig.worker.json
├── typedoc.json
├── vitest.config.ts
└── vitest.worker.config.ts
```

# Files

--------------------------------------------------------------------------------
/src/modules/stdio-capability-discovery.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Logger } from '../utils/logger.js'
  2 | import type { ServerCapabilities } from '../types/server.js'
  3 | import { StdioManager } from './stdio-manager.js'
  4 | 
  5 | export class StdioCapabilityDiscovery {
  6 |   constructor(private stdioManager: StdioManager = new StdioManager()) {}
  7 | 
  8 |   async discoverCapabilities(serverId: string, filePath: string): Promise<ServerCapabilities> {
  9 |     Logger.info('Discovering capabilities for STDIO server', { serverId, filePath })
 10 |     
 11 |     try {
 12 |       // Start the STDIO server process
 13 |       await this.stdioManager.startServer(serverId, filePath)
 14 |       
 15 |       // Send initialize request
 16 |       const initializeRequestId = Date.now()
 17 |       const initializeRequest = {
 18 |         jsonrpc: "2.0",
 19 |         id: initializeRequestId,
 20 |         method: "initialize",
 21 |         params: {
 22 |           protocolVersion: "2025-06-18",
 23 |           capabilities: {},
 24 |           clientInfo: { 
 25 |             name: "master-mcp-server",
 26 |             version: "1.0.0"
 27 |           }
 28 |         }
 29 |       }
 30 |       
 31 |       await this.stdioManager.sendMessage(serverId, initializeRequest)
 32 |       const initializeResponse = await this.stdioManager.waitForResponse(serverId, initializeRequestId)
 33 |       
 34 |       if (initializeResponse.error) {
 35 |         throw new Error(`Failed to initialize STDIO server: ${initializeResponse.error.message}`)
 36 |       }
 37 |       
 38 |       // Send tools/list request
 39 |       const toolsRequestId = Date.now() + 1
 40 |       const toolsRequest = {
 41 |         jsonrpc: "2.0",
 42 |         id: toolsRequestId,
 43 |         method: "tools/list",
 44 |         params: {}
 45 |       }
 46 |       
 47 |       await this.stdioManager.sendMessage(serverId, toolsRequest)
 48 |       const toolsResponse = await this.stdioManager.waitForResponse(serverId, toolsRequestId)
 49 |       
 50 |       if (toolsResponse.error) {
 51 |         throw new Error(`Failed to list tools from STDIO server: ${toolsResponse.error.message}`)
 52 |       }
 53 |       
 54 |       const tools = toolsResponse.result?.tools || []
 55 |       
 56 |       // Send resources/list request
 57 |       const resourcesRequestId = Date.now() + 2
 58 |       const resourcesRequest = {
 59 |         jsonrpc: "2.0",
 60 |         id: resourcesRequestId,
 61 |         method: "resources/list",
 62 |         params: {}
 63 |       }
 64 |       
 65 |       await this.stdioManager.sendMessage(serverId, resourcesRequest)
 66 |       const resourcesResponse = await this.stdioManager.waitForResponse(serverId, resourcesRequestId)
 67 |       
 68 |       if (resourcesResponse.error) {
 69 |         throw new Error(`Failed to list resources from STDIO server: ${resourcesResponse.error.message}`)
 70 |       }
 71 |       
 72 |       const resources = resourcesResponse.result?.resources || []
 73 |       
 74 |       Logger.info('Discovered STDIO server capabilities', { serverId, tools: tools.length, resources: resources.length })
 75 |       
 76 |       return { 
 77 |         tools, 
 78 |         resources 
 79 |       }
 80 |     } catch (error) {
 81 |       Logger.error('Failed to discover STDIO server capabilities', { serverId, error })
 82 |       throw error
 83 |     }
 84 |   }
 85 | 
 86 |   async callTool(serverId: string, toolName: string, args: any): Promise<any> {
 87 |     try {
 88 |       const toolRequestId = Date.now()
 89 |       const toolRequest = {
 90 |         jsonrpc: "2.0",
 91 |         id: toolRequestId,
 92 |         method: "tools/call",
 93 |         params: {
 94 |           name: toolName,
 95 |           arguments: args
 96 |         }
 97 |       }
 98 |       
 99 |       await this.stdioManager.sendMessage(serverId, toolRequest)
100 |       const toolResponse = await this.stdioManager.waitForResponse(serverId, toolRequestId)
101 |       
102 |       if (toolResponse.error) {
103 |         throw new Error(`Failed to call tool ${toolName} on STDIO server: ${toolResponse.error.message}`)
104 |       }
105 |       
106 |       return toolResponse
107 |     } catch (error) {
108 |       Logger.error('STDIO tool call failed', { serverId, toolName, error })
109 |       throw error
110 |     }
111 |   }
112 | 
113 |   async readResource(serverId: string, uri: string): Promise<any> {
114 |     try {
115 |       const resourceRequestId = Date.now()
116 |       const resourceRequest = {
117 |         jsonrpc: "2.0",
118 |         id: resourceRequestId,
119 |         method: "resources/read",
120 |         params: { 
121 |           uri
122 |         }
123 |       }
124 |       
125 |       await this.stdioManager.sendMessage(serverId, resourceRequest)
126 |       const resourceResponse = await this.stdioManager.waitForResponse(serverId, resourceRequestId)
127 |       
128 |       if (resourceResponse.error) {
129 |         throw new Error(`Failed to read resource ${uri} from STDIO server: ${resourceResponse.error.message}`)
130 |       }
131 |       
132 |       return resourceResponse
133 |     } catch (error) {
134 |       Logger.error('STDIO resource read failed', { serverId, uri, error })
135 |       throw error
136 |     }
137 |   }
138 | }
```

--------------------------------------------------------------------------------
/docs/.vitepress/theme/components/ConfigGenerator.vue:
--------------------------------------------------------------------------------

```vue
  1 | <template>
  2 |   <div class="mcp-grid" style="margin: 8px 0 16px; align-items: start;">
  3 |     <div class="mcp-col-6">
  4 |       <h3 style="margin:6px 0">Master Settings</h3>
  5 |       <div class="mcp-callout">
  6 |         <label>Port
  7 |           <input v-model.number="state.port" type="number" min="1" max="65535" style="width:120px;margin-left:8px" />
  8 |         </label>
  9 |         <br />
 10 |         <label style="margin-top:8px;display:block">Base URL
 11 |           <input v-model="state.baseUrl" type="text" placeholder="https://your.domain" style="width:100%;margin-top:4px" />
 12 |         </label>
 13 |         <label style="margin-top:8px;display:block">Client Token (optional)
 14 |           <input v-model="state.clientToken" type="text" placeholder="Bearer token for testing" style="width:100%;margin-top:4px" />
 15 |         </label>
 16 |       </div>
 17 | 
 18 |       <h3 style="margin:12px 0 6px">Backends</h3>
 19 |       <div v-for="(s, i) in state.servers" :key="i" class="mcp-callout">
 20 |         <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
 21 |           <label>Id <input v-model="s.id" placeholder="search" /></label>
 22 |           <label>Type
 23 |             <select v-model="s.type">
 24 |               <option>local</option>
 25 |               <option>git</option>
 26 |               <option>npm</option>
 27 |               <option>docker</option>
 28 |               <option>pypi</option>
 29 |             </select>
 30 |           </label>
 31 |           <label v-if="s.type==='local'">Port <input v-model.number="s.config.port" type="number" min="1" max="65535" style="width:100px" /></label>
 32 |           <label v-else>URL <input v-model="s.config.url" placeholder="http://host:port" /></label>
 33 |           <label>Auth
 34 |             <select v-model="s.auth_strategy">
 35 |               <option>master_oauth</option>
 36 |               <option>delegate_oauth</option>
 37 |               <option>proxy_oauth</option>
 38 |               <option>bypass_auth</option>
 39 |             </select>
 40 |           </label>
 41 |           <button class="mcp-cta" style="margin-left:auto" @click="remove(i)">Remove</button>
 42 |         </div>
 43 |       </div>
 44 |       <button class="mcp-cta" @click="add">Add Backend</button>
 45 |     </div>
 46 | 
 47 |     <div class="mcp-col-6">
 48 |       <h3 style="margin:6px 0">Generated config.yaml</h3>
 49 |       <div style="position:relative">
 50 |         <button class="mcp-cta" style="position:absolute;right:8px;top:8px" @click="copyText(yaml)">Copy</button>
 51 |         <pre><code class="language-yaml">{{ yaml }}</code></pre>
 52 |       </div>
 53 | 
 54 |       <h3 style="margin:12px 0 6px">config.json</h3>
 55 |       <div style="position:relative">
 56 |         <button class="mcp-cta" style="position:absolute;right:8px;top:8px" @click="copyText(json)">Copy</button>
 57 |         <pre><code class="language-json">{{ json }}</code></pre>
 58 |       </div>
 59 |     </div>
 60 |   </div>
 61 | </template>
 62 | 
 63 | <script setup lang="ts">
 64 | import { computed, reactive } from 'vue'
 65 | 
 66 | type Server = {
 67 |   id: string
 68 |   type: 'local' | 'git' | 'npm' | 'docker' | 'pypi'
 69 |   auth_strategy: 'master_oauth' | 'delegate_oauth' | 'proxy_oauth' | 'bypass_auth'
 70 |   config: { port?: number; url?: string }
 71 | }
 72 | 
 73 | const state = reactive({
 74 |   port: 3000,
 75 |   baseUrl: '',
 76 |   clientToken: '',
 77 |   servers: [
 78 |     { id: 'search', type: 'local', auth_strategy: 'master_oauth', config: { port: 4100 } } as Server,
 79 |   ],
 80 | })
 81 | 
 82 | function add() {
 83 |   state.servers.push({ id: '', type: 'local', auth_strategy: 'master_oauth', config: {} })
 84 | }
 85 | function remove(i: number) {
 86 |   state.servers.splice(i, 1)
 87 | }
 88 | 
 89 | const jsonObj = computed(() => ({
 90 |   hosting: {
 91 |     port: state.port,
 92 |     base_url: state.baseUrl || undefined,
 93 |   },
 94 |   servers: state.servers.map(s => ({
 95 |     id: s.id, type: s.type, auth_strategy: s.auth_strategy, config: s.config,
 96 |   })),
 97 | }))
 98 | 
 99 | const json = computed(() => JSON.stringify(jsonObj.value, null, 2))
100 | 
101 | function toYaml(obj: any, indent = 0): string {
102 |   const pad = '  '.repeat(indent)
103 |   if (obj === null || obj === undefined) return ''
104 |   if (typeof obj !== 'object') return String(obj)
105 |   if (Array.isArray(obj)) {
106 |     return obj.map(v => `${pad}- ${typeof v === 'object' ? `\n${toYaml(v, indent + 1)}` : toYaml(v, indent)}`).join('\n')
107 |   }
108 |   return Object.entries(obj)
109 |     .filter(([, v]) => v !== undefined)
110 |     .map(([k, v]) => {
111 |       if (v && typeof v === 'object') {
112 |         const nested = toYaml(v, indent + 1)
113 |         return `${pad}${k}:\n${nested}`
114 |       }
115 |       return `${pad}${k}: ${toYaml(v, 0)}`
116 |     })
117 |     .join('\n')
118 | }
119 | 
120 | const yaml = computed(() => toYaml({
121 |   hosting: { port: state.port, base_url: state.baseUrl || undefined },
122 |   servers: jsonObj.value.servers,
123 | }))
124 | 
125 | async function copyText(text: string) {
126 |   try {
127 |     await navigator.clipboard.writeText(text)
128 |   } catch (e) {
129 |     console.warn('Copy failed', e)
130 |   }
131 | }
132 | </script>
133 | 
134 | 
```

--------------------------------------------------------------------------------
/src/config/secret-manager.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { CryptoUtils } from '../utils/crypto.js'
  2 | import { Logger } from '../utils/logger.js'
  3 | 
  4 | export interface SecretManagerOptions {
  5 |   // Name of env var that holds the encryption key used for configuration secrets
  6 |   keyEnvVar?: string
  7 |   // Optional explicit key value (discouraged in production)
  8 |   key?: string
  9 | }
 10 | 
 11 | export class SecretManager {
 12 |   private key: string
 13 | 
 14 |   constructor(options?: SecretManagerOptions) {
 15 |     const env = (globalThis as any)?.process?.env ?? {}
 16 |     const provided = options?.key || env[options?.keyEnvVar || 'MASTER_CONFIG_KEY'] || env.MASTER_SECRET_KEY
 17 |     const isProd = (env.NODE_ENV || env.MASTER_ENV) === 'production'
 18 |     if (!provided) {
 19 |       if (isProd) throw new Error('Missing MASTER_CONFIG_KEY for decrypting secrets in production')
 20 |       Logger.warn('MASTER_CONFIG_KEY missing; using ephemeral key (dev only)')
 21 |       this.key = CryptoUtils.generateSecureRandom(32)
 22 |     } else {
 23 |       this.key = String(provided)
 24 |     }
 25 |   }
 26 | 
 27 |   getKey(): string {
 28 |     return this.key
 29 |   }
 30 | 
 31 |   encrypt(value: string): string {
 32 |     return `enc:gcm:${CryptoUtils.encrypt(value, this.key)}`
 33 |   }
 34 | 
 35 |   decrypt(value: string): string {
 36 |     if (value.startsWith('enc:gcm:')) {
 37 |       const raw = value.slice('enc:gcm:'.length)
 38 |       return CryptoUtils.decrypt(raw, this.key)
 39 |     }
 40 |     return value
 41 |   }
 42 | 
 43 |   isEncrypted(value: string): boolean {
 44 |     return typeof value === 'string' && value.indexOf('enc:gcm:') === 0
 45 |   }
 46 | 
 47 |   // Resolve secret placeholders within a config object
 48 |   // - enc:gcm:<base64> → decrypted
 49 |   // - env:VARNAME → process.env[VARNAME]
 50 |   resolveSecrets<T>(obj: T): T {
 51 |     const env = (globalThis as any)?.process?.env ?? {}
 52 |     const visit = (v: any): any => {
 53 |       if (typeof v === 'string') {
 54 |         const vs: string = String(v)
 55 |         if (this.isEncrypted(vs)) return this.decrypt(vs)
 56 |         if (vs.slice(0, 4) === 'env:') return String(env[vs.slice(4)] ?? '')
 57 |         return v
 58 |       }
 59 |       if (Array.isArray(v)) return v.map((x) => visit(x))
 60 |       if (v && typeof v === 'object') {
 61 |         const out: Record<string, unknown> = {}
 62 |         for (const [k, vv] of Object.entries(v)) out[k] = visit(vv)
 63 |         return out
 64 |       }
 65 |       return v
 66 |     }
 67 |     return visit(obj)
 68 |   }
 69 | 
 70 |   redact<T>(obj: T): T {
 71 |     const secretKeyMatcher = /secret|token|password|key/i
 72 |     const visit = (v: any, keyHint?: string): any => {
 73 |       if (typeof v === 'string') {
 74 |         const vs: string = String(v)
 75 |         if (this.isEncrypted(vs)) return '***'
 76 |         if (keyHint && secretKeyMatcher.test(keyHint)) return '***'
 77 |         if (vs.slice(0, 4) === 'env:' && secretKeyMatcher.test(keyHint || '')) return '***'
 78 |         return v
 79 |       }
 80 |       if (Array.isArray(v)) return v.map((x) => visit(x, keyHint))
 81 |       if (v && typeof v === 'object') {
 82 |         const out: Record<string, unknown> = {}
 83 |         for (const [k, vv] of Object.entries(v)) out[k] = visit(vv, k)
 84 |         return out
 85 |       }
 86 |       return v
 87 |     }
 88 |     return visit(obj)
 89 |   }
 90 | 
 91 |   rotate<T extends Record<string, unknown>>(obj: T, newKey: string, secretPaths?: string[]): T {
 92 |     // Re-encrypt values under known secret paths
 93 |     const prevKey = this.key
 94 |     this.key = newKey
 95 |     const result = structuredClone(obj)
 96 |     const paths = secretPaths ?? inferSecretPaths(obj)
 97 |     for (const p of paths) {
 98 |       try {
 99 |         const cur = getByPath(result, p)
100 |         if (typeof cur === 'string') {
101 |           const plain = this.isEncrypted(cur) ? CryptoUtils.decrypt(cur.slice('enc:gcm:'.length), prevKey) : cur
102 |           setByPath(result, p, this.encrypt(plain))
103 |         }
104 |       } catch (err) {
105 |         Logger.warn(`Failed to rotate secret at ${p}`, String(err))
106 |       }
107 |     }
108 |     return result
109 |   }
110 | }
111 | 
112 | function getByPath(obj: any, path: string): unknown {
113 |   const parts = path.split('.')
114 |   let cur = obj
115 |   for (const p of parts) {
116 |     if (!cur || typeof cur !== 'object') return undefined
117 |     cur = cur[p]
118 |   }
119 |   return cur
120 | }
121 | 
122 | function setByPath(obj: any, path: string, value: unknown): void {
123 |   const parts = path.split('.')
124 |   let cur = obj
125 |   for (let i = 0; i < parts.length - 1; i++) {
126 |     const p = parts[i]
127 |     if (!cur[p] || typeof cur[p] !== 'object') cur[p] = {}
128 |     cur = cur[p]
129 |   }
130 |   cur[parts[parts.length - 1]] = value
131 | }
132 | 
133 | function inferSecretPaths(obj: Record<string, unknown>, base = ''): string[] {
134 |   const out: string[] = []
135 |   for (const [k, v] of Object.entries(obj)) {
136 |     const p = base ? `${base}.${k}` : k
137 |     if (typeof v === 'string') {
138 |       if (/secret|token|password|key/i.test(k)) out.push(p)
139 |       else if (v.startsWith('enc:gcm:') || v.startsWith('env:')) out.push(p)
140 |     } else if (v && typeof v === 'object') {
141 |       out.push(...inferSecretPaths(v as Record<string, unknown>, p))
142 |     }
143 |   }
144 |   return out
145 | }
146 | 
```

--------------------------------------------------------------------------------
/src/mcp-server.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
  2 | import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'
  3 | import type { Request, Response } from 'express'
  4 | import { Logger } from './utils/logger.js'
  5 | import { DependencyContainer } from './server/dependency-container.js'
  6 | import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'
  7 | 
  8 | // Create an MCP server with the official SDK
  9 | export async function createMcpServer(container: DependencyContainer): Promise<{
 10 |   mcpServer: McpServer,
 11 |   transport: StreamableHTTPServerTransport,
 12 |   handleRequest: (req: Request, res: Response) => Promise<void>
 13 | }> {
 14 |   // Create the MCP server with server info
 15 |   const mcpServer = new McpServer({
 16 |     name: 'master-mcp-server',
 17 |     version: '0.1.0'
 18 |   }, {
 19 |     capabilities: {
 20 |       tools: { listChanged: true },
 21 |       resources: { listChanged: true },
 22 |       prompts: { listChanged: true }
 23 |     }
 24 |   })
 25 | 
 26 |   // Register tools from the aggregated servers BEFORE connecting to transport
 27 |   const aggregatedTools = container.master.getAggregatedTools()
 28 |   Logger.info('Aggregated tools', { count: aggregatedTools.length, tools: aggregatedTools.map(t => t.name) })
 29 |   for (const tool of aggregatedTools) {
 30 |     // Skip tools with names that might cause conflicts
 31 |     if (tool.name.includes('..')) continue;
 32 |     
 33 |     Logger.info('Registering tool', { name: tool.name, description: tool.description })
 34 |     // Register the tool with the MCP server
 35 |     mcpServer.tool(tool.name, tool.description ?? '', async (args) => {
 36 |       try {
 37 |         // Route the tool call to the appropriate backend server
 38 |         const result = await container.master.handler.handleCallTool({
 39 |           name: tool.name,
 40 |           arguments: args
 41 |         })
 42 |         return result as CallToolResult
 43 |       } catch (error) {
 44 |         Logger.error('Tool execution failed', { tool: tool.name, error })
 45 |         return {
 46 |           content: [{
 47 |             type: 'text',
 48 |             text: `Error executing tool ${tool.name}: ${error instanceof Error ? error.message : String(error)}`
 49 |           }],
 50 |           isError: true
 51 |         }
 52 |       }
 53 |     })
 54 |   }
 55 | 
 56 |   // Register resources from the aggregated servers BEFORE connecting to transport
 57 |   const aggregatedResources = container.master.getAggregatedResources()
 58 |   Logger.info('Aggregated resources', { count: aggregatedResources.length, resources: aggregatedResources.map(r => r.uri) })
 59 |   for (const resource of aggregatedResources) {
 60 |     // Skip resources with URIs that might cause conflicts
 61 |     if (resource.uri.includes('..')) continue;
 62 |     
 63 |     Logger.info('Registering resource', { name: resource.name, uri: resource.uri, description: resource.description })
 64 |     mcpServer.resource(
 65 |       resource.name ?? resource.uri,
 66 |       resource.uri,
 67 |       {
 68 |         description: resource.description,
 69 |         mimeType: resource.mimeType
 70 |       },
 71 |       async () => {
 72 |         try {
 73 |           // Route the resource read to the appropriate backend server
 74 |           const result = await container.master.handler.handleReadResource({
 75 |             uri: resource.uri
 76 |           })
 77 |           
 78 |           // Convert the result to the format expected by the MCP server
 79 |           if (typeof result.contents === 'string') {
 80 |             return {
 81 |               contents: [{
 82 |                 uri: resource.uri,
 83 |                 text: result.contents,
 84 |                 mimeType: result.mimeType
 85 |               }]
 86 |             }
 87 |           } else {
 88 |             return {
 89 |               contents: [{
 90 |                 uri: resource.uri,
 91 |                 blob: Buffer.from(result.contents).toString('base64'),
 92 |                 mimeType: result.mimeType
 93 |               }]
 94 |             }
 95 |           }
 96 |         } catch (error) {
 97 |           Logger.error('Resource read failed', { resource: resource.uri, error })
 98 |           throw new Error(`Error reading resource ${resource.uri}: ${error instanceof Error ? error.message : String(error)}`)
 99 |         }
100 |       }
101 |     )
102 |   }
103 | 
104 |   // Create the HTTP streaming transport in stateless mode
105 |   const transport = new StreamableHTTPServerTransport({
106 |     sessionIdGenerator: undefined, // Stateless mode
107 |     enableJsonResponse: false, // Use SSE by default
108 |     enableDnsRebindingProtection: false
109 |   })
110 | 
111 |   // Connect the server to the transport AFTER registering tools and resources
112 |   await mcpServer.connect(transport)
113 | 
114 |   // Create a handler function for Express
115 |   const handleRequest = async (req: Request, res: Response) => {
116 |     try {
117 |       await transport.handleRequest(req, res, req.body)
118 |     } catch (error) {
119 |       Logger.error('MCP request handling failed', { error })
120 |       res.status(500).json({
121 |         error: 'Internal server error'
122 |       })
123 |     }
124 |   }
125 | 
126 |   return { mcpServer, transport, handleRequest }
127 | }
```

--------------------------------------------------------------------------------
/src/auth/token-manager.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { OAuthToken } from '../types/auth.js'
  2 | import { CryptoUtils } from '../utils/crypto.js'
  3 | import { Logger } from '../utils/logger.js'
  4 | 
  5 | export interface TokenStorage {
  6 |   set(key: string, value: string): Promise<void> | void
  7 |   get(key: string): Promise<string | undefined> | string | undefined
  8 |   delete(key: string): Promise<void> | void
  9 |   entries(): AsyncIterable<[string, string]> | Iterable<[string, string]>
 10 | }
 11 | 
 12 | class InMemoryTokenStorage implements TokenStorage {
 13 |   private map = new Map<string, string>()
 14 | 
 15 |   set(key: string, value: string): void {
 16 |     this.map.set(key, value)
 17 |   }
 18 |   get(key: string): string | undefined {
 19 |     return this.map.get(key)
 20 |   }
 21 |   delete(key: string): void {
 22 |     this.map.delete(key)
 23 |   }
 24 |   *entries(): Iterable<[string, string]> {
 25 |     yield* this.map.entries()
 26 |   }
 27 | }
 28 | 
 29 | export class TokenManager {
 30 |   private readonly storage: TokenStorage
 31 |   private readonly encKey: string
 32 | 
 33 |   constructor(options?: { storage?: TokenStorage; secret?: string }) {
 34 |     this.storage = options?.storage ?? autoDetectStorage()
 35 |     const g: any = globalThis as any
 36 |     const env = (g?.process?.env ?? g?.__WORKER_ENV ?? {}) as Record<string, string>
 37 |     const provided = options?.secret ?? (env as any).TOKEN_ENC_KEY
 38 | 
 39 |     if (!provided) {
 40 |       const envName = ((g?.process?.env ?? (g?.__WORKER_ENV ?? {})) as any).NODE_ENV ?? 'development'
 41 |       if (envName === 'production') {
 42 |         throw new Error('TOKEN_ENC_KEY is required in production for secure token storage')
 43 |       }
 44 |       Logger.warn('TOKEN_ENC_KEY missing; generating ephemeral dev key (tokens won\'t persist across restarts)')
 45 |       this.encKey = CryptoUtils.generateSecureRandom(32)
 46 |     } else {
 47 |       this.encKey = provided
 48 |     }
 49 |   }
 50 | 
 51 |   async storeToken(key: string, token: OAuthToken): Promise<void> {
 52 |     const serialized = JSON.stringify(token)
 53 |     const encrypted = CryptoUtils.encrypt(serialized, this.encKey)
 54 |     await this.storage.set(key, encrypted)
 55 |   }
 56 | 
 57 |   async getToken(key: string): Promise<OAuthToken | null> {
 58 |     const encrypted = await this.storage.get(key)
 59 |     if (!encrypted) return null
 60 |     try {
 61 |       const decrypted = CryptoUtils.decrypt(encrypted, this.encKey)
 62 |       return JSON.parse(decrypted) as OAuthToken
 63 |     } catch (err) {
 64 |       Logger.error('Failed to decrypt token; deleting corrupted entry', { key, err: String(err) })
 65 |       await this.storage.delete(key)
 66 |       return null
 67 |     }
 68 |   }
 69 | 
 70 |   async cleanupExpiredTokens(): Promise<void> {
 71 |     const now = Date.now()
 72 |     for await (const [k, v] of this.storage.entries() as AsyncIterable<[string, string]>) {
 73 |       try {
 74 |         const tok = JSON.parse(CryptoUtils.decrypt(v, this.encKey)) as OAuthToken
 75 |         if (typeof tok.expires_at === 'number' && tok.expires_at <= now) {
 76 |           await this.storage.delete(k)
 77 |         }
 78 |       } catch {
 79 |         await this.storage.delete(k)
 80 |       }
 81 |     }
 82 |   }
 83 | 
 84 |   generateState(data: unknown): string {
 85 |     const payload = JSON.stringify({ d: data, t: Date.now() })
 86 |     return CryptoUtils.encrypt(payload, this.encKey)
 87 |   }
 88 | 
 89 |   validateState(state: string, expectedData: unknown): boolean {
 90 |     try {
 91 |       const payload = JSON.parse(CryptoUtils.decrypt(state, this.encKey)) as { d: unknown }
 92 |       return JSON.stringify(payload.d) === JSON.stringify(expectedData)
 93 |     } catch {
 94 |       return false
 95 |     }
 96 |   }
 97 | }
 98 | 
 99 | export { InMemoryTokenStorage }
100 | 
101 | /**
102 |  * Auto-detects the best available storage backend.
103 |  * - Cloudflare Workers: KV namespace bound as `TOKENS`
104 |  * - Fallback: in-memory (non-persistent)
105 |  */
106 | function autoDetectStorage(): TokenStorage {
107 |   const g: any = globalThis as any
108 |   const env = g.__WORKER_ENV || {}
109 |   const kv = env.TOKENS || g.TOKENS || g.TOKENS_KV
110 |   if (kv && typeof kv.get === 'function' && typeof kv.put === 'function' && typeof kv.delete === 'function') {
111 |     return new KVTokenStorage(kv)
112 |   }
113 |   return new InMemoryTokenStorage()
114 | }
115 | 
116 | class KVTokenStorage implements TokenStorage {
117 |   constructor(private readonly kv: { get: (k: string) => Promise<string | null>; put: (k: string, v: string, opts?: any) => Promise<void>; delete: (k: string) => Promise<void>; list?: (opts?: any) => Promise<{ keys: { name: string }[] }> }) {}
118 |   async set(key: string, value: string): Promise<void> {
119 |     await this.kv.put(key, value)
120 |   }
121 |   async get(key: string): Promise<string | undefined> {
122 |     const v = await this.kv.get(key)
123 |     return v === null ? undefined : v
124 |   }
125 |   async delete(key: string): Promise<void> {
126 |     await this.kv.delete(key)
127 |   }
128 |   async *entries(): AsyncIterable<[string, string]> {
129 |     if (typeof this.kv.list === 'function') {
130 |       const { keys } = await this.kv.list()
131 |       for (const k of keys) {
132 |         const v = await this.kv.get(k.name)
133 |         if (v !== null) yield [k.name, v]
134 |       }
135 |     } else {
136 |       // KV without list support: nothing to iterate
137 |       return
138 |     }
139 |   }
140 | }
141 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import 'dotenv/config'
  2 | import express from 'express'
  3 | import type { Request, Response } from 'express'
  4 | import { DependencyContainer } from './server/dependency-container.js'
  5 | import { collectSystemMetrics } from './utils/monitoring.js'
  6 | import { CapabilityAggregator } from './modules/capability-aggregator.js'
  7 | import { createMcpServer } from './mcp-server.js'
  8 | 
  9 | export interface RunningServer {
 10 |   name: string
 11 |   version: string
 12 |   container: DependencyContainer
 13 |   stop: () => Promise<void>
 14 | }
 15 | 
 16 | function isNode(): boolean {
 17 |   return Boolean((globalThis as any)?.process?.versions?.node)
 18 | }
 19 | 
 20 | export async function createServer(startHttp = true): Promise<RunningServer> {
 21 |   const version = (globalThis as any)?.process?.env?.APP_VERSION ?? '0.1.0'
 22 |   const container = new DependencyContainer()
 23 |   await container.initialize()
 24 | 
 25 |   const server: RunningServer = {
 26 |     name: 'master-mcp-server',
 27 |     version,
 28 |     container,
 29 |     stop: async () => {
 30 |       try {
 31 |         container.configManager.stop()
 32 |         await container.master.unloadAll()
 33 |       } catch {
 34 |         // ignore
 35 |       }
 36 |     },
 37 |   }
 38 | 
 39 |   if (isNode() && startHttp) {
 40 |     await startNodeHttp(container)
 41 |   }
 42 | 
 43 |   // Graceful shutdown (Node only)
 44 |   if (isNode()) {
 45 |     const onSig = async () => {
 46 |       await server.stop()
 47 |       ;(process as any).exit?.(0)
 48 |     }
 49 |     process.on('SIGINT', onSig)
 50 |     process.on('SIGTERM', onSig)
 51 |   }
 52 | 
 53 |   return server
 54 | }
 55 | 
 56 | async function startNodeHttp(container: DependencyContainer): Promise<void> {
 57 |   const app = express()
 58 |   app.use(express.json())
 59 |   // Serve static assets for OAuth pages
 60 |   // eslint-disable-next-line @typescript-eslint/no-var-requires
 61 |   const expressStatic = (express as any).static
 62 |   if (expressStatic) app.use('/static', expressStatic('static'))
 63 | 
 64 |   const getToken = (req: Request): string | undefined => {
 65 |     const h = req.headers['authorization'] || req.headers['Authorization']
 66 |     if (typeof h === 'string' && h.toLowerCase().startsWith('bearer ')) return h.slice(7)
 67 |     return undefined
 68 |   }
 69 | 
 70 |   app.get('/health', (_req, res) => res.json({ ok: true }))
 71 |   app.get('/metrics', (_req, res) => {
 72 |     try {
 73 |       res.json({ ok: true, system: collectSystemMetrics() })
 74 |     } catch {
 75 |       res.json({ ok: true })
 76 |     }
 77 |   })
 78 |   // Mount OAuth endpoints using the master server's controller
 79 |   try {
 80 |     container.master.getOAuthFlowController().registerExpress(app)
 81 |   } catch {
 82 |     // If not available yet, ignore; will be mounted on demand if needed
 83 |   }
 84 |   app.get('/capabilities', (_req, res) => {
 85 |     const agg = new CapabilityAggregator()
 86 |     const caps = agg.aggregate(Array.from(container.master.getRouter().getServers().values()))
 87 |     res.json(caps)
 88 |   })
 89 | 
 90 |   // Create the MCP server with HTTP streaming transport
 91 |   const { handleRequest } = await createMcpServer(container)
 92 |   
 93 |   // Register MCP endpoints
 94 |   app.post('/mcp', handleRequest)
 95 |   app.get('/mcp', handleRequest)
 96 |   app.delete('/mcp', handleRequest)
 97 | 
 98 |   // Keep the existing endpoints for backward compatibility
 99 |   app.post('/mcp/tools/list', async (_req: Request, res: Response) => {
100 |     const handler = container.master.handler
101 |     const result = await handler.handleListTools({ type: 'list_tools' })
102 |     res.json(result)
103 |   })
104 | 
105 |   app.post('/mcp/tools/call', async (req: Request, res: Response) => {
106 |     const token = getToken(req)
107 |     const handler = new (container.master.handler.constructor as any)({
108 |       aggregator: container.aggregator,
109 |       router: container.master.getRouter(),
110 |       getClientToken: () => token,
111 |     }) as typeof container.master.handler
112 |     const result = await handler.handleCallTool({ name: req.body?.name, arguments: req.body?.arguments ?? {} })
113 |     res.json(result)
114 |   })
115 | 
116 |   app.post('/mcp/resources/list', async (_req: Request, res: Response) => {
117 |     const handler = container.master.handler
118 |     const result = await handler.handleListResources({ type: 'list_resources' })
119 |     res.json(result)
120 |   })
121 | 
122 |   app.post('/mcp/resources/read', async (req: Request, res: Response) => {
123 |     const token = getToken(req)
124 |     const handler = new (container.master.handler.constructor as any)({
125 |       aggregator: container.aggregator,
126 |       router: container.master.getRouter(),
127 |       getClientToken: () => token,
128 |     }) as typeof container.master.handler
129 |     const result = await handler.handleReadResource({ uri: req.body?.uri })
130 |     res.json(result)
131 |   })
132 | 
133 |   const port = container.getConfig().hosting.port ?? 3000
134 |   await new Promise<void>((resolve) => {
135 |     app.listen(port, () => {
136 |       // eslint-disable-next-line no-console
137 |       console.log(`Master MCP listening on http://localhost:${port}`)
138 |       resolve()
139 |     })
140 |   })
141 | }
142 | 
143 | export default createServer
144 | 
145 | // If this file is being run directly (not imported), start the server
146 | if (import.meta.url === `file://${process.argv[1]}`) {
147 |   createServer().catch((err) => {
148 |     console.error('Failed to start server:', err)
149 |     process.exit(1)
150 |   })
151 | }
152 | 
```

--------------------------------------------------------------------------------
/src/utils/monitoring.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Lightweight metrics, health checks, and profiling utilities.
  3 |  */
  4 | 
  5 | export type Labels = Record<string, string>
  6 | 
  7 | export class Counter {
  8 |   private value = 0
  9 |   inc(delta = 1): void {
 10 |     this.value += delta
 11 |   }
 12 |   get(): number {
 13 |     return this.value
 14 |   }
 15 | }
 16 | 
 17 | export class Gauge {
 18 |   private value = 0
 19 |   set(v: number): void {
 20 |     this.value = v
 21 |   }
 22 |   add(delta: number): void {
 23 |     this.value += delta
 24 |   }
 25 |   get(): number {
 26 |     return this.value
 27 |   }
 28 | }
 29 | 
 30 | export class Histogram {
 31 |   private readonly buckets: number[]
 32 |   private counts: number[]
 33 |   private sum = 0
 34 |   constructor(buckets = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]) {
 35 |     this.buckets = [...buckets].sort((a, b) => a - b)
 36 |     this.counts = Array(this.buckets.length + 1).fill(0)
 37 |   }
 38 |   observe(value: number): void {
 39 |     this.sum += value
 40 |     for (let i = 0; i < this.buckets.length; i++) {
 41 |       if (value <= this.buckets[i]) {
 42 |         this.counts[i]++
 43 |         return
 44 |       }
 45 |     }
 46 |     this.counts[this.counts.length - 1]++
 47 |   }
 48 |   snapshot(): { buckets: number[]; counts: number[]; sum: number } {
 49 |     return { buckets: [...this.buckets], counts: [...this.counts], sum: this.sum }
 50 |   }
 51 | }
 52 | 
 53 | export class MetricRegistry {
 54 |   private counters = new Map<string, Counter>()
 55 |   private gauges = new Map<string, Gauge>()
 56 |   private histograms = new Map<string, Histogram>()
 57 | 
 58 |   counter(name: string): Counter {
 59 |     let c = this.counters.get(name)
 60 |     if (!c) {
 61 |       c = new Counter()
 62 |       this.counters.set(name, c)
 63 |     }
 64 |     return c
 65 |   }
 66 | 
 67 |   gauge(name: string): Gauge {
 68 |     let g = this.gauges.get(name)
 69 |     if (!g) {
 70 |       g = new Gauge()
 71 |       this.gauges.set(name, g)
 72 |     }
 73 |     return g
 74 |   }
 75 | 
 76 |   histogram(name: string, buckets?: number[]): Histogram {
 77 |     let h = this.histograms.get(name)
 78 |     if (!h) {
 79 |       h = new Histogram(buckets)
 80 |       this.histograms.set(name, h)
 81 |     }
 82 |     return h
 83 |   }
 84 | 
 85 |   list(): { counters: Record<string, number>; gauges: Record<string, number>; histograms: Record<string, ReturnType<Histogram['snapshot']>> } {
 86 |     const counters: Record<string, number> = {}
 87 |     const gauges: Record<string, number> = {}
 88 |     const histograms: Record<string, ReturnType<Histogram['snapshot']>> = {}
 89 |     for (const [k, v] of this.counters.entries()) counters[k] = v.get()
 90 |     for (const [k, v] of this.gauges.entries()) gauges[k] = v.get()
 91 |     for (const [k, v] of this.histograms.entries()) histograms[k] = v.snapshot()
 92 |     return { counters, gauges, histograms }
 93 |   }
 94 | }
 95 | 
 96 | export class HealthCheckRegistry {
 97 |   private checks = new Map<string, () => Promise<{ ok: boolean; info?: unknown }>>()
 98 |   register(name: string, fn: () => Promise<{ ok: boolean; info?: unknown }>): void {
 99 |     this.checks.set(name, fn)
100 |   }
101 |   unregister(name: string): void {
102 |     this.checks.delete(name)
103 |   }
104 |   async run(): Promise<{ status: 'ok' | 'degraded' | 'fail'; results: Record<string, { ok: boolean; info?: unknown }> }> {
105 |     const entries: [string, { ok: boolean; info?: unknown }][] = []
106 |     for (const [name, fn] of this.checks) {
107 |       try {
108 |         const res = await fn()
109 |         entries.push([name, res])
110 |       } catch (e) {
111 |         entries.push([name, { ok: false, info: e instanceof Error ? e.message : String(e) }])
112 |       }
113 |     }
114 |     const results = Object.fromEntries(entries)
115 |     const oks = entries.filter(([, r]) => r.ok).length
116 |     const status: 'ok' | 'degraded' | 'fail' = oks === entries.length ? 'ok' : oks > 0 ? 'degraded' : 'fail'
117 |     return { status, results }
118 |   }
119 | }
120 | 
121 | /** Monitors event loop delay by scheduling microtasks. Returns a stop function. */
122 | export function monitorEventLoopLag(callback: (lagMs: number) => void, intervalMs = 500): () => void {
123 |   let timer: any
124 |   let stopped = false
125 |   const tick = () => {
126 |     if (stopped) return
127 |     const start = now()
128 |     timer = setTimeout(() => {
129 |       const lag = now() - start - intervalMs
130 |       callback(Math.max(0, lag))
131 |       tick()
132 |     }, intervalMs)
133 |   }
134 |   tick()
135 |   return () => {
136 |     stopped = true
137 |     if (typeof clearTimeout === 'function' && timer) clearTimeout(timer)
138 |   }
139 | }
140 | 
141 | export function now(): number {
142 |   if (typeof performance !== 'undefined' && typeof performance.now === 'function') return performance.now()
143 |   return Date.now()
144 | }
145 | 
146 | export function collectSystemMetrics(): Record<string, unknown> {
147 |   const g: any = globalThis as any
148 |   const out: Record<string, unknown> = { timestamp: new Date().toISOString() }
149 |   try {
150 |     if (g.process?.memoryUsage) {
151 |       const m = g.process.memoryUsage()
152 |       out.memory = {
153 |         rss: m.rss,
154 |         heapTotal: m.heapTotal,
155 |         heapUsed: m.heapUsed,
156 |         external: m.external,
157 |       }
158 |     }
159 |     if (g.os?.loadavg) {
160 |       const l = g.os.loadavg()
161 |       out.loadavg = { '1m': l[0], '5m': l[1], '15m': l[2] }
162 |     }
163 |   } catch {
164 |     // ignore
165 |   }
166 |   // Workers: best-effort
167 |   if (!out.memory && (performance as any)?.memory) {
168 |     out.memory = (performance as any).memory
169 |   }
170 |   return out
171 | }
172 | 
173 | 
```

--------------------------------------------------------------------------------
/src/routing/circuit-breaker.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Logger } from '../utils/logger.js'
  2 | 
  3 | export type CircuitState = 'closed' | 'open' | 'half_open'
  4 | 
  5 | export interface CircuitRecord {
  6 |   state: CircuitState
  7 |   failures: number
  8 |   successes: number
  9 |   nextTryAt: number // epoch ms when half-open trial is permitted
 10 |   openedAt?: number
 11 |   halfOpenInProgress?: boolean
 12 | }
 13 | 
 14 | export interface CircuitBreakerOptions {
 15 |   failureThreshold: number // failures before opening circuit
 16 |   successThreshold: number // successes in half-open before closing
 17 |   recoveryTimeoutMs: number // time to wait before permitting a half-open trial
 18 |   name?: string
 19 | }
 20 | 
 21 | export interface CircuitStorage {
 22 |   get(key: string): CircuitRecord | undefined
 23 |   set(key: string, value: CircuitRecord): void
 24 |   delete?(key: string): void
 25 | }
 26 | 
 27 | export class InMemoryCircuitStorage implements CircuitStorage {
 28 |   private readonly map = new Map<string, CircuitRecord>()
 29 |   get(key: string): CircuitRecord | undefined {
 30 |     return this.map.get(key)
 31 |   }
 32 |   set(key: string, value: CircuitRecord): void {
 33 |     this.map.set(key, value)
 34 |   }
 35 |   delete(key: string): void {
 36 |     this.map.delete(key)
 37 |   }
 38 | }
 39 | 
 40 | export class CircuitOpenError extends Error {
 41 |   readonly retryAfterMs?: number
 42 |   constructor(message: string, retryAfterMs?: number) {
 43 |     super(message)
 44 |     this.name = 'CircuitOpenError'
 45 |     this.retryAfterMs = retryAfterMs
 46 |   }
 47 | }
 48 | 
 49 | export class CircuitBreaker {
 50 |   private readonly storage: CircuitStorage
 51 |   private readonly opts: Required<CircuitBreakerOptions>
 52 | 
 53 |   constructor(options?: Partial<CircuitBreakerOptions>, storage?: CircuitStorage) {
 54 |     this.opts = {
 55 |       failureThreshold: options?.failureThreshold ?? 5,
 56 |       successThreshold: options?.successThreshold ?? 2,
 57 |       recoveryTimeoutMs: options?.recoveryTimeoutMs ?? 30_000,
 58 |       name: options?.name ?? 'default',
 59 |     }
 60 |     this.storage = storage ?? new InMemoryCircuitStorage()
 61 |   }
 62 | 
 63 |   private now(): number {
 64 |     return Date.now()
 65 |   }
 66 | 
 67 |   private initial(): CircuitRecord {
 68 |     return { state: 'closed', failures: 0, successes: 0, nextTryAt: 0 }
 69 |   }
 70 | 
 71 |   private getRecord(key: string): CircuitRecord {
 72 |     return this.storage.get(key) ?? this.initial()
 73 |   }
 74 | 
 75 |   canExecute(key: string): { allowed: boolean; state: CircuitState; retryAfterMs?: number } {
 76 |     const rec = this.getRecord(key)
 77 |     const now = this.now()
 78 |     if (rec.state === 'open') {
 79 |       if (now >= rec.nextTryAt && !rec.halfOpenInProgress) {
 80 |         rec.state = 'half_open'
 81 |         rec.halfOpenInProgress = true
 82 |         rec.successes = 0
 83 |         this.storage.set(key, rec)
 84 |         return { allowed: true, state: rec.state }
 85 |       }
 86 |       return { allowed: false, state: 'open', retryAfterMs: Math.max(0, rec.nextTryAt - now) }
 87 |     }
 88 |     if (rec.state === 'half_open') {
 89 |       // Only permit one in-flight trial at a time
 90 |       if (rec.halfOpenInProgress) return { allowed: false, state: 'half_open', retryAfterMs: this.opts.recoveryTimeoutMs }
 91 |       rec.halfOpenInProgress = true
 92 |       this.storage.set(key, rec)
 93 |       return { allowed: true, state: rec.state }
 94 |     }
 95 |     return { allowed: true, state: rec.state }
 96 |   }
 97 | 
 98 |   onSuccess(key: string): void {
 99 |     const rec = this.getRecord(key)
100 |     if (rec.state === 'half_open') {
101 |       rec.successes += 1
102 |       rec.halfOpenInProgress = false
103 |       if (rec.successes >= this.opts.successThreshold) {
104 |         // Close the circuit after consecutive successes
105 |         this.storage.set(key, this.initial())
106 |         Logger.debug(`[Circuit] CLOSED after half-open successes`, { key, name: this.opts.name })
107 |         return
108 |       }
109 |       this.storage.set(key, rec)
110 |       return
111 |     }
112 |     // Closed state: reset failures on success
113 |     rec.failures = 0
114 |     this.storage.set(key, rec)
115 |   }
116 | 
117 |   onFailure(key: string, _error?: unknown): void {
118 |     const rec = this.getRecord(key)
119 |     const now = this.now()
120 |     if (rec.state === 'half_open') {
121 |       // Failure during half-open => immediately open again
122 |       rec.state = 'open'
123 |       rec.failures = this.opts.failureThreshold
124 |       rec.successes = 0
125 |       rec.openedAt = now
126 |       rec.nextTryAt = now + this.opts.recoveryTimeoutMs
127 |       rec.halfOpenInProgress = false
128 |       this.storage.set(key, rec)
129 |       Logger.debug(`[Circuit] RE-OPEN after half-open failure`, { key, name: this.opts.name })
130 |       return
131 |     }
132 | 
133 |     rec.failures += 1
134 |     if (rec.failures >= this.opts.failureThreshold) {
135 |       rec.state = 'open'
136 |       rec.openedAt = now
137 |       rec.nextTryAt = now + this.opts.recoveryTimeoutMs
138 |       this.storage.set(key, rec)
139 |       Logger.debug(`[Circuit] OPEN due to failures`, { key, name: this.opts.name, failures: rec.failures })
140 |     } else {
141 |       this.storage.set(key, rec)
142 |     }
143 |   }
144 | 
145 |   async execute<T>(key: string, fn: () => Promise<T>): Promise<T> {
146 |     const gate = this.canExecute(key)
147 |     if (!gate.allowed) throw new CircuitOpenError('Circuit open', gate.retryAfterMs)
148 |     try {
149 |       const result = await fn()
150 |       this.onSuccess(key)
151 |       return result
152 |     } catch (err) {
153 |       this.onFailure(key, err)
154 |       throw err
155 |     }
156 |   }
157 | }
158 | 
159 | 
```

--------------------------------------------------------------------------------
/tests/servers/test-streaming-both-simple.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | import { spawn } from 'node:child_process'
  4 | import { Client } from '@modelcontextprotocol/sdk/client/index.js'
  5 | import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
  6 | 
  7 | async function startHttpServer() {
  8 |   console.log('Starting HTTP test server...')
  9 |   
 10 |   // Start the HTTP server as a background process
 11 |   const httpServer = spawn('node', ['examples/test-mcp-server.js'], {
 12 |     stdio: ['ignore', 'pipe', 'pipe'],
 13 |     env: { ...process.env, PORT: '3006' }
 14 |   })
 15 |   
 16 |   // Capture stdout and stderr
 17 |   httpServer.stdout.on('data', (data) => {
 18 |     console.log(`[HTTP Server] ${data.toString().trim()}`)
 19 |   })
 20 |   
 21 |   httpServer.stderr.on('data', (data) => {
 22 |     console.error(`[HTTP Server ERROR] ${data.toString().trim()}`)
 23 |   })
 24 |   
 25 |   // Wait for the server to start
 26 |   await new Promise((resolve, reject) => {
 27 |     let timeout = setTimeout(() => {
 28 |       reject(new Error('HTTP server startup timeout'))
 29 |     }, 5000)
 30 |     
 31 |     httpServer.stdout.on('data', (data) => {
 32 |       if (data.toString().includes('Test MCP server listening')) {
 33 |         clearTimeout(timeout)
 34 |         resolve()
 35 |       }
 36 |     })
 37 |   })
 38 |   
 39 |   return httpServer
 40 | }
 41 | 
 42 | async function runStreamingTest() {
 43 |   try {
 44 |     console.log('Testing Master MCP Server with HTTP Streaming...')
 45 |     
 46 |     // Create a streamable HTTP transport to connect to our MCP server
 47 |     const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3005/mcp'))
 48 |     
 49 |     // Create the MCP client
 50 |     const client = new Client({
 51 |       name: 'master-mcp-streaming-test-client',
 52 |       version: '1.0.0'
 53 |     })
 54 |     
 55 |     // Initialize the client
 56 |     await client.connect(transport)
 57 |     console.log('✅ Server initialized with streaming transport')
 58 |     console.log('Server info:', client.getServerVersion())
 59 |     console.log('Server capabilities:', client.getServerCapabilities())
 60 |     
 61 |     // List tools using streaming
 62 |     console.log('\n--- Testing tools/list with streaming ---')
 63 |     const toolsResult = await client.listTools({})
 64 |     console.log('✅ tools/list successful with streaming')
 65 |     console.log('Number of tools:', toolsResult.tools.length)
 66 |     console.log('Tools:', toolsResult.tools.map(t => t.name))
 67 |     
 68 |     // Verify both servers are present
 69 |     const hasHttpTool = toolsResult.tools.some(t => t.name === 'test-server.echo')
 70 |     const hasStdioTool = toolsResult.tools.some(t => t.name === 'stdio-server.stdio-echo')
 71 |     
 72 |     if (hasHttpTool) {
 73 |       console.log('✅ HTTP server tool found')
 74 |     } else {
 75 |       console.log('❌ HTTP server tool not found')
 76 |     }
 77 |     
 78 |     if (hasStdioTool) {
 79 |       console.log('✅ STDIO server tool found')
 80 |     } else {
 81 |       console.log('❌ STDIO server tool not found')
 82 |     }
 83 |     
 84 |     // List resources using streaming
 85 |     console.log('\n--- Testing resources/list with streaming ---')
 86 |     const resourcesResult = await client.listResources({})
 87 |     console.log('✅ resources/list successful with streaming')
 88 |     console.log('Number of resources:', resourcesResult.resources.length)
 89 |     console.log('Resources:', resourcesResult.resources.map(r => r.uri))
 90 |     
 91 |     // Verify both servers are present
 92 |     const hasHttpResource = resourcesResult.resources.some(r => r.uri === 'test-server.test://example')
 93 |     const hasStdioResource = resourcesResult.resources.some(r => r.uri === 'stdio-server.stdio://example/resource')
 94 |     
 95 |     if (hasHttpResource) {
 96 |       console.log('✅ HTTP server resource found')
 97 |     } else {
 98 |       console.log('❌ HTTP server resource not found')
 99 |     }
100 |     
101 |     if (hasStdioResource) {
102 |       console.log('✅ STDIO server resource found')
103 |     } else {
104 |       console.log('❌ STDIO server resource not found')
105 |     }
106 |     
107 |     // Test ping
108 |     console.log('\n--- Testing ping with streaming ---')
109 |     const pingResult = await client.ping()
110 |     console.log('✅ ping successful with streaming')
111 |     console.log('Ping result:', pingResult)
112 |     
113 |     // Summary
114 |     console.log('\n--- Test Summary ---')
115 |     if (hasHttpTool && hasStdioTool && hasHttpResource && hasStdioResource) {
116 |       console.log('🎉 All tests passed! Both HTTP and STDIO servers are working correctly.')
117 |     } else {
118 |       console.log('⚠️ Some tests failed. Check the output above for details.')
119 |     }
120 |     
121 |     // Close the connection
122 |     await client.close()
123 |     console.log('\n✅ Disconnected from MCP server')
124 |     
125 |   } catch (error) {
126 |     console.error('❌ Streaming test failed:', error)
127 |     console.error('Error stack:', error.stack)
128 |   }
129 | }
130 | 
131 | async function main() {
132 |   let httpServer
133 |   
134 |   try {
135 |     // Start the HTTP server
136 |     httpServer = await startHttpServer()
137 |     
138 |     // Wait a bit for the master server to discover the HTTP server
139 |     console.log('Waiting for server discovery...')
140 |     await new Promise(resolve => setTimeout(resolve, 3000))
141 |     
142 |     // Run the streaming test
143 |     await runStreamingTest()
144 |   } catch (error) {
145 |     console.error('Test failed:', error)
146 |   } finally {
147 |     // Clean up: kill the HTTP server
148 |     if (httpServer) {
149 |       console.log('Stopping HTTP server...')
150 |       httpServer.kill()
151 |     }
152 |   }
153 | }
154 | 
155 | // Run the test
156 | main()
```

--------------------------------------------------------------------------------
/docs/.vitepress/theme/components/AuthFlowDemo.vue:
--------------------------------------------------------------------------------

```vue
  1 | <template>
  2 |   <div class="mcp-callout">
  3 |     <label>Strategy
  4 |       <select v-model="strategy" style="margin-left:8px">
  5 |         <option value="master_oauth">master_oauth</option>
  6 |         <option value="delegate_oauth">delegate_oauth</option>
  7 |         <option value="proxy_oauth">proxy_oauth</option>
  8 |         <option value="bypass_auth">bypass_auth</option>
  9 |       </select>
 10 |     </label>
 11 |   </div>
 12 | 
 13 |   <div class="mcp-grid" style="align-items:start">
 14 |     <div class="mcp-col-6">
 15 |       <h4 style="margin:8px 0">Flow</h4>
 16 |       <ul>
 17 |         <li v-for="(s, i) in flow.steps" :key="i">{{ s }}</li>
 18 |       </ul>
 19 |       <div class="mcp-callout" v-if="flow.note">{{ flow.note }}</div>
 20 |     </div>
 21 |     <div class="mcp-col-6">
 22 |       <h4 style="margin:8px 0">Diagram</h4>
 23 |       <div class="mcp-diagram">
 24 |         <svg viewBox="0 0 600 240" width="100%" height="180">
 25 |           <defs>
 26 |             <marker id="arrow" markerWidth="10" markerHeight="10" refX="6" refY="3" orient="auto">
 27 |               <path d="M0,0 L0,6 L6,3 z" fill="currentColor" />
 28 |             </marker>
 29 |           </defs>
 30 |           <!-- Nodes -->
 31 |           <rect x="30" y="30" width="140" height="40" rx="8" fill="none" stroke="currentColor" />
 32 |           <text x="100" y="55" text-anchor="middle">Client</text>
 33 |           <rect x="230" y="30" width="140" height="40" rx="8" fill="none" stroke="currentColor" />
 34 |           <text x="300" y="55" text-anchor="middle">Master</text>
 35 |           <rect x="430" y="30" width="140" height="40" rx="8" fill="none" stroke="currentColor" />
 36 |           <text x="500" y="55" text-anchor="middle">Backend</text>
 37 | 
 38 |           <!-- Arrows vary by strategy -->
 39 |           <g v-if="strategy==='master_oauth'">
 40 |             <path d="M170,50 L230,50" stroke="currentColor" marker-end="url(#arrow)" />
 41 |             <text x="200" y="40" text-anchor="middle">Bearer client_token</text>
 42 |             <path d="M370,50 L430,50" stroke="currentColor" marker-end="url(#arrow)" />
 43 |             <text x="400" y="40" text-anchor="middle">Bearer client_token</text>
 44 |           </g>
 45 |           <g v-else-if="strategy==='delegate_oauth'">
 46 |             <path d="M170,50 L230,50" stroke="currentColor" marker-end="url(#arrow)" />
 47 |             <text x="200" y="40" text-anchor="middle">call tool</text>
 48 |             <path d="M230,90 L120,160" stroke="currentColor" marker-end="url(#arrow)" />
 49 |             <text x="170" y="130" text-anchor="middle">302 authorize</text>
 50 |             <path d="M120,160 L430,50" stroke="currentColor" marker-end="url(#arrow)" />
 51 |             <text x="275" y="120" text-anchor="middle">code + PKCE</text>
 52 |             <path d="M430,50 L230,50" stroke="currentColor" marker-end="url(#arrow)" />
 53 |             <text x="330" y="40" text-anchor="middle">token stored</text>
 54 |           </g>
 55 |           <g v-else-if="strategy==='proxy_oauth'">
 56 |             <path d="M170,50 L230,50" stroke="currentColor" marker-end="url(#arrow)" />
 57 |             <text x="200" y="40" text-anchor="middle">call tool</text>
 58 |             <path d="M370,50 L430,50" stroke="currentColor" marker-end="url(#arrow)" />
 59 |             <text x="400" y="40" text-anchor="middle">Bearer backend_token</text>
 60 |             <path d="M300,70 L300,120 L430,120" stroke="currentColor" marker-end="url(#arrow)" />
 61 |             <text x="360" y="110" text-anchor="middle">refresh if needed</text>
 62 |           </g>
 63 |           <g v-else>
 64 |             <path d="M170,50 L230,50" stroke="currentColor" marker-end="url(#arrow)" />
 65 |             <path d="M370,50 L430,50" stroke="currentColor" marker-end="url(#arrow)" />
 66 |             <text x="400" y="40" text-anchor="middle">no auth header</text>
 67 |           </g>
 68 |         </svg>
 69 |       </div>
 70 |     </div>
 71 |   </div>
 72 | </template>
 73 | 
 74 | <script setup lang="ts">
 75 | import { computed, ref } from 'vue'
 76 | 
 77 | const strategy = ref<'master_oauth'|'delegate_oauth'|'proxy_oauth'|'bypass_auth'>('master_oauth')
 78 | 
 79 | const flow = computed(() => {
 80 |   switch (strategy.value) {
 81 |     case 'master_oauth':
 82 |       return {
 83 |         steps: [
 84 |           'Client calls master with Authorization: Bearer <client_token>',
 85 |           'Master forwards same token to backend',
 86 |           'Backend validates token and serves the request'
 87 |         ],
 88 |         note: 'Simple and effective when backends trust the same issuer/audience as the client.'
 89 |       }
 90 |     case 'delegate_oauth':
 91 |       return {
 92 |         steps: [
 93 |           'Client calls master; master requires backend auth',
 94 |           'Master responds with OAuth delegation metadata',
 95 |           'Client completes provider auth via /oauth/authorize and callback',
 96 |           'Master stores backend token and retries the call'
 97 |         ],
 98 |         note: 'Use PKCE + state. Configure provider endpoints and client credentials.'
 99 |       }
100 |     case 'proxy_oauth':
101 |       return {
102 |         steps: [
103 |           'Master manages backend tokens and refresh cycles',
104 |           'Requests include backend token; refresh on expiry',
105 |           'Fallback to delegation or pass-through as configured'
106 |         ],
107 |         note: 'Centralizes token lifecycle with fewer client prompts.'
108 |       }
109 |     default:
110 |       return {
111 |         steps: [
112 |           'No authentication is added by master',
113 |           'Backend must not require Authorization for selected endpoints'
114 |         ],
115 |         note: 'Use only for public or development backends.'
116 |       }
117 |   }
118 | })
119 | </script>
120 | 
121 | 
```

--------------------------------------------------------------------------------
/docs/.vitepress/config.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { defineConfig } from 'vitepress'
  2 | 
  3 | export default defineConfig({
  4 |   title: 'Master MCP Server',
  5 |   description: 'Aggregate and orchestrate multiple MCP servers behind one endpoint',
  6 |   lastUpdated: true,
  7 |   cleanUrls: true,
  8 |   head: [
  9 |     ['meta', { name: 'theme-color', content: '#0ea5e9' }],
 10 |     ['meta', { name: 'viewport', content: 'width=device-width, initial-scale=1' }],
 11 |   ],
 12 |   themeConfig: {
 13 |     logo: '/logo.svg',
 14 |     siteTitle: 'Master MCP Server',
 15 |     search: {
 16 |       provider: 'local'
 17 |     },
 18 |     socialLinks: [
 19 |       { icon: 'github', link: 'https://github.com/your-org/master-mcp-server' }
 20 |     ],
 21 |     outline: [2, 6],
 22 |     nav: [
 23 |       { text: 'Getting Started', link: '/getting-started/overview' },
 24 |       { text: 'Guides', link: '/guides/index' },
 25 |       { text: 'API', link: '/api/index' },
 26 |       { text: 'Configuration', link: '/configuration/overview' },
 27 |       { text: 'Deployment', link: '/deployment/index' },
 28 |       { text: 'Examples', link: '/examples/index' },
 29 |       { text: 'Advanced', link: '/advanced/index' },
 30 |       { text: 'Troubleshooting', link: '/troubleshooting/index' },
 31 |       { text: 'Contributing', link: '/contributing/index' }
 32 |     ],
 33 |     sidebar: {
 34 |       '/getting-started/': [
 35 |         {
 36 |           text: 'Getting Started',
 37 |           items: [
 38 |             { text: 'Overview', link: '/getting-started/overview' },
 39 |             { text: 'Installation', link: '/getting-started/installation' },
 40 |             { text: 'Quick Start', link: '/getting-started/quick-start' },
 41 |             { text: 'Quickstart (Node)', link: '/getting-started/quickstart-node' },
 42 |             { text: 'Quickstart (Workers)', link: '/getting-started/quickstart-workers' },
 43 |             { text: 'Core Concepts', link: '/getting-started/concepts' }
 44 |           ]
 45 |         }
 46 |       ],
 47 |       '/guides/': [
 48 |         {
 49 |           text: 'User Guides',
 50 |           items: [
 51 |             { text: 'Authentication', link: '/guides/authentication' },
 52 |             { text: 'OAuth Delegation', link: '/guides/oauth-delegation' },
 53 |             { text: 'Client Integration', link: '/guides/client-integration' },
 54 |             { text: 'Server Sharing', link: '/guides/server-sharing' },
 55 |             { text: 'Module Loading', link: '/guides/module-loading' },
 56 |             { text: 'Request Routing', link: '/guides/request-routing' },
 57 |             { text: 'Configuration', link: '/guides/configuration' },
 58 |             { text: 'Testing Strategy', link: '/guides/testing' }
 59 |           ]
 60 |         }
 61 |       ],
 62 |       '/api/': [
 63 |         {
 64 |           text: 'API Reference',
 65 |           items: [
 66 |             { text: 'Overview', link: '/api/index' },
 67 |             { text: 'Types', link: '/api/reference/modules' }
 68 |           ]
 69 |         }
 70 |       ],
 71 |       '/configuration/': [
 72 |         {
 73 |           text: 'Configuration',
 74 |           items: [
 75 |             { text: 'Overview', link: '/configuration/overview' },
 76 |             { text: 'Reference', link: '/configuration/reference' },
 77 |             { text: 'Examples', link: '/configuration/examples' },
 78 |             { text: 'Environment Variables', link: '/configuration/environment' }
 79 |           ]
 80 |         }
 81 |       ],
 82 |       '/deployment/': [
 83 |         {
 84 |           text: 'Deployment',
 85 |           items: [
 86 |             { text: 'Overview', link: '/deployment/index' },
 87 |             { text: 'Docker', link: '/deployment/docker' },
 88 |             { text: 'Cloudflare Workers', link: '/deployment/cloudflare-workers' },
 89 |             { text: 'Koyeb', link: '/deployment/koyeb' },
 90 |             { text: 'Docs Site', link: '/deployment/docs-site' }
 91 |           ]
 92 |         }
 93 |       ],
 94 |       '/examples/': [
 95 |         {
 96 |           text: 'Examples',
 97 |           items: [
 98 |             { text: 'Index', link: '/examples/index' },
 99 |             { text: 'Basic Node Aggregator', link: '/examples/basic-node' },
100 |             { text: 'Cloudflare Worker', link: '/examples/cloudflare-worker' },
101 |             { text: 'Advanced Routing', link: '/examples/advanced-routing' },
102 |             { text: 'OAuth Delegation', link: '/examples/oauth-delegation' },
103 |             { text: 'Testing Patterns', link: '/examples/testing' }
104 |           ]
105 |         }
106 |       ],
107 |       '/advanced/': [
108 |         {
109 |           text: 'Advanced Topics',
110 |           items: [
111 |             { text: 'Security Hardening', link: '/advanced/security' },
112 |             { text: 'Performance & Scalability', link: '/advanced/performance' },
113 |             { text: 'Monitoring & Logging', link: '/advanced/monitoring' },
114 |             { text: 'Extensibility & Plugins', link: '/advanced/extensibility' }
115 |           ]
116 |         }
117 |       ],
118 |       '/troubleshooting/': [
119 |         {
120 |           text: 'Troubleshooting',
121 |           items: [
122 |             { text: 'Common Issues', link: '/troubleshooting/index' },
123 |             { text: 'OAuth & Tokens', link: '/troubleshooting/oauth' },
124 |             { text: 'Routing & Modules', link: '/troubleshooting/routing' },
125 |             { text: 'Deployment', link: '/troubleshooting/deployment' }
126 |           ]
127 |         }
128 |       ],
129 |       '/contributing/': [
130 |         {
131 |           text: 'Contributing',
132 |           items: [
133 |             { text: 'Overview', link: '/contributing/index' },
134 |             { text: 'Development Setup', link: '/contributing/dev-setup' },
135 |             { text: 'Coding & Docs Guidelines', link: '/contributing/guidelines' }
136 |           ]
137 |         }
138 |       ]
139 |     }
140 |   }
141 | })
142 | 
```

--------------------------------------------------------------------------------
/src/server/config-manager.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { MasterConfig, RoutingConfig, ServerConfig } from '../types/config.js'
  2 | import { ConfigLoader } from '../config/config-loader.js'
  3 | import { Logger } from '../utils/logger.js'
  4 | import { EnvironmentManager } from '../config/environment-manager.js'
  5 | import { SecretManager } from '../config/secret-manager.js'
  6 | 
  7 | export interface ConfigManagerOptions {
  8 |   // If provided, watch the file for changes (Node only)
  9 |   watch?: boolean
 10 | }
 11 | 
 12 | type Listener = (config: MasterConfig) => void
 13 | 
 14 | export class ConfigManager {
 15 |   private config: MasterConfig | null = null
 16 |   private readonly listeners: Set<Listener> = new Set()
 17 |   private stopWatcher?: () => void
 18 |   private readonly secrets = new SecretManager()
 19 |   private watchPaths: string[] = []
 20 | 
 21 |   constructor(private readonly options?: ConfigManagerOptions) {}
 22 | 
 23 |   async load(): Promise<MasterConfig> {
 24 |     const explicit = EnvironmentManager.getExplicitConfigPath()
 25 |     let loaded: MasterConfig
 26 |     try {
 27 |       loaded = await ConfigLoader.load({ path: explicit })
 28 |     } catch (err) {
 29 |       Logger.warn('Primary config load failed; attempting env-only load', String(err))
 30 |       loaded = await ConfigLoader.loadFromEnv()
 31 |     }
 32 |     const normalized = this.applyDefaults(loaded)
 33 |     this.config = normalized
 34 |     const redacted = this.secrets.redact(normalized)
 35 |     Logger.info('Configuration loaded', {
 36 |       servers: normalized.servers.length,
 37 |       hosting: normalized.hosting.platform,
 38 |       redacted,
 39 |     })
 40 |     if (this.options?.watch) this.prepareWatcher(explicit)
 41 |     return normalized
 42 |   }
 43 | 
 44 |   getConfig(): MasterConfig {
 45 |     if (!this.config) throw new Error('Config not loaded')
 46 |     return this.config
 47 |   }
 48 | 
 49 |   getRouting(): RoutingConfig | undefined {
 50 |     return this.config?.routing
 51 |   }
 52 | 
 53 |   onChange(listener: Listener): () => void {
 54 |     this.listeners.add(listener)
 55 |     return () => this.listeners.delete(listener)
 56 |   }
 57 | 
 58 |   async reload(): Promise<void> {
 59 |     await this.load()
 60 |     if (this.config) this.emit(this.config)
 61 |   }
 62 | 
 63 |   stop(): void {
 64 |     try {
 65 |       this.stopWatcher?.()
 66 |     } catch {
 67 |       // ignore
 68 |     }
 69 |   }
 70 | 
 71 |   private emit(config: MasterConfig): void {
 72 |     for (const l of this.listeners) {
 73 |       try {
 74 |         l(config)
 75 |       } catch (err) {
 76 |         Logger.warn('Config listener threw', err)
 77 |       }
 78 |     }
 79 |   }
 80 | 
 81 |   private applyDefaults(cfg: MasterConfig): MasterConfig {
 82 |     // Shallow copy to avoid mutation
 83 |     const copy: MasterConfig = {
 84 |       ...cfg,
 85 |       hosting: {
 86 |         platform: cfg.hosting.platform ?? 'node',
 87 |         port: cfg.hosting.port ?? 3000,
 88 |         base_url: cfg.hosting.base_url,
 89 |       },
 90 |       routing: cfg.routing ? { ...cfg.routing } : {},
 91 |       master_oauth: { ...cfg.master_oauth },
 92 |       servers: cfg.servers.map((s) => this.normalizeServer(s)),
 93 |     }
 94 |     return copy
 95 |   }
 96 | 
 97 |   private normalizeServer(s: ServerConfig): ServerConfig {
 98 |     const port = s.config?.port
 99 |     const normalized: ServerConfig = {
100 |       ...s,
101 |       config: {
102 |         environment: s.config?.environment ?? {},
103 |         args: s.config?.args ?? [],
104 |         ...(port ? { port } : {}),
105 |       },
106 |     }
107 |     return normalized
108 |   }
109 | 
110 |   private prepareWatcher(explicitPath?: string): void {
111 |     const isNode = Boolean((globalThis as any)?.process?.versions?.node)
112 |     if (!isNode) return
113 |     const { base, env } = EnvironmentManager.getConfigPaths('config')
114 |     this.watchPaths = []
115 |     if (explicitPath) this.watchPaths.push(explicitPath)
116 |     if (base) this.watchPaths.push(base)
117 |     if (env) this.watchPaths.push(env)
118 |     this.startWatcher()
119 |   }
120 | 
121 |   private startWatcher(): void {
122 |     const isNode = Boolean((globalThis as any)?.process?.versions?.node)
123 |     if (!isNode || this.watchPaths.length === 0) return
124 |     import('node:fs').then((fs) => {
125 |       const watchers: any[] = []
126 |       const onChange = async () => {
127 |         try {
128 |           Logger.info('Config change detected; validating and reloading...')
129 |           const prev = this.config
130 |           const newCfg = await ConfigLoader.load({ path: EnvironmentManager.getExplicitConfigPath() })
131 |           const applied = this.applyDefaults(newCfg)
132 |           if (prev) this.auditDiff(prev, applied)
133 |           this.config = applied
134 |           this.emit(this.config)
135 |         } catch (err) {
136 |           Logger.warn('Hot-reload failed to apply new config', String(err))
137 |         }
138 |       }
139 |       for (const p of this.watchPaths) {
140 |         try {
141 |           watchers.push((fs as any).watch(p, { persistent: false }, onChange))
142 |         } catch (err) {
143 |           Logger.warn(`Failed to watch ${p}`, String(err))
144 |         }
145 |       }
146 |       this.stopWatcher = () => {
147 |         for (const w of watchers) {
148 |           try {
149 |             w?.close?.()
150 |           } catch {
151 |             // ignore
152 |           }
153 |         }
154 |       }
155 |     }).catch((err) => Logger.warn('Failed to start config file watcher', String(err)))
156 |   }
157 | 
158 |   private auditDiff(oldCfg: MasterConfig, newCfg: MasterConfig): void {
159 |     const diff: Record<string, { from: unknown; to: unknown }> = {}
160 |     const keys = new Set([...Object.keys(oldCfg), ...Object.keys(newCfg)])
161 |     for (const k of keys) {
162 |       const a: any = (oldCfg as any)[k]
163 |       const b: any = (newCfg as any)[k]
164 |       if (JSON.stringify(a) !== JSON.stringify(b)) diff[k] = { from: a, to: b }
165 |     }
166 |     const redacted = this.secrets.redact(diff)
167 |     // Highlight non-hot-reloadable settings
168 |     if (oldCfg.hosting?.port !== newCfg.hosting?.port) {
169 |       Logger.warn('Hosting port changed; restart required to apply')
170 |     }
171 |     Logger.info('Config change audit', redacted)
172 |   }
173 | }
174 | 
```

--------------------------------------------------------------------------------
/docs/configuration/reference.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Configuration Reference
  2 | 
  3 | Configuration can be provided as JSON or YAML. The loader merges:
  4 | 
  5 | 1) `config/default.json`
  6 | 2) `config/<env>.json` (`MASTER_ENV` or `NODE_ENV`)
  7 | 3) Environment overrides
  8 | 4) CLI overrides
  9 | 5) Explicit file from `MASTER_CONFIG_PATH`
 10 | 
 11 | Validated against `config/schema.json` (or embedded fallback).
 12 | 
 13 | ## Top-Level Fields
 14 | 
 15 | - `master_oauth` (required): OAuth settings for the master/client tokens
 16 |   - `authorization_endpoint` (url)
 17 |   - `token_endpoint` (url)
 18 |   - `client_id` (string)
 19 |   - `client_secret` (string | `env:VAR` | `enc:gcm:...`)
 20 |   - `redirect_uri` (string)
 21 |   - `scopes` (string[])
 22 |   - `issuer?` (string)
 23 |   - `jwks_uri?` (string)
 24 |   - `audience?` (string)
 25 | - `hosting` (required)
 26 |   - `platform`: `node` | `cloudflare-workers` | `koyeb` | `docker` | `unknown`
 27 |   - `port?`: integer (Node only)
 28 |   - `base_url?`: used for OAuth redirect URL construction
 29 |   - `storage_backend?`: hints (e.g., `kv`, `durable_object`, `fs`)
 30 | - `logging`
 31 |   - `level`: `debug` | `info` | `warn` | `error`
 32 | - `routing`
 33 |   - `loadBalancer.strategy`: `round_robin` | `weighted` | `health`
 34 |   - `circuitBreaker`: `failureThreshold`, `successThreshold`, `recoveryTimeoutMs`
 35 |   - `retry`: `maxRetries`, `baseDelayMs`, `maxDelayMs`, `backoffFactor`, `jitter`, `retryOn.*`
 36 | - `security`
 37 |   - `config_key_env?`: env var name containing config secret key (defaults to `MASTER_CONFIG_KEY`)
 38 |   - `audit?`: enable config change audit logs
 39 |   - `rotation_days?`: secret rotation policy hint
 40 | - `servers` (required) — array of:
 41 |   - `id` (string)
 42 |   - `type`: `git` | `npm` | `pypi` | `docker` | `local`
 43 |   - `auth_strategy`: `master_oauth` | `delegate_oauth` | `bypass_auth` | `proxy_oauth`
 44 |   - `auth_config?`: provider-specific details
 45 |   - `config`:
 46 |     - `port?` (integer)
 47 |     - `environment?` (map)
 48 |     - `args?` (string[])
 49 | 
 50 | ## YAML Example
 51 | 
 52 | ```yaml
 53 | hosting:
 54 |   platform: node
 55 |   port: 3000
 56 | 
 57 | logging:
 58 |   level: info
 59 | 
 60 | routing:
 61 |   loadBalancer: { strategy: round_robin }
 62 |   circuitBreaker: { failureThreshold: 5, successThreshold: 2, recoveryTimeoutMs: 30000 }
 63 |   retry: { maxRetries: 2, baseDelayMs: 250, maxDelayMs: 4000, backoffFactor: 2, jitter: full }
 64 | 
 65 | master_oauth:
 66 |   authorization_endpoint: https://example.com/oauth/authorize
 67 |   token_endpoint: https://example.com/oauth/token
 68 |   client_id: master-mcp
 69 |   client_secret: env:MASTER_OAUTH_CLIENT_SECRET
 70 |   redirect_uri: http://localhost:3000/oauth/callback
 71 |   scopes: [openid]
 72 | 
 73 | servers:
 74 |   - id: tools
 75 |     type: local
 76 |     auth_strategy: bypass_auth
 77 |     config:
 78 |       port: 3333
 79 | ```
 80 | 
 81 | <!-- GENERATED:BEGIN -->
 82 | 
 83 | # Configuration Reference
 84 | 
 85 | This reference is generated from the built-in JSON Schema used by the server to validate configuration.
 86 | 
 87 | ## Top-Level Fields
 88 | 
 89 | - `master_oauth` (required) — type: object
 90 |   - `master_oauth.issuer` — type: string
 91 |   - `master_oauth.authorization_endpoint` (required) — type: string, format: url
 92 |   - `master_oauth.token_endpoint` (required) — type: string, format: url
 93 |   - `master_oauth.jwks_uri` — type: string
 94 |   - `master_oauth.client_id` (required) — type: string
 95 |   - `master_oauth.client_secret` — type: string
 96 |   - `master_oauth.redirect_uri` (required) — type: string
 97 |   - `master_oauth.scopes` (required) — type: array
 98 |       - items: — type: string
 99 |   - `master_oauth.audience` — type: string
100 | - `hosting` (required) — type: object
101 |   - `hosting.platform` (required) — type: string, enum: node, cloudflare-workers, koyeb, docker, unknown
102 |   - `hosting.port` — type: number, format: integer
103 |   - `hosting.base_url` — type: string
104 | - `logging` — type: object
105 |   - `logging.level` — type: string, enum: debug, info, warn, error
106 | - `routing` — type: object
107 |   - `routing.loadBalancer` — type: object
108 |     - `routing.loadBalancer.strategy` — type: string
109 |   - `routing.circuitBreaker` — type: object
110 |   - `routing.retry` — type: object
111 | - `servers` (required) — type: array
112 |     - items: — type: object
113 |       - `servers[].id` (required) — type: string
114 |       - `servers[].type` (required) — type: string, enum: git, npm, pypi, docker, local
115 |       - `servers[].url` — type: string
116 |       - `servers[].package` — type: string
117 |       - `servers[].version` — type: string
118 |       - `servers[].branch` — type: string
119 |       - `servers[].auth_strategy` (required) — type: string, enum: master_oauth, delegate_oauth, bypass_auth, proxy_oauth
120 |       - `servers[].auth_config` — type: object
121 |       - `servers[].config` (required) — type: object
122 |         - `servers[].config.environment` — type: object
123 |         - `servers[].config.args` — type: array
124 |             - items: — type: string
125 |         - `servers[].config.port` — type: number, format: integer
126 | 
127 | 
128 | ## Examples
129 | 
130 | ## Example: basic.yaml
131 | 
132 | ```yaml
133 | hosting:
134 |   platform: node
135 |   port: 3000
136 | 
137 | master_oauth:
138 |   authorization_endpoint: https://example.com/auth
139 |   token_endpoint: https://example.com/token
140 |   client_id: demo-client
141 |   redirect_uri: http://localhost:3000/callback
142 |   scopes:
143 |     - openid
144 | 
145 | servers:
146 |   - id: example
147 |     type: local
148 |     auth_strategy: master_oauth
149 |     config:
150 |       environment: {}
151 |       args: []
152 |       port: 3333
153 | 
154 | 
155 | ```
156 | 
157 | ## Example: simple-setup.yaml
158 | 
159 | ```yaml
160 | hosting:
161 |   platform: node
162 |   port: 3000
163 | 
164 | master_oauth:
165 |   authorization_endpoint: https://auth.example.com/authorize
166 |   token_endpoint: https://auth.example.com/token
167 |   client_id: master-mcp
168 |   redirect_uri: http://localhost:3000/oauth/callback
169 |   scopes: [openid, profile]
170 | 
171 | servers:
172 |   - id: local-simple
173 |     type: local
174 |     auth_strategy: bypass_auth
175 |     config:
176 |       port: 4001
177 |       environment: {}
178 | 
179 | 
180 | ```
181 | 
182 | 
183 | <!-- GENERATED:END -->
```

--------------------------------------------------------------------------------
/tests/servers/test-streaming-both.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | import { spawn } from 'node:child_process'
  4 | import { Client } from '@modelcontextprotocol/sdk/client/index.js'
  5 | import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
  6 | 
  7 | async function startHttpServer() {
  8 |   console.log('Starting HTTP test server...')
  9 |   
 10 |   // Start the HTTP server as a background process
 11 |   const httpServer = spawn('node', ['examples/test-mcp-server.js'], {
 12 |     stdio: ['ignore', 'pipe', 'pipe'],
 13 |     env: { ...process.env, PORT: '3006' }
 14 |   })
 15 |   
 16 |   // Capture stdout and stderr
 17 |   httpServer.stdout.on('data', (data) => {
 18 |     console.log(`[HTTP Server] ${data.toString().trim()}`)
 19 |   })
 20 |   
 21 |   httpServer.stderr.on('data', (data) => {
 22 |     console.error(`[HTTP Server ERROR] ${data.toString().trim()}`)
 23 |   })
 24 |   
 25 |   // Wait a moment for the server to start
 26 |   await new Promise(resolve => setTimeout(resolve, 2000))
 27 |   
 28 |   return httpServer
 29 | }
 30 | 
 31 | async function runStreamingTest() {
 32 |   try {
 33 |     console.log('Testing Master MCP Server with HTTP Streaming...')
 34 |     
 35 |     // Create a streamable HTTP transport to connect to our MCP server
 36 |     const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3005/mcp'))
 37 |     
 38 |     // Create the MCP client
 39 |     const client = new Client({
 40 |       name: 'master-mcp-streaming-test-client',
 41 |       version: '1.0.0'
 42 |     })
 43 |     
 44 |     // Initialize the client
 45 |     await client.connect(transport)
 46 |     console.log('✅ Server initialized with streaming transport')
 47 |     console.log('Server info:', client.getServerVersion())
 48 |     console.log('Server capabilities:', client.getServerCapabilities())
 49 |     
 50 |     // List tools using streaming
 51 |     console.log('\n--- Testing tools/list with streaming ---')
 52 |     const toolsResult = await client.listTools({})
 53 |     console.log('✅ tools/list successful with streaming')
 54 |     console.log('Number of tools:', toolsResult.tools.length)
 55 |     console.log('Tools:', toolsResult.tools.map(t => t.name))
 56 |     
 57 |     // List resources using streaming
 58 |     console.log('\n--- Testing resources/list with streaming ---')
 59 |     const resourcesResult = await client.listResources({})
 60 |     console.log('✅ resources/list successful with streaming')
 61 |     console.log('Number of resources:', resourcesResult.resources.length)
 62 |     console.log('Resources:', resourcesResult.resources.map(r => r.uri))
 63 |     
 64 |     // Test ping
 65 |     console.log('\n--- Testing ping with streaming ---')
 66 |     const pingResult = await client.ping()
 67 |     console.log('✅ ping successful with streaming')
 68 |     console.log('Ping result:', pingResult)
 69 |     
 70 |     // Try calling a tool from the HTTP server
 71 |     console.log('\n--- Testing tool call to HTTP server ---')
 72 |     try {
 73 |       const httpToolCallResult = await client.callTool({
 74 |         name: 'test-server.echo',  // Prefixed with server ID
 75 |         arguments: { message: 'Hello from HTTP server!' }
 76 |       })
 77 |       console.log('✅ HTTP tool call successful')
 78 |       console.log('HTTP tool result:', JSON.stringify(httpToolCallResult, null, 2))
 79 |     } catch (error) {
 80 |       console.log('⚠️ HTTP tool call failed (might not be available):', error.message)
 81 |     }
 82 |     
 83 |     // Try calling a tool from the STDIO server
 84 |     console.log('\n--- Testing tool call to STDIO server ---')
 85 |     try {
 86 |       const stdioToolCallResult = await client.callTool({
 87 |         name: 'stdio-server.stdio-echo',  // Prefixed with server ID
 88 |         arguments: { message: 'Hello from STDIO server!' }
 89 |       })
 90 |       console.log('✅ STDIO tool call successful')
 91 |       console.log('STDIO tool result:', JSON.stringify(stdioToolCallResult, null, 2))
 92 |     } catch (error) {
 93 |       console.log('⚠️ STDIO tool call failed (might not be available):', error.message)
 94 |     }
 95 |     
 96 |     // Try reading a resource from the HTTP server
 97 |     console.log('\n--- Testing resource read from HTTP server ---')
 98 |     try {
 99 |       const httpResourceResult = await client.readResource({
100 |         uri: 'test://example'  // This should be prefixed with server ID if needed
101 |       })
102 |       console.log('✅ HTTP resource read successful')
103 |       console.log('HTTP resource result:', JSON.stringify(httpResourceResult, null, 2))
104 |     } catch (error) {
105 |       console.log('⚠️ HTTP resource read failed (might not be available):', error.message)
106 |     }
107 |     
108 |     // Try reading a resource from the STDIO server
109 |     console.log('\n--- Testing resource read from STDIO server ---')
110 |     try {
111 |       const stdioResourceResult = await client.readResource({
112 |         uri: 'stdio-server.stdio://example/resource'  // Prefixed with server ID
113 |       })
114 |       console.log('✅ STDIO resource read successful')
115 |       console.log('STDIO resource result:', JSON.stringify(stdioResourceResult, null, 2))
116 |     } catch (error) {
117 |       console.log('⚠️ STDIO resource read failed (might not be available):', error.message)
118 |     }
119 |     
120 |     // Close the connection
121 |     await client.close()
122 |     console.log('\n✅ Disconnected from MCP server')
123 |     console.log('\n🎉 All streaming tests completed successfully!')
124 |     
125 |   } catch (error) {
126 |     console.error('❌ Streaming test failed:', error)
127 |     console.error('Error stack:', error.stack)
128 |   }
129 | }
130 | 
131 | async function main() {
132 |   let httpServer
133 |   
134 |   try {
135 |     // Start the HTTP server
136 |     httpServer = await startHttpServer()
137 |     
138 |     // Run the streaming test
139 |     await runStreamingTest()
140 |   } catch (error) {
141 |     console.error('Test failed:', error)
142 |   } finally {
143 |     // Clean up: kill the HTTP server
144 |     if (httpServer) {
145 |       console.log('Stopping HTTP server...')
146 |       httpServer.kill()
147 |     }
148 |   }
149 | }
150 | 
151 | // Run the test
152 | main()
```

--------------------------------------------------------------------------------
/src/config/environment-manager.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { HostingConfig, MasterConfig } from '../types/config.js'
  2 | import { Logger } from '../utils/logger.js'
  3 | 
  4 | export type EnvironmentName = 'development' | 'staging' | 'production' | 'test'
  5 | 
  6 | function isNode(): boolean {
  7 |   return Boolean((globalThis as any)?.process?.versions?.node)
  8 | }
  9 | 
 10 | export class EnvironmentManager {
 11 |   static detectEnvironment(): EnvironmentName {
 12 |     const env = ((globalThis as any)?.process?.env?.MASTER_ENV ||
 13 |       (globalThis as any)?.process?.env?.NODE_ENV ||
 14 |       'development') as string
 15 |     const normalized = env.toLowerCase()
 16 |     if (normalized === 'prod') return 'production'
 17 |     if (normalized === 'stage' || normalized === 'staging') return 'staging'
 18 |     if (normalized === 'test') return 'test'
 19 |     return 'development'
 20 |   }
 21 | 
 22 |   static detectPlatform(): HostingConfig['platform'] {
 23 |     if (isNode()) return 'node'
 24 |     // Heuristic: in CF workers, 'WebSocketPair' and 'navigator' often exist
 25 |     // We default to workers if Node.js globals are absent
 26 |     return 'cloudflare-workers'
 27 |   }
 28 | 
 29 |   static getConfigPaths(baseDir = 'config'): { base?: string; env?: string; schema?: string } {
 30 |     const env = this.detectEnvironment()
 31 |     return {
 32 |       base: `${baseDir}/default.json`,
 33 |       env: `${baseDir}/${env}.json`,
 34 |       schema: `${baseDir}/schema.json`,
 35 |     }
 36 |   }
 37 | 
 38 |   static getExplicitConfigPath(): string | undefined {
 39 |     const fromEnv = (globalThis as any)?.process?.env?.MASTER_CONFIG_PATH
 40 |     const fromArg = isNode() ? EnvironmentManager.parseCliArgs().configPath : undefined
 41 |     return (fromArg as string | undefined) || (fromEnv as string | undefined)
 42 |   }
 43 | 
 44 |   static parseCliArgs(): { [k: string]: unknown; configPath?: string } {
 45 |     if (!isNode()) return {}
 46 |     const args = (process.argv || []).slice(2)
 47 |     const result: Record<string, unknown> = {}
 48 |     for (const a of args) {
 49 |       if (!a.startsWith('--')) continue
 50 |       const eq = a.indexOf('=')
 51 |       let key = a
 52 |       let val: unknown = true
 53 |       if (eq > -1) {
 54 |         key = a.slice(0, eq)
 55 |         const raw = a.slice(eq + 1)
 56 |         try {
 57 |           val = JSON.parse(raw)
 58 |         } catch {
 59 |           val = raw
 60 |         }
 61 |       }
 62 |       key = key.replace(/^--/, '')
 63 |       if (key === 'config' || key === 'config-path') {
 64 |         ;(result as any).configPath = String(val)
 65 |         continue
 66 |       }
 67 |       // Support dotted keys: --hosting.port=4000
 68 |       setByPath(result, key, val)
 69 |     }
 70 |     return result
 71 |   }
 72 | 
 73 |   static loadEnvOverrides(): Partial<MasterConfig> {
 74 |     // Map env vars to config fields. All are optional overrides.
 75 |     const env = (globalThis as any)?.process?.env ?? {}
 76 |     const hosting: Partial<HostingConfig> = {}
 77 |     if (env.MASTER_HOSTING_PLATFORM) hosting.platform = env.MASTER_HOSTING_PLATFORM as HostingConfig['platform']
 78 |     if (env.MASTER_HOSTING_PORT) hosting.port = Number(env.MASTER_HOSTING_PORT)
 79 |     if (env.MASTER_BASE_URL) hosting.base_url = String(env.MASTER_BASE_URL)
 80 | 
 81 |     const logging: Partial<NonNullable<MasterConfig['logging']>> = {}
 82 |     if (env.MASTER_LOG_LEVEL) logging.level = env.MASTER_LOG_LEVEL as any
 83 | 
 84 |     const master_oauth: Partial<MasterConfig['master_oauth']> = {}
 85 |     if (env.MASTER_OAUTH_ISSUER) master_oauth.issuer = String(env.MASTER_OAUTH_ISSUER)
 86 |     if (env.MASTER_OAUTH_AUTHORIZATION_ENDPOINT)
 87 |       master_oauth.authorization_endpoint = String(env.MASTER_OAUTH_AUTHORIZATION_ENDPOINT)
 88 |     if (env.MASTER_OAUTH_TOKEN_ENDPOINT) master_oauth.token_endpoint = String(env.MASTER_OAUTH_TOKEN_ENDPOINT)
 89 |     if (env.MASTER_OAUTH_JWKS_URI) master_oauth.jwks_uri = String(env.MASTER_OAUTH_JWKS_URI)
 90 |     if (env.MASTER_OAUTH_CLIENT_ID) master_oauth.client_id = String(env.MASTER_OAUTH_CLIENT_ID)
 91 |     if (env.MASTER_OAUTH_CLIENT_SECRET) master_oauth.client_secret = `env:MASTER_OAUTH_CLIENT_SECRET`
 92 |     if (env.MASTER_OAUTH_REDIRECT_URI) master_oauth.redirect_uri = String(env.MASTER_OAUTH_REDIRECT_URI)
 93 |     if (env.MASTER_OAUTH_SCOPES) master_oauth.scopes = String(env.MASTER_OAUTH_SCOPES)
 94 |       .split(',')
 95 |       .map((s) => s.trim())
 96 |       .filter(Boolean)
 97 |     if (env.MASTER_OAUTH_AUDIENCE) master_oauth.audience = String(env.MASTER_OAUTH_AUDIENCE)
 98 | 
 99 |     // Servers can be provided as JSON in MASTER_SERVERS or YAML in MASTER_SERVERS_YAML
100 |     let servers: MasterConfig['servers'] | undefined
101 |     try {
102 |       if (env.MASTER_SERVERS) servers = JSON.parse(String(env.MASTER_SERVERS))
103 |     } catch (err) {
104 |       Logger.warn('Failed to parse MASTER_SERVERS JSON; ignoring', String(err))
105 |     }
106 |     if (!servers && env.MASTER_SERVERS_YAML) {
107 |       try {
108 |         // External import avoided in workers; only parse if Node
109 |         const YAML = isNode() ? (require('yaml') as typeof import('yaml')) : undefined
110 |         if (YAML) servers = YAML.parse(String(env.MASTER_SERVERS_YAML))
111 |       } catch (err) {
112 |         Logger.warn('Failed to parse MASTER_SERVERS_YAML; ignoring', String(err))
113 |       }
114 |     }
115 | 
116 |     const override: Partial<MasterConfig> = {}
117 |     if (Object.keys(hosting).length) (override as any).hosting = hosting
118 |     if (Object.keys(logging).length) (override as any).logging = logging
119 |     if (Object.keys(master_oauth).length) (override as any).master_oauth = master_oauth
120 |     if (servers) (override as any).servers = servers
121 |     return override
122 |   }
123 | }
124 | 
125 | function setByPath(target: Record<string, unknown>, dottedKey: string, value: unknown): void {
126 |   const parts = dottedKey.split('.')
127 |   let cur: any = target
128 |   for (let i = 0; i < parts.length - 1; i++) {
129 |     const p = parts[i]
130 |     if (typeof cur[p] !== 'object' || cur[p] === null) cur[p] = {}
131 |     cur = cur[p]
132 |   }
133 |   cur[parts[parts.length - 1]] = value
134 | }
135 | 
136 | 
```

--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/vue.js:
--------------------------------------------------------------------------------

```javascript
  1 | import {
  2 |   BaseTransition,
  3 |   BaseTransitionPropsValidators,
  4 |   Comment,
  5 |   DeprecationTypes,
  6 |   EffectScope,
  7 |   ErrorCodes,
  8 |   ErrorTypeStrings,
  9 |   Fragment,
 10 |   KeepAlive,
 11 |   ReactiveEffect,
 12 |   Static,
 13 |   Suspense,
 14 |   Teleport,
 15 |   Text,
 16 |   TrackOpTypes,
 17 |   Transition,
 18 |   TransitionGroup,
 19 |   TriggerOpTypes,
 20 |   VueElement,
 21 |   assertNumber,
 22 |   callWithAsyncErrorHandling,
 23 |   callWithErrorHandling,
 24 |   camelize,
 25 |   capitalize,
 26 |   cloneVNode,
 27 |   compatUtils,
 28 |   compile,
 29 |   computed,
 30 |   createApp,
 31 |   createBaseVNode,
 32 |   createBlock,
 33 |   createCommentVNode,
 34 |   createElementBlock,
 35 |   createHydrationRenderer,
 36 |   createPropsRestProxy,
 37 |   createRenderer,
 38 |   createSSRApp,
 39 |   createSlots,
 40 |   createStaticVNode,
 41 |   createTextVNode,
 42 |   createVNode,
 43 |   customRef,
 44 |   defineAsyncComponent,
 45 |   defineComponent,
 46 |   defineCustomElement,
 47 |   defineEmits,
 48 |   defineExpose,
 49 |   defineModel,
 50 |   defineOptions,
 51 |   defineProps,
 52 |   defineSSRCustomElement,
 53 |   defineSlots,
 54 |   devtools,
 55 |   effect,
 56 |   effectScope,
 57 |   getCurrentInstance,
 58 |   getCurrentScope,
 59 |   getCurrentWatcher,
 60 |   getTransitionRawChildren,
 61 |   guardReactiveProps,
 62 |   h,
 63 |   handleError,
 64 |   hasInjectionContext,
 65 |   hydrate,
 66 |   hydrateOnIdle,
 67 |   hydrateOnInteraction,
 68 |   hydrateOnMediaQuery,
 69 |   hydrateOnVisible,
 70 |   initCustomFormatter,
 71 |   initDirectivesForSSR,
 72 |   inject,
 73 |   isMemoSame,
 74 |   isProxy,
 75 |   isReactive,
 76 |   isReadonly,
 77 |   isRef,
 78 |   isRuntimeOnly,
 79 |   isShallow,
 80 |   isVNode,
 81 |   markRaw,
 82 |   mergeDefaults,
 83 |   mergeModels,
 84 |   mergeProps,
 85 |   nextTick,
 86 |   normalizeClass,
 87 |   normalizeProps,
 88 |   normalizeStyle,
 89 |   onActivated,
 90 |   onBeforeMount,
 91 |   onBeforeUnmount,
 92 |   onBeforeUpdate,
 93 |   onDeactivated,
 94 |   onErrorCaptured,
 95 |   onMounted,
 96 |   onRenderTracked,
 97 |   onRenderTriggered,
 98 |   onScopeDispose,
 99 |   onServerPrefetch,
100 |   onUnmounted,
101 |   onUpdated,
102 |   onWatcherCleanup,
103 |   openBlock,
104 |   popScopeId,
105 |   provide,
106 |   proxyRefs,
107 |   pushScopeId,
108 |   queuePostFlushCb,
109 |   reactive,
110 |   readonly,
111 |   ref,
112 |   registerRuntimeCompiler,
113 |   render,
114 |   renderList,
115 |   renderSlot,
116 |   resolveComponent,
117 |   resolveDirective,
118 |   resolveDynamicComponent,
119 |   resolveFilter,
120 |   resolveTransitionHooks,
121 |   setBlockTracking,
122 |   setDevtoolsHook,
123 |   setTransitionHooks,
124 |   shallowReactive,
125 |   shallowReadonly,
126 |   shallowRef,
127 |   ssrContextKey,
128 |   ssrUtils,
129 |   stop,
130 |   toDisplayString,
131 |   toHandlerKey,
132 |   toHandlers,
133 |   toRaw,
134 |   toRef,
135 |   toRefs,
136 |   toValue,
137 |   transformVNodeArgs,
138 |   triggerRef,
139 |   unref,
140 |   useAttrs,
141 |   useCssModule,
142 |   useCssVars,
143 |   useHost,
144 |   useId,
145 |   useModel,
146 |   useSSRContext,
147 |   useShadowRoot,
148 |   useSlots,
149 |   useTemplateRef,
150 |   useTransitionState,
151 |   vModelCheckbox,
152 |   vModelDynamic,
153 |   vModelRadio,
154 |   vModelSelect,
155 |   vModelText,
156 |   vShow,
157 |   version,
158 |   warn,
159 |   watch,
160 |   watchEffect,
161 |   watchPostEffect,
162 |   watchSyncEffect,
163 |   withAsyncContext,
164 |   withCtx,
165 |   withDefaults,
166 |   withDirectives,
167 |   withKeys,
168 |   withMemo,
169 |   withModifiers,
170 |   withScopeId
171 | } from "./chunk-HVR2FF6M.js";
172 | export {
173 |   BaseTransition,
174 |   BaseTransitionPropsValidators,
175 |   Comment,
176 |   DeprecationTypes,
177 |   EffectScope,
178 |   ErrorCodes,
179 |   ErrorTypeStrings,
180 |   Fragment,
181 |   KeepAlive,
182 |   ReactiveEffect,
183 |   Static,
184 |   Suspense,
185 |   Teleport,
186 |   Text,
187 |   TrackOpTypes,
188 |   Transition,
189 |   TransitionGroup,
190 |   TriggerOpTypes,
191 |   VueElement,
192 |   assertNumber,
193 |   callWithAsyncErrorHandling,
194 |   callWithErrorHandling,
195 |   camelize,
196 |   capitalize,
197 |   cloneVNode,
198 |   compatUtils,
199 |   compile,
200 |   computed,
201 |   createApp,
202 |   createBlock,
203 |   createCommentVNode,
204 |   createElementBlock,
205 |   createBaseVNode as createElementVNode,
206 |   createHydrationRenderer,
207 |   createPropsRestProxy,
208 |   createRenderer,
209 |   createSSRApp,
210 |   createSlots,
211 |   createStaticVNode,
212 |   createTextVNode,
213 |   createVNode,
214 |   customRef,
215 |   defineAsyncComponent,
216 |   defineComponent,
217 |   defineCustomElement,
218 |   defineEmits,
219 |   defineExpose,
220 |   defineModel,
221 |   defineOptions,
222 |   defineProps,
223 |   defineSSRCustomElement,
224 |   defineSlots,
225 |   devtools,
226 |   effect,
227 |   effectScope,
228 |   getCurrentInstance,
229 |   getCurrentScope,
230 |   getCurrentWatcher,
231 |   getTransitionRawChildren,
232 |   guardReactiveProps,
233 |   h,
234 |   handleError,
235 |   hasInjectionContext,
236 |   hydrate,
237 |   hydrateOnIdle,
238 |   hydrateOnInteraction,
239 |   hydrateOnMediaQuery,
240 |   hydrateOnVisible,
241 |   initCustomFormatter,
242 |   initDirectivesForSSR,
243 |   inject,
244 |   isMemoSame,
245 |   isProxy,
246 |   isReactive,
247 |   isReadonly,
248 |   isRef,
249 |   isRuntimeOnly,
250 |   isShallow,
251 |   isVNode,
252 |   markRaw,
253 |   mergeDefaults,
254 |   mergeModels,
255 |   mergeProps,
256 |   nextTick,
257 |   normalizeClass,
258 |   normalizeProps,
259 |   normalizeStyle,
260 |   onActivated,
261 |   onBeforeMount,
262 |   onBeforeUnmount,
263 |   onBeforeUpdate,
264 |   onDeactivated,
265 |   onErrorCaptured,
266 |   onMounted,
267 |   onRenderTracked,
268 |   onRenderTriggered,
269 |   onScopeDispose,
270 |   onServerPrefetch,
271 |   onUnmounted,
272 |   onUpdated,
273 |   onWatcherCleanup,
274 |   openBlock,
275 |   popScopeId,
276 |   provide,
277 |   proxyRefs,
278 |   pushScopeId,
279 |   queuePostFlushCb,
280 |   reactive,
281 |   readonly,
282 |   ref,
283 |   registerRuntimeCompiler,
284 |   render,
285 |   renderList,
286 |   renderSlot,
287 |   resolveComponent,
288 |   resolveDirective,
289 |   resolveDynamicComponent,
290 |   resolveFilter,
291 |   resolveTransitionHooks,
292 |   setBlockTracking,
293 |   setDevtoolsHook,
294 |   setTransitionHooks,
295 |   shallowReactive,
296 |   shallowReadonly,
297 |   shallowRef,
298 |   ssrContextKey,
299 |   ssrUtils,
300 |   stop,
301 |   toDisplayString,
302 |   toHandlerKey,
303 |   toHandlers,
304 |   toRaw,
305 |   toRef,
306 |   toRefs,
307 |   toValue,
308 |   transformVNodeArgs,
309 |   triggerRef,
310 |   unref,
311 |   useAttrs,
312 |   useCssModule,
313 |   useCssVars,
314 |   useHost,
315 |   useId,
316 |   useModel,
317 |   useSSRContext,
318 |   useShadowRoot,
319 |   useSlots,
320 |   useTemplateRef,
321 |   useTransitionState,
322 |   vModelCheckbox,
323 |   vModelDynamic,
324 |   vModelRadio,
325 |   vModelSelect,
326 |   vModelText,
327 |   vShow,
328 |   version,
329 |   warn,
330 |   watch,
331 |   watchEffect,
332 |   watchPostEffect,
333 |   watchSyncEffect,
334 |   withAsyncContext,
335 |   withCtx,
336 |   withDefaults,
337 |   withDirectives,
338 |   withKeys,
339 |   withMemo,
340 |   withModifiers,
341 |   withScopeId
342 | };
343 | //# sourceMappingURL=vue.js.map
344 | 
```

--------------------------------------------------------------------------------
/src/config/config-loader.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { MasterConfig } from '../types/config.js'
  2 | import { EnvironmentManager } from './environment-manager.js'
  3 | import { SecretManager } from './secret-manager.js'
  4 | import { SchemaValidator } from './schema-validator.js'
  5 | import { Logger } from '../utils/logger.js'
  6 | 
  7 | type LoadOptions = {
  8 |   // Explicit path to config file; when provided, overrides environment-based discovery
  9 |   path?: string
 10 |   // Optional base directory for default and env configs
 11 |   baseDir?: string
 12 |   // Provide a schema path override
 13 |   schemaPath?: string
 14 | }
 15 | 
 16 | function isNode(): boolean {
 17 |   return Boolean((globalThis as any)?.process?.versions?.node)
 18 | }
 19 | 
 20 | export class ConfigLoader {
 21 | 
 22 |   static async load(options?: LoadOptions): Promise<MasterConfig> {
 23 |     const envName = EnvironmentManager.detectEnvironment()
 24 |     const platform = EnvironmentManager.detectPlatform()
 25 |     const explicit = options?.path ?? EnvironmentManager.getExplicitConfigPath()
 26 |     const baseDir = options?.baseDir ?? 'config'
 27 |     const paths = EnvironmentManager.getConfigPaths(baseDir)
 28 |     const schemaPath = options?.schemaPath ?? paths.schema
 29 | 
 30 |     let fileConfig: Partial<MasterConfig> = {}
 31 |     const loadedFiles: string[] = []
 32 | 
 33 |     if (explicit && isNode()) {
 34 |       const cfg = await this.loadFromFile(explicit)
 35 |       fileConfig = deepMerge(fileConfig, cfg)
 36 |       loadedFiles.push(explicit)
 37 |     } else if (isNode()) {
 38 |       // Load default.json then <env>.json if present
 39 |       const fs = await import('node:fs/promises')
 40 |       const fsc = await import('node:fs')
 41 |       if (paths.base && fsc.existsSync(paths.base)) {
 42 |         fileConfig = deepMerge(fileConfig, await this.loadFromFile(paths.base))
 43 |         loadedFiles.push(paths.base)
 44 |       }
 45 |       if (paths.env && fsc.existsSync(paths.env)) {
 46 |         fileConfig = deepMerge(fileConfig, await this.loadFromFile(paths.env))
 47 |         loadedFiles.push(paths.env)
 48 |       }
 49 |       // If nothing loaded and config dir doesn't exist, try a default path
 50 |       ;(void fs)
 51 |     }
 52 | 
 53 |     Logger.info('File config loaded', { fileConfig, loadedFiles })
 54 | 
 55 |     // Environment variables
 56 |     const envOverrides = EnvironmentManager.loadEnvOverrides()
 57 |     Logger.info('Environment overrides', { envOverrides })
 58 |     fileConfig = deepMerge(fileConfig, envOverrides)
 59 | 
 60 |     // CLI args nested overrides
 61 |     const cli = EnvironmentManager.parseCliArgs()
 62 |     Logger.info('CLI args', { cli })
 63 |     fileConfig = deepMerge(fileConfig, cli as any)
 64 | 
 65 |     // Ensure hosting.platform and env awareness
 66 |     const normalized: Partial<MasterConfig> = {
 67 |       ...fileConfig,
 68 |       hosting: { ...fileConfig.hosting, platform },
 69 |     }
 70 | 
 71 |     Logger.info('Normalized config', { normalized })
 72 | 
 73 |     // Schema validation and secret resolution
 74 |     const schema = await SchemaValidator.loadSchema(schemaPath)
 75 |     const validated = SchemaValidator.assertValid<MasterConfig>(normalized, schema!)
 76 |     const secrets = new SecretManager()
 77 |     const resolved = secrets.resolveSecrets(validated)
 78 | 
 79 |     // Cache with key based on env and paths
 80 |     // In-memory caching can be added if needed; omitted to keep memory footprint small
 81 | 
 82 |     Logger.info('Configuration loaded', {
 83 |       files: loadedFiles,
 84 |       platform,
 85 |       env: envName,
 86 |     })
 87 |     return resolved
 88 |   }
 89 | 
 90 |   static async loadFromFile(filePath: string): Promise<Partial<MasterConfig>> {
 91 |     if (!isNode()) throw new Error('File loading is only supported in Node.js runtime')
 92 |     const fs = await import('node:fs/promises')
 93 |     const path = await import('node:path')
 94 |     const raw = await fs.readFile(filePath, 'utf8')
 95 |     Logger.info('Loading config from file', { filePath, raw })
 96 |     const ext = path.extname(filePath).toLowerCase()
 97 |     let parsed: any
 98 |     if (ext === '.json') parsed = JSON.parse(raw)
 99 |     else if (ext === '.yaml' || ext === '.yml') parsed = (await import('yaml')).parse(raw)
100 |     else {
101 |       // Fallback: try JSON then YAML
102 |       try {
103 |         parsed = JSON.parse(raw)
104 |       } catch {
105 |         parsed = (await import('yaml')).parse(raw)
106 |       }
107 |     }
108 |     Logger.info('Parsed config from file', { filePath, parsed })
109 |     return parsed as Partial<MasterConfig>
110 |   }
111 | 
112 |   static async loadFromEnv(): Promise<MasterConfig> {
113 |     // For compatibility with older phases
114 |     const override = EnvironmentManager.loadEnvOverrides()
115 |     const defaults: Partial<MasterConfig> = {
116 |       hosting: {
117 |         platform: EnvironmentManager.detectPlatform(),
118 |         port: (globalThis as any)?.process?.env?.PORT ? Number((globalThis as any)?.process?.env?.PORT) : 3000,
119 |         base_url: (globalThis as any)?.process?.env?.BASE_URL,
120 |       },
121 |       servers: [],
122 |       master_oauth: {
123 |         authorization_endpoint: 'https://example.com/auth',
124 |         token_endpoint: 'https://example.com/token',
125 |         client_id: 'placeholder',
126 |         redirect_uri: 'http://localhost/callback',
127 |         scopes: ['openid'],
128 |       },
129 |     }
130 |     const merged = deepMerge(defaults, override) as MasterConfig
131 |     const schema = await SchemaValidator.loadSchema()
132 |     return SchemaValidator.assertValid(merged, schema!)
133 |   }
134 | }
135 | 
136 | function deepMerge<T>(base: T, override: Partial<T>): T {
137 |   if (Array.isArray(base) && Array.isArray(override)) return override as unknown as T
138 |   if (base && typeof base === 'object' && override && typeof override === 'object') {
139 |     const out: any = { ...(base as any) }
140 |     for (const [k, v] of Object.entries(override as any)) {
141 |       if (v === undefined) continue
142 |       if (Array.isArray(v)) out[k] = v
143 |       else if (typeof v === 'object' && v !== null) out[k] = deepMerge((base as any)[k], v)
144 |       else out[k] = v
145 |     }
146 |     return out
147 |   }
148 |   return (override as T) ?? base
149 | }
150 | 
```

--------------------------------------------------------------------------------
/src/utils/validation.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Validation and sanitization helpers with a small schema system.
  3 |  * No external dependencies; suitable for Node and Workers.
  4 |  */
  5 | 
  6 | export function isNonEmptyString(value: unknown): value is string {
  7 |   return typeof value === 'string' && value.trim().length > 0
  8 | }
  9 | 
 10 | export function isRecord(value: unknown): value is Record<string, unknown> {
 11 |   return !!value && typeof value === 'object' && !Array.isArray(value)
 12 | }
 13 | 
 14 | export function sanitizeString(input: unknown, opts?: { maxLength?: number; trim?: boolean }): string {
 15 |   let s = typeof input === 'string' ? input : String(input ?? '')
 16 |   if (opts?.trim !== false) s = s.trim()
 17 |   // Remove control characters except tab, newline, carriage return
 18 |   s = s.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, '')
 19 |   if (opts?.maxLength && s.length > opts.maxLength) s = s.slice(0, opts.maxLength)
 20 |   return s
 21 | }
 22 | 
 23 | export function sanitizeObject<T extends Record<string, unknown>>(obj: T): T {
 24 |   const dangerous = ['__proto__', 'constructor', 'prototype']
 25 |   for (const k of Object.keys(obj)) {
 26 |     if (dangerous.includes(k)) delete (obj as any)[k]
 27 |   }
 28 |   return obj
 29 | }
 30 | 
 31 | export function assert(condition: unknown, message = 'Assertion failed'): asserts condition {
 32 |   if (!condition) throw new Error(message)
 33 | }
 34 | 
 35 | export function assertString(value: unknown, message = 'Expected string'): asserts value is string {
 36 |   if (typeof value !== 'string') throw new Error(message)
 37 | }
 38 | 
 39 | export function assertNumber(value: unknown, message = 'Expected number'): asserts value is number {
 40 |   if (typeof value !== 'number' || Number.isNaN(value)) throw new Error(message)
 41 | }
 42 | 
 43 | export function assertBoolean(value: unknown, message = 'Expected boolean'): asserts value is boolean {
 44 |   if (typeof value !== 'boolean') throw new Error(message)
 45 | }
 46 | 
 47 | export type SafeParseResult<T> = { success: true; data: T } | { success: false; error: string }
 48 | 
 49 | export interface Schema<T> {
 50 |   parse(input: unknown): T
 51 |   safeParse(input: unknown): SafeParseResult<T>
 52 | }
 53 | 
 54 | function makeSchema<T>(name: string, parse: (i: unknown) => T): Schema<T> {
 55 |   return {
 56 |     parse(input: unknown): T {
 57 |       try {
 58 |         return parse(input)
 59 |       } catch (e) {
 60 |         const msg = e instanceof Error ? e.message : String(e)
 61 |         throw new Error(`${name} validation failed: ${msg}`)
 62 |       }
 63 |     },
 64 |     safeParse(input: unknown): SafeParseResult<T> {
 65 |       try {
 66 |         return { success: true, data: parse(input) }
 67 |       } catch (e) {
 68 |         return { success: false, error: e instanceof Error ? e.message : String(e) }
 69 |       }
 70 |     },
 71 |   }
 72 | }
 73 | 
 74 | export const v = {
 75 |   string: (opts?: { min?: number; max?: number; pattern?: RegExp }) =>
 76 |     makeSchema<string>('string', (i) => {
 77 |       if (typeof i !== 'string') throw new Error('not a string')
 78 |       const s = i
 79 |       if (opts?.min !== undefined && s.length < opts.min) throw new Error(`min length ${opts.min}`)
 80 |       if (opts?.max !== undefined && s.length > opts.max) throw new Error(`max length ${opts.max}`)
 81 |       if (opts?.pattern && !opts.pattern.test(s)) throw new Error('pattern mismatch')
 82 |       return s
 83 |     }),
 84 | 
 85 |   number: (opts?: { min?: number; max?: number; int?: boolean }) =>
 86 |     makeSchema<number>('number', (i) => {
 87 |       if (typeof i !== 'number' || Number.isNaN(i)) throw new Error('not a number')
 88 |       const n = i
 89 |       if (opts?.int) {
 90 |         if (!Number.isInteger(n)) throw new Error('not an integer')
 91 |       }
 92 |       if (opts?.min !== undefined && n < opts.min) throw new Error(`min ${opts.min}`)
 93 |       if (opts?.max !== undefined && n > opts.max) throw new Error(`max ${opts.max}`)
 94 |       return n
 95 |     }),
 96 | 
 97 |   boolean: () => makeSchema<boolean>('boolean', (i) => {
 98 |     if (typeof i !== 'boolean') throw new Error('not a boolean')
 99 |     return i
100 |   }),
101 | 
102 |   literal: <T extends string | number | boolean | null>(val: T) =>
103 |     makeSchema<T>('literal', (i) => {
104 |       if (i !== val) throw new Error(`expected ${String(val)}`)
105 |       return i as T
106 |     }),
107 | 
108 |   array: <T>(inner: Schema<T>, opts?: { min?: number; max?: number }) =>
109 |     makeSchema<T[]>('array', (i) => {
110 |       if (!Array.isArray(i)) throw new Error('not an array')
111 |       if (opts?.min !== undefined && i.length < opts.min) throw new Error(`min length ${opts.min}`)
112 |       if (opts?.max !== undefined && i.length > opts.max) throw new Error(`max length ${opts.max}`)
113 |       return i.map((x) => inner.parse(x))
114 |     }),
115 | 
116 |   object: <S extends Record<string, Schema<any>>>(shape: S) =>
117 |     makeSchema<{ [K in keyof S]: S[K] extends Schema<infer U> ? U : never }>('object', (i) => {
118 |       if (!isRecord(i)) throw new Error('not an object')
119 |       const out: Record<string, unknown> = {}
120 |       for (const [k, s] of Object.entries(shape)) {
121 |         out[k] = (s as Schema<unknown>).parse((i as any)[k])
122 |       }
123 |       return out as any
124 |     }),
125 | 
126 |   union: <A, B>(a: Schema<A>, b: Schema<B>) =>
127 |     makeSchema<A | B>('union', (i) => {
128 |       const ra = a.safeParse(i)
129 |       if (ra.success) return ra.data
130 |       const rb = b.safeParse(i)
131 |       if (rb.success) return rb.data
132 |       throw new Error(`no union match: ${ra.error}; ${rb.error}`)
133 |     }),
134 | 
135 |   optional: <T>(inner: Schema<T>) =>
136 |     makeSchema<T | undefined>('optional', (i) => {
137 |       if (i === undefined) return undefined
138 |       return inner.parse(i)
139 |     }),
140 | }
141 | 
142 | export function isEmail(input: string): boolean {
143 |   // Simple and conservative
144 |   return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input)
145 | }
146 | 
147 | export function isUrl(input: string): boolean {
148 |   try {
149 |     const u = new URL(input)
150 |     return u.protocol === 'http:' || u.protocol === 'https:'
151 |   } catch {
152 |     return false
153 |   }
154 | }
155 | 
156 | export function isUUID(input: string): boolean {
157 |   return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(input)
158 | }
159 | 
160 | export function safeHeaderName(name: string): boolean {
161 |   return /^[A-Za-z0-9-]+$/.test(name) && !/__/g.test(name)
162 | }
163 | 
164 | export function safeHeaderValue(value: string): boolean {
165 |   return !/[\r\n]/.test(value) && value.length < 8192
166 | }
167 | 
168 | export function validateAgainstSchema<T>(schema: Schema<T>, input: unknown): T {
169 |   return schema.parse(input)
170 | }
171 | 
172 | 
```

--------------------------------------------------------------------------------
/src/auth/multi-auth-manager.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { createRemoteJWKSet, jwtVerify } from 'jose'
  2 | import type { AuthHeaders, OAuthDelegation, OAuthToken } from '../types/auth.js'
  3 | import type { MasterAuthConfig, ServerAuthConfig } from '../types/config.js'
  4 | import { AuthStrategy } from '../types/config.js'
  5 | import { Logger } from '../utils/logger.js'
  6 | import { getOAuthProvider } from './oauth-providers.js'
  7 | import { TokenManager } from './token-manager.js'
  8 | 
  9 | export class MultiAuthManager {
 10 |   private serverAuth: Map<string, { strategy: AuthStrategy; config?: ServerAuthConfig }> = new Map()
 11 |   private jwks?: ReturnType<typeof createRemoteJWKSet>
 12 |   private tokenManager = new TokenManager()
 13 | 
 14 |   constructor(private readonly config: MasterAuthConfig) {
 15 |     if (config.jwks_uri) {
 16 |       try {
 17 |         this.jwks = createRemoteJWKSet(new URL(config.jwks_uri))
 18 |       } catch (err) {
 19 |         Logger.warn('Failed to initialize JWKS for client token validation', err)
 20 |       }
 21 |     }
 22 |   }
 23 | 
 24 |   registerServerAuth(serverId: string, strategy: AuthStrategy, authConfig?: ServerAuthConfig): void {
 25 |     this.serverAuth.set(serverId, { strategy, config: authConfig })
 26 |   }
 27 | 
 28 |   private keyFor(clientToken: string, serverId: string): string {
 29 |     return `${serverId}::${clientToken.slice(0, 16)}`
 30 |   }
 31 | 
 32 |   async validateClientToken(token: string): Promise<boolean> {
 33 |     if (!token || typeof token !== 'string') return false
 34 |     if (!this.jwks) {
 35 |       // Best-effort: check structural validity and expiration if it is a JWT; otherwise accept as opaque bearer
 36 |       try {
 37 |         const { payload } = await jwtVerify(token, async () => {
 38 |           // No key ⇒ force failure to reach catch where we treat opaque tokens as valid
 39 |           throw new Error('no-jwks')
 40 |         })
 41 |         const now = Math.floor(Date.now() / 1000)
 42 |         return typeof payload.exp !== 'number' || payload.exp > now
 43 |       } catch {
 44 |         return true // Accept opaque tokens when no JWKS is configured
 45 |       }
 46 |     }
 47 | 
 48 |     try {
 49 |       await jwtVerify(token, this.jwks, {
 50 |         issuer: this.config.issuer,
 51 |         audience: this.config.audience ?? this.config.client_id,
 52 |       })
 53 |       return true
 54 |     } catch (err) {
 55 |       Logger.warn('Client token verification failed', String(err))
 56 |       return false
 57 |     }
 58 |   }
 59 | 
 60 |   async prepareAuthForBackend(serverId: string, clientToken: string): Promise<AuthHeaders | OAuthDelegation> {
 61 |     const isValid = await this.validateClientToken(clientToken)
 62 |     if (!isValid) throw new Error('Invalid client token')
 63 | 
 64 |     const entry = this.serverAuth.get(serverId)
 65 |     if (!entry) {
 66 |       // Default: pass-through
 67 |       return { Authorization: `Bearer ${clientToken}` }
 68 |     }
 69 | 
 70 |     const { strategy, config } = entry
 71 |     switch (strategy) {
 72 |       case AuthStrategy.MASTER_OAUTH:
 73 |         return this.handleMasterOAuth(serverId, clientToken)
 74 |       case AuthStrategy.DELEGATE_OAUTH:
 75 |         if (!config) throw new Error(`Missing auth config for server ${serverId}`)
 76 |         return this.handleDelegatedOAuth(serverId, clientToken, config)
 77 |       case AuthStrategy.BYPASS_AUTH:
 78 |         return {}
 79 |       case AuthStrategy.PROXY_OAUTH:
 80 |         if (!config) throw new Error(`Missing auth config for server ${serverId}`)
 81 |         return this.handleProxyOAuth(serverId, clientToken, config)
 82 |       default:
 83 |         return { Authorization: `Bearer ${clientToken}` }
 84 |     }
 85 |   }
 86 | 
 87 |   public async handleMasterOAuth(_serverId: string, clientToken: string): Promise<AuthHeaders> {
 88 |     // Pass-through the client's master token
 89 |     return { Authorization: `Bearer ${clientToken}` }
 90 |   }
 91 | 
 92 |   public async handleDelegatedOAuth(
 93 |     serverId: string,
 94 |     clientToken: string,
 95 |     serverAuthConfig: ServerAuthConfig
 96 |   ): Promise<OAuthDelegation> {
 97 |     // Return instructions for the client to complete OAuth against the provider
 98 |     const scopes = Array.isArray(serverAuthConfig.scopes) ? serverAuthConfig.scopes : ['openid']
 99 |     // Create state binding server + client
100 |     const state = this.tokenManager.generateState({ serverId })
101 |     // Store a minimal pending marker for later exchange if needed
102 |     await this.tokenManager.storeToken(this.keyFor(clientToken, serverId), {
103 |       access_token: '',
104 |       expires_at: 0,
105 |       scope: [],
106 |     })
107 | 
108 |     return {
109 |       type: 'oauth_delegation',
110 |       auth_endpoint: serverAuthConfig.authorization_endpoint,
111 |       token_endpoint: serverAuthConfig.token_endpoint,
112 |       client_info: { client_id: serverAuthConfig.client_id, metadata: { state } },
113 |       required_scopes: scopes,
114 |       redirect_after_auth: true,
115 |     }
116 |   }
117 | 
118 |   public async handleProxyOAuth(
119 |     serverId: string,
120 |     clientToken: string,
121 |     serverAuthConfig: ServerAuthConfig
122 |   ): Promise<AuthHeaders> {
123 |     const key = this.keyFor(clientToken, serverId)
124 |     const existing = await this.tokenManager.getToken(key)
125 |     const now = Date.now()
126 |     if (existing && existing.access_token && existing.expires_at > now + 30_000) {
127 |       return { Authorization: `Bearer ${existing.access_token}` }
128 |     }
129 | 
130 |     if (existing?.refresh_token) {
131 |       try {
132 |         const provider = getOAuthProvider(serverAuthConfig as any)
133 |         const refreshed = await provider.refreshToken(existing.refresh_token)
134 |         await this.tokenManager.storeToken(key, refreshed)
135 |         return { Authorization: `Bearer ${refreshed.access_token}` }
136 |       } catch (err) {
137 |         Logger.warn('Refresh token failed; falling back to pass-through', err)
138 |       }
139 |     }
140 | 
141 |     // Fallback: pass through the client token (may be accepted by backend if configured)
142 |     return { Authorization: `Bearer ${clientToken}` }
143 |   }
144 | 
145 |   async storeDelegatedToken(clientToken: string, serverId: string, serverToken: string | OAuthToken): Promise<void> {
146 |     const key = this.keyFor(clientToken, serverId)
147 |     const tokenObj: OAuthToken = typeof serverToken === 'string'
148 |       ? { access_token: serverToken, expires_at: Date.now() + 3600_000, scope: [] }
149 |       : serverToken
150 |     await this.tokenManager.storeToken(key, tokenObj)
151 |   }
152 | 
153 |   async getStoredServerToken(serverId: string, clientToken: string): Promise<string | undefined> {
154 |     const tok = await this.tokenManager.getToken(this.keyFor(clientToken, serverId))
155 |     return tok?.access_token
156 |   }
157 | }
158 | 
```

--------------------------------------------------------------------------------
/src/utils/logger.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { AuthInfo } from '../types/auth.js'
  2 | 
  3 | export type LogLevel = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace'
  4 | 
  5 | export interface LogFields {
  6 |   [key: string]: unknown
  7 |   correlationId?: string
  8 | }
  9 | 
 10 | interface LoggerOptions {
 11 |   level?: LogLevel
 12 |   json?: boolean
 13 |   base?: LogFields
 14 | }
 15 | 
 16 | /**
 17 |  * Lightweight, structured, context-aware logger with JSON output support and
 18 |  * timing utilities. Designed to run on Node.js and Workers without deps.
 19 |  */
 20 | export class Logger {
 21 |   private static level: LogLevel = ((): LogLevel => {
 22 |     const env = (globalThis as any)?.process?.env
 23 |     const raw = (env?.LOG_LEVEL || env?.NODE_LOG_LEVEL || 'info').toLowerCase()
 24 |     const allowed: LogLevel[] = ['fatal', 'error', 'warn', 'info', 'debug', 'trace']
 25 |     return (allowed.includes(raw as LogLevel) ? (raw as LogLevel) : 'info') as LogLevel
 26 |   })()
 27 | 
 28 |   private static json: boolean = ((): boolean => {
 29 |     const env = (globalThis as any)?.process?.env
 30 |     const raw = env?.LOG_FORMAT || env?.LOG_JSON
 31 |     if (!raw) return (env?.NODE_ENV === 'production') as boolean
 32 |     return String(raw).toLowerCase() === 'true' || String(raw).toLowerCase() === 'json'
 33 |   })()
 34 | 
 35 |   private static base: LogFields = {}
 36 | 
 37 |   static configure(opts: LoggerOptions): void {
 38 |     if (opts.level) this.level = opts.level
 39 |     if (typeof opts.json === 'boolean') this.json = opts.json
 40 |     if (opts.base) this.base = { ...this.base, ...sanitizeFields(opts.base) }
 41 |   }
 42 | 
 43 |   static with(fields: LogFields): typeof Logger {
 44 |     const merged = { ...this.base, ...sanitizeFields(fields) }
 45 |     const child = new Proxy(this, {
 46 |       get: (target, prop) => {
 47 |         if (prop === 'base') return merged
 48 |         return (target as any)[prop]
 49 |       },
 50 |     }) as typeof Logger
 51 |     return child
 52 |   }
 53 | 
 54 |   static setLevel(level: LogLevel): void {
 55 |     this.level = level
 56 |   }
 57 | 
 58 |   static enableJSON(enabled: boolean): void {
 59 |     this.json = enabled
 60 |   }
 61 | 
 62 |   static getLevel(): LogLevel {
 63 |     return this.level
 64 |   }
 65 | 
 66 |   static trace(message: string, fields?: LogFields | unknown): void {
 67 |     const f = fieldsToLogFields(fields)
 68 |     this._log('trace', message, f)
 69 |   }
 70 |   static debug(message: string, fields?: LogFields | unknown): void {
 71 |     const envDebug = (globalThis as any)?.process?.env?.DEBUG
 72 |     const f = fieldsToLogFields(fields)
 73 |     if (envDebug || this.levelAllowed('debug')) this._log('debug', message, f)
 74 |   }
 75 |   static info(message: string, fields?: LogFields | unknown): void {
 76 |     const f = fieldsToLogFields(fields)
 77 |     this._log('info', message, f)
 78 |   }
 79 |   static warn(message: string, fields?: LogFields | unknown): void {
 80 |     const f = fieldsToLogFields(fields)
 81 |     this._log('warn', message, f)
 82 |   }
 83 |   static error(message: string, fields?: LogFields | unknown): void {
 84 |     const f = fieldsToLogFields(fields)
 85 |     this._log('error', message, f)
 86 |   }
 87 |   static fatal(message: string, fields?: LogFields | unknown): void {
 88 |     const f = fieldsToLogFields(fields)
 89 |     this._log('fatal', message, f)
 90 |   }
 91 | 
 92 |   /**
 93 |    * Structured auth event helper for backward compatibility.
 94 |    */
 95 |   static logAuthEvent(event: string, context: AuthInfo): void {
 96 |     this.info('auth_event', { event, ...context })
 97 |   }
 98 | 
 99 |   /**
100 |    * Structured server event helper for backward compatibility.
101 |    */
102 |   static logServerEvent(event: string, serverId: string, context?: unknown): void {
103 |     const fields = fieldsToLogFields(context)
104 |     this.info('server_event', { event, serverId, ...(fields ?? {}) })
105 |   }
106 | 
107 |   /**
108 |    * Starts a performance timer, returning a function to log completion.
109 |    *
110 |    * Usage:
111 |    * const done = Logger.time('load_config', { id })
112 |    * ...work...
113 |    * done({ status: 'ok' })
114 |    */
115 |   static time(name: string, fields?: LogFields): (extra?: LogFields) => void {
116 |     const start = now()
117 |     const base = { name, ...(fields ? sanitizeFields(fields) : {}) }
118 |     return (extra?: LogFields) => {
119 |       const durationMs = Math.max(0, now() - start)
120 |       this.info('perf', { ...base, ...(extra ? sanitizeFields(extra) : {}), durationMs })
121 |     }
122 |   }
123 | 
124 |   /**
125 |    * Low-level log method honoring level and output format.
126 |    */
127 |   private static _log(level: LogLevel, message: string, fields?: LogFields): void {
128 |     if (!this.levelAllowed(level)) return
129 |     const ts = new Date().toISOString()
130 |     const entry = {
131 |       ts,
132 |       level,
133 |       msg: message,
134 |       ...this.base,
135 |       ...(fields ? sanitizeFields(fields) : {}),
136 |     }
137 | 
138 |     // eslint-disable-next-line no-console
139 |     if (this.json) console.log(JSON.stringify(entry))
140 |     else console.log(formatHuman(entry))
141 |   }
142 | 
143 |   private static levelAllowed(check: LogLevel): boolean {
144 |     const order: LogLevel[] = ['trace', 'debug', 'info', 'warn', 'error', 'fatal']
145 |     const curIdx = order.indexOf(this.level)
146 |     const chkIdx = order.indexOf(check)
147 |     return chkIdx >= curIdx
148 |   }
149 | }
150 | 
151 | function now(): number {
152 |   if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
153 |     return performance.now()
154 |   }
155 |   return Date.now()
156 | }
157 | 
158 | function sanitizeFields(fields: LogFields): LogFields {
159 |   const out: LogFields = {}
160 |   for (const [k, v] of Object.entries(fields)) {
161 |     if (v === undefined) continue
162 |     if (v instanceof Error) {
163 |       out[k] = {
164 |         name: v.name,
165 |         message: v.message,
166 |         stack: v.stack,
167 |       }
168 |     } else if (typeof v === 'object' && v !== null) {
169 |       try {
170 |         // Avoid circular structures
171 |         out[k] = JSON.parse(JSON.stringify(v))
172 |       } catch {
173 |         out[k] = String(v)
174 |       }
175 |     } else {
176 |       out[k] = v as any
177 |     }
178 |   }
179 |   return out
180 | }
181 | 
182 | function formatHuman(entry: { [k: string]: unknown }): string {
183 |   const { ts, level, msg, ...rest } = entry as any
184 |   const head = `[${String(level).toUpperCase()}] ${ts} ${msg}`
185 |   const restKeys = Object.keys(rest)
186 |   if (restKeys.length === 0) return head
187 |   return `${head} ${safeStringify(rest)}`
188 | }
189 | 
190 | function safeStringify(obj: any): string {
191 |   try {
192 |     return JSON.stringify(obj)
193 |   } catch {
194 |     return '[object]'
195 |   }
196 | }
197 | 
198 | function fieldsToLogFields(f?: LogFields | unknown): LogFields | undefined {
199 |   if (!f) return undefined
200 |   if (typeof f === 'object' && !(f instanceof Error)) return f as LogFields
201 |   return { detail: f }
202 | }
203 | 
```

--------------------------------------------------------------------------------
/src/server/master-server.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // Phase 1: avoid hard dependency on SDK types to ensure compilation
  2 | import type { ServerCapabilities, LoadedServer } from '../types/server.js'
  3 | import type { MasterConfig, RoutingConfig, ServerConfig } from '../types/config.js'
  4 | import type { AuthHeaders, OAuthDelegation } from '../types/auth.js'
  5 | import { ProtocolHandler } from './protocol-handler.js'
  6 | import { DefaultModuleLoader } from '../modules/module-loader.js'
  7 | import { CapabilityAggregator } from '../modules/capability-aggregator.js'
  8 | import { RequestRouter } from '../modules/request-router.js'
  9 | import { Logger } from '../utils/logger.js'
 10 | import { MultiAuthManager } from '../auth/multi-auth-manager.js'
 11 | import { OAuthFlowController } from '../oauth/flow-controller.js'
 12 | 
 13 | export class MasterServer {
 14 |   readonly server: unknown
 15 |   readonly handler: ProtocolHandler
 16 | 
 17 |   private readonly loader = new DefaultModuleLoader()
 18 |   private readonly aggregator = new CapabilityAggregator()
 19 |   private readonly servers = new Map<string, LoadedServer>()
 20 |   private router!: RequestRouter
 21 |   private config?: MasterConfig
 22 |   private authManager?: MultiAuthManager
 23 |   private oauthController?: OAuthFlowController
 24 |   private getAuthHeaders: (
 25 |     serverId: string,
 26 |     clientToken?: string
 27 |   ) => Promise<AuthHeaders | OAuthDelegation | undefined>
 28 | 
 29 |   constructor(capabilities?: Partial<ServerCapabilities>, routing?: RoutingConfig) {
 30 |     const version = (globalThis as any)?.process?.env?.APP_VERSION ?? '0.1.0'
 31 |     this.server = { name: 'master-mcp-server', version }
 32 |     this.getAuthHeaders = async (_serverId: string, clientToken?: string) =>
 33 |       clientToken ? { Authorization: `Bearer ${clientToken}` } : undefined
 34 |     this.router = new RequestRouter(this.servers, this.aggregator, this.getAuthHeaders.bind(this), { routing })
 35 |     this.handler = new ProtocolHandler({ aggregator: this.aggregator, router: this.router })
 36 |     void capabilities
 37 |   }
 38 | 
 39 |   async startFromConfig(config: MasterConfig, clientToken?: string): Promise<void> {
 40 |     Logger.info('Starting MasterServer from config')
 41 |     this.config = config
 42 |     await this.loadServers(config.servers, clientToken)
 43 |     await this.discoverAllCapabilities(clientToken)
 44 |   }
 45 | 
 46 |   async loadServers(servers: ServerConfig[], clientToken?: string): Promise<void> {
 47 |     Logger.info('Loading servers', { servers })
 48 |     const loaded = await this.loader.loadServers(servers, clientToken)
 49 |     Logger.info('Loaded servers', { loaded: Array.from(loaded.entries()) })
 50 |     this.servers.clear()
 51 |     for (const [id, s] of loaded) this.servers.set(id, s)
 52 |     this.router = new RequestRouter(this.servers, this.aggregator, this.getAuthHeaders.bind(this), {
 53 |       routing: this.config?.routing,
 54 |     })
 55 |     ;(this as any).handler = new ProtocolHandler({ aggregator: this.aggregator, router: this.router })
 56 |   }
 57 | 
 58 |   async discoverAllCapabilities(clientToken?: string): Promise<void> {
 59 |     Logger.info('Discovering all capabilities', { servers: Array.from(this.servers.entries()) })
 60 |     const headersOnly = async (serverId: string, token?: string) => {
 61 |       const res = await this.getAuthHeaders(serverId, token)
 62 |       if (res && (res as OAuthDelegation).type === 'oauth_delegation') {
 63 |         return token ? { Authorization: `Bearer ${token}` } : undefined
 64 |       }
 65 |       return res as AuthHeaders | undefined
 66 |     }
 67 |     await this.aggregator.discoverCapabilities(this.servers, clientToken, headersOnly)
 68 |     Logger.info('Discovered all capabilities', { tools: this.aggregator.getAllTools(this.servers), resources: this.aggregator.getAllResources(this.servers) })
 69 |   }
 70 | 
 71 |   // Allow host app to inject an auth header strategy (e.g., MultiAuthManager)
 72 |   setAuthHeaderProvider(
 73 |     fn: (serverId: string, clientToken?: string) => Promise<AuthHeaders | OAuthDelegation | undefined>
 74 |   ): void {
 75 |     this.getAuthHeaders = fn
 76 |     this.router = new RequestRouter(this.servers, this.aggregator, this.getAuthHeaders.bind(this), {
 77 |       routing: this.config?.routing,
 78 |     })
 79 |     ;(this as any).handler = new ProtocolHandler({ aggregator: this.aggregator, router: this.router })
 80 |   }
 81 | 
 82 |   getRouter(): RequestRouter {
 83 |     return this.router
 84 |   }
 85 | 
 86 |   getAggregatedTools(): ServerCapabilities['tools'] {
 87 |     return this.aggregator.getAllTools(this.servers)
 88 |   }
 89 | 
 90 |   getAggregatedResources(): ServerCapabilities['resources'] {
 91 |     return this.aggregator.getAllResources(this.servers)
 92 |   }
 93 | 
 94 |   async performHealthChecks(clientToken?: string): Promise<Record<string, boolean>> {
 95 |     const results: Record<string, boolean> = {}
 96 |     for (const [id, s] of this.servers) {
 97 |       results[id] = await this.loader.performHealthCheck(s, clientToken)
 98 |     }
 99 |     return results
100 |   }
101 | 
102 |   async restartServer(id: string): Promise<void> {
103 |     await this.loader.restartServer(id)
104 |   }
105 | 
106 |   async unloadAll(): Promise<void> {
107 |     await Promise.all(Array.from(this.servers.keys()).map((id) => this.loader.unload(id)))
108 |     this.servers.clear()
109 |   }
110 | 
111 |   attachAuthManager(manager: MultiAuthManager): void {
112 |     this.authManager = manager
113 |     this.setAuthHeaderProvider((serverId: string, clientToken?: string) => {
114 |       if (!clientToken) return Promise.resolve(undefined)
115 |       return this.authManager!.prepareAuthForBackend(serverId, clientToken)
116 |     })
117 |   }
118 | 
119 |   // Provide an OAuthFlowController wired to the current config and auth manager.
120 |   // Host runtimes (Node/Workers) can use this to mount HTTP endpoints without coupling MasterServer to a specific HTTP framework.
121 |   getOAuthFlowController(): OAuthFlowController {
122 |     if (!this.config) throw new Error('MasterServer config not initialized')
123 |     if (!this.authManager) throw new Error('Auth manager not attached')
124 |     if (!this.oauthController) {
125 |       this.oauthController = new OAuthFlowController(
126 |         {
127 |           getConfig: () => this.config!,
128 |           storeDelegatedToken: async (clientToken, serverId, token) => {
129 |             await this.authManager!.storeDelegatedToken(clientToken, serverId, token)
130 |           },
131 |         },
132 |         '/oauth'
133 |       )
134 |     }
135 |     return this.oauthController
136 |   }
137 | 
138 |   updateRouting(routing?: RoutingConfig): void {
139 |     this.router = new RequestRouter(this.servers, this.aggregator, this.getAuthHeaders.bind(this), { routing })
140 |     ;(this as any).handler = new ProtocolHandler({ aggregator: this.aggregator, router: this.router })
141 |   }
142 | }
143 | 
```

--------------------------------------------------------------------------------
/tests/servers/test-streaming-both-complete.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | import { spawn } from 'node:child_process'
  4 | import { Client } from '@modelcontextprotocol/sdk/client/index.js'
  5 | import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
  6 | 
  7 | async function startHttpServer() {
  8 |   console.log('Starting HTTP test server...')
  9 |   
 10 |   // Start the HTTP server as a background process
 11 |   const httpServer = spawn('node', ['examples/test-mcp-server.js'], {
 12 |     stdio: ['ignore', 'pipe', 'pipe'],
 13 |     env: { ...process.env, PORT: '3006' }
 14 |   })
 15 |   
 16 |   // Capture stdout and stderr
 17 |   httpServer.stdout.on('data', (data) => {
 18 |     console.log(`[HTTP Server] ${data.toString().trim()}`)
 19 |   })
 20 |   
 21 |   httpServer.stderr.on('data', (data) => {
 22 |     console.error(`[HTTP Server ERROR] ${data.toString().trim()}`)
 23 |   })
 24 |   
 25 |   // Wait for the server to start
 26 |   await new Promise((resolve, reject) => {
 27 |     let timeout = setTimeout(() => {
 28 |       reject(new Error('HTTP server startup timeout'))
 29 |     }, 5000)
 30 |     
 31 |     httpServer.stdout.on('data', (data) => {
 32 |       if (data.toString().includes('Test MCP server listening')) {
 33 |         clearTimeout(timeout)
 34 |         resolve()
 35 |       }
 36 |     })
 37 |   })
 38 |   
 39 |   return httpServer
 40 | }
 41 | 
 42 | async function startMasterServer() {
 43 |   console.log('Starting Master MCP server...')
 44 |   
 45 |   // Start the master server as a background process
 46 |   const masterServer = spawn('npm', ['run', 'dev'], {
 47 |     stdio: ['ignore', 'pipe', 'pipe'],
 48 |     cwd: process.cwd()
 49 |   })
 50 |   
 51 |   // Capture stdout and stderr
 52 |   masterServer.stdout.on('data', (data) => {
 53 |     const output = data.toString().trim()
 54 |     // Only log important messages to avoid too much output
 55 |     if (output.includes('Master MCP listening') || output.includes('error') || output.includes('ERROR')) {
 56 |       console.log(`[Master Server] ${output}`)
 57 |     }
 58 |   })
 59 |   
 60 |   masterServer.stderr.on('data', (data) => {
 61 |     console.error(`[Master Server ERROR] ${data.toString().trim()}`)
 62 |   })
 63 |   
 64 |   // Wait for the server to start
 65 |   await new Promise((resolve, reject) => {
 66 |     let timeout = setTimeout(() => {
 67 |       reject(new Error('Master server startup timeout'))
 68 |     }, 15000)
 69 |     
 70 |     masterServer.stdout.on('data', (data) => {
 71 |       if (data.toString().includes('Master MCP listening')) {
 72 |         clearTimeout(timeout)
 73 |         console.log('[Master Server] Server is ready!')
 74 |         resolve()
 75 |       }
 76 |     })
 77 |   })
 78 |   
 79 |   return masterServer
 80 | }
 81 | 
 82 | async function runStreamingTest() {
 83 |   try {
 84 |     console.log('Testing Master MCP Server with HTTP Streaming...')
 85 |     
 86 |     // Create a streamable HTTP transport to connect to our MCP server
 87 |     const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3005/mcp'))
 88 |     
 89 |     // Create the MCP client
 90 |     const client = new Client({
 91 |       name: 'master-mcp-streaming-test-client',
 92 |       version: '1.0.0'
 93 |     })
 94 |     
 95 |     // Initialize the client
 96 |     await client.connect(transport)
 97 |     console.log('✅ Server initialized with streaming transport')
 98 |     console.log('Server info:', client.getServerVersion())
 99 |     console.log('Server capabilities:', client.getServerCapabilities())
100 |     
101 |     // List tools using streaming
102 |     console.log('\n--- Testing tools/list with streaming ---')
103 |     const toolsResult = await client.listTools({})
104 |     console.log('✅ tools/list successful with streaming')
105 |     console.log('Number of tools:', toolsResult.tools.length)
106 |     console.log('Tools:', toolsResult.tools.map(t => t.name))
107 |     
108 |     // Verify both servers are present
109 |     const hasHttpTool = toolsResult.tools.some(t => t.name === 'test-server.echo')
110 |     const hasStdioTool = toolsResult.tools.some(t => t.name === 'stdio-server.stdio-echo')
111 |     
112 |     if (hasHttpTool) {
113 |       console.log('✅ HTTP server tool found')
114 |     } else {
115 |       console.log('❌ HTTP server tool not found')
116 |     }
117 |     
118 |     if (hasStdioTool) {
119 |       console.log('✅ STDIO server tool found')
120 |     } else {
121 |       console.log('❌ STDIO server tool not found')
122 |     }
123 |     
124 |     // List resources using streaming
125 |     console.log('\n--- Testing resources/list with streaming ---')
126 |     const resourcesResult = await client.listResources({})
127 |     console.log('✅ resources/list successful with streaming')
128 |     console.log('Number of resources:', resourcesResult.resources.length)
129 |     console.log('Resources:', resourcesResult.resources.map(r => r.uri))
130 |     
131 |     // Verify both servers are present
132 |     const hasHttpResource = resourcesResult.resources.some(r => r.uri === 'test-server.test://example')
133 |     const hasStdioResource = resourcesResult.resources.some(r => r.uri === 'stdio-server.stdio://example/resource')
134 |     
135 |     if (hasHttpResource) {
136 |       console.log('✅ HTTP server resource found')
137 |     } else {
138 |       console.log('❌ HTTP server resource not found')
139 |     }
140 |     
141 |     if (hasStdioResource) {
142 |       console.log('✅ STDIO server resource found')
143 |     } else {
144 |       console.log('❌ STDIO server resource not found')
145 |     }
146 |     
147 |     // Test ping
148 |     console.log('\n--- Testing ping with streaming ---')
149 |     const pingResult = await client.ping()
150 |     console.log('✅ ping successful with streaming')
151 |     console.log('Ping result:', pingResult)
152 |     
153 |     // Summary
154 |     console.log('\n--- Test Summary ---')
155 |     if (hasHttpTool && hasStdioTool && hasHttpResource && hasStdioResource) {
156 |       console.log('🎉 All tests passed! Both HTTP and STDIO servers are working correctly.')
157 |     } else {
158 |       console.log('⚠️ Some tests failed. Check the output above for details.')
159 |     }
160 |     
161 |     // Close the connection
162 |     await client.close()
163 |     console.log('\n✅ Disconnected from MCP server')
164 |     
165 |   } catch (error) {
166 |     console.error('❌ Streaming test failed:', error)
167 |     console.error('Error stack:', error.stack)
168 |   }
169 | }
170 | 
171 | async function main() {
172 |   let httpServer, masterServer
173 |   
174 |   try {
175 |     // Start the HTTP server
176 |     httpServer = await startHttpServer()
177 |     
178 |     // Start the master server
179 |     masterServer = await startMasterServer()
180 |     
181 |     // Wait a bit for discovery to happen
182 |     console.log('Waiting for server discovery...')
183 |     await new Promise(resolve => setTimeout(resolve, 3000))
184 |     
185 |     // Run the streaming test
186 |     await runStreamingTest()
187 |   } catch (error) {
188 |     console.error('Test failed:', error)
189 |   } finally {
190 |     // Clean up: kill the servers
191 |     if (httpServer) {
192 |       console.log('Stopping HTTP server...')
193 |       httpServer.kill()
194 |     }
195 |     if (masterServer) {
196 |       console.log('Stopping Master server...')
197 |       masterServer.kill()
198 |     }
199 |   }
200 | }
201 | 
202 | // Run the test
203 | main()
```

--------------------------------------------------------------------------------
/src/config/schema-validator.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { MasterConfig } from '../types/config.js'
  2 | import { Logger } from '../utils/logger.js'
  3 | 
  4 | type JSONSchema = {
  5 |   $id?: string
  6 |   type?: string | string[]
  7 |   properties?: Record<string, JSONSchema>
  8 |   required?: string[]
  9 |   additionalProperties?: boolean
 10 |   enum?: unknown[]
 11 |   items?: JSONSchema
 12 |   format?: 'url' | 'secret' | 'integer'
 13 |   anyOf?: JSONSchema[]
 14 |   allOf?: JSONSchema[]
 15 |   description?: string
 16 | }
 17 | 
 18 | export interface SchemaValidationError {
 19 |   path: string
 20 |   message: string
 21 | }
 22 | 
 23 | export class SchemaValidator {
 24 |   // Lightweight JSON Schema validator supporting core features used by our config schema
 25 |   static async loadSchema(schemaPath?: string): Promise<JSONSchema | undefined> {
 26 |     if (!schemaPath) return defaultSchema
 27 |     try {
 28 |       const isNode = Boolean((globalThis as any)?.process?.versions?.node)
 29 |       if (!isNode) return defaultSchema
 30 |       const fs = await import('node:fs/promises')
 31 |       const raw = await fs.readFile(schemaPath, 'utf8')
 32 |       return JSON.parse(raw) as JSONSchema
 33 |     } catch (err) {
 34 |       Logger.warn(`Failed to read schema at ${schemaPath}; using built-in`, String(err))
 35 |       return defaultSchema
 36 |     }
 37 |   }
 38 | 
 39 |   static validate(config: unknown, schema: JSONSchema): { valid: boolean; errors: SchemaValidationError[] } {
 40 |     const errors: SchemaValidationError[] = []
 41 |     validateAgainst(config, schema, '', errors)
 42 |     return { valid: errors.length === 0, errors }
 43 |   }
 44 | 
 45 |   static assertValid<T = MasterConfig>(config: unknown, schema: JSONSchema): T {
 46 |     const { valid, errors } = this.validate(config, schema)
 47 |     if (!valid) {
 48 |       const msg = errors.map((e) => `${e.path || '<root>'}: ${e.message}`).join('\n')
 49 |       throw new Error(`Configuration validation failed:\n${msg}`)
 50 |     }
 51 |     return config as T
 52 |   }
 53 | }
 54 | 
 55 | function typeOf(val: unknown): string {
 56 |   if (Array.isArray(val)) return 'array'
 57 |   return typeof val
 58 | }
 59 | 
 60 | function validateAgainst(value: unknown, schema: JSONSchema, path: string, errors: SchemaValidationError[]): void {
 61 |   if (!schema) return
 62 |   // Type check
 63 |   if (schema.type) {
 64 |     const allowed = Array.isArray(schema.type) ? schema.type : [schema.type]
 65 |     const actual = typeOf(value)
 66 |     if (!allowed.includes(actual)) {
 67 |       errors.push({ path, message: `expected type ${allowed.join('|')}, got ${actual}` })
 68 |       return
 69 |     }
 70 |   }
 71 | 
 72 |   if (schema.enum && !schema.enum.includes(value)) {
 73 |     errors.push({ path, message: `must be one of ${schema.enum.join(', ')}` })
 74 |   }
 75 | 
 76 |   if (schema.format) {
 77 |     if (schema.format === 'url' && typeof value === 'string') {
 78 |       try {
 79 |         // eslint-disable-next-line no-new
 80 |         new URL(value)
 81 |       } catch {
 82 |         errors.push({ path, message: 'must be a valid URL' })
 83 |       }
 84 |     }
 85 |     if (schema.format === 'integer' && typeof value === 'number') {
 86 |       if (!Number.isInteger(value)) errors.push({ path, message: 'must be an integer' })
 87 |     }
 88 |   }
 89 | 
 90 |   if (schema.properties && value && typeof value === 'object' && !Array.isArray(value)) {
 91 |     const v = value as Record<string, unknown>
 92 |     const required = schema.required || []
 93 |     for (const r of required) {
 94 |       if (!(r in v)) errors.push({ path: join(path, r), message: 'is required' })
 95 |     }
 96 |     for (const [k, subschema] of Object.entries(schema.properties)) {
 97 |       if (k in v) validateAgainst(v[k], subschema, join(path, k), errors)
 98 |     }
 99 |     if (schema.additionalProperties === false) {
100 |       for (const k of Object.keys(v)) {
101 |         if (!schema.properties[k]) errors.push({ path: join(path, k), message: 'is not allowed' })
102 |       }
103 |     }
104 |   }
105 | 
106 |   if (schema.items && Array.isArray(value)) {
107 |     value.forEach((item, idx) => validateAgainst(item, schema.items!, join(path, String(idx)), errors))
108 |   }
109 | 
110 |   if (schema.allOf) {
111 |     for (const s of schema.allOf) validateAgainst(value, s, path, errors)
112 |   }
113 |   if (schema.anyOf) {
114 |     const ok = schema.anyOf.some((s) => {
115 |       const temp: SchemaValidationError[] = []
116 |       validateAgainst(value, s, path, temp)
117 |       return temp.length === 0
118 |     })
119 |     if (!ok) errors.push({ path, message: 'does not match any allowed schema' })
120 |   }
121 | }
122 | 
123 | function join(base: string, key: string): string {
124 |   return base ? `${base}.${key}` : key
125 | }
126 | 
127 | // Built-in fallback schema captures core fields and constraints.
128 | const defaultSchema: JSONSchema = {
129 |   type: 'object',
130 |   required: ['master_oauth', 'hosting', 'servers'],
131 |   properties: {
132 |     master_oauth: {
133 |       type: 'object',
134 |       required: ['authorization_endpoint', 'token_endpoint', 'client_id', 'redirect_uri', 'scopes'],
135 |       properties: {
136 |         issuer: { type: 'string' },
137 |         authorization_endpoint: { type: 'string', format: 'url' },
138 |         token_endpoint: { type: 'string', format: 'url' },
139 |         jwks_uri: { type: 'string' },
140 |         client_id: { type: 'string' },
141 |         client_secret: { type: 'string' },
142 |         redirect_uri: { type: 'string' },
143 |         scopes: { type: 'array', items: { type: 'string' } },
144 |         audience: { type: 'string' },
145 |       },
146 |       additionalProperties: true,
147 |     },
148 |     hosting: {
149 |       type: 'object',
150 |       required: ['platform'],
151 |       properties: {
152 |         platform: { type: 'string', enum: ['node', 'cloudflare-workers', 'koyeb', 'docker', 'unknown'] },
153 |         port: { type: 'number', format: 'integer' },
154 |         base_url: { type: 'string' },
155 |       },
156 |       additionalProperties: true,
157 |     },
158 |     logging: {
159 |       type: 'object',
160 |       properties: { level: { type: 'string', enum: ['debug', 'info', 'warn', 'error'] } },
161 |     },
162 |     routing: {
163 |       type: 'object',
164 |       properties: {
165 |         loadBalancer: { type: 'object', properties: { strategy: { type: 'string' } }, additionalProperties: true },
166 |         circuitBreaker: { type: 'object', additionalProperties: true },
167 |         retry: { type: 'object', additionalProperties: true },
168 |       },
169 |       additionalProperties: true,
170 |     },
171 |     servers: {
172 |       type: 'array',
173 |       items: {
174 |         type: 'object',
175 |         required: ['id', 'type', 'auth_strategy', 'config'],
176 |         properties: {
177 |           id: { type: 'string' },
178 |           type: { type: 'string', enum: ['git', 'npm', 'pypi', 'docker', 'local'] },
179 |           url: { type: 'string' },
180 |           package: { type: 'string' },
181 |           version: { type: 'string' },
182 |           branch: { type: 'string' },
183 |           auth_strategy: {
184 |             type: 'string',
185 |             enum: ['master_oauth', 'delegate_oauth', 'bypass_auth', 'proxy_oauth'],
186 |           },
187 |           auth_config: { type: 'object', additionalProperties: true },
188 |           config: {
189 |             type: 'object',
190 |             properties: {
191 |               environment: { type: 'object', additionalProperties: true },
192 |               args: { type: 'array', items: { type: 'string' } },
193 |               port: { type: 'number', format: 'integer' },
194 |             },
195 |             additionalProperties: true,
196 |           },
197 |         },
198 |         additionalProperties: true,
199 |       },
200 |     },
201 |   },
202 |   additionalProperties: true,
203 | }
204 | 
```

