This is page 1 of 8. Use http://codebase.md/jakedismo/master-mcp-server?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
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
```javascript
```
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
```
```
--------------------------------------------------------------------------------
/deploy/cloudflare/.gitkeep:
--------------------------------------------------------------------------------
```
```
--------------------------------------------------------------------------------
/deploy/docker/.gitkeep:
--------------------------------------------------------------------------------
```
```
--------------------------------------------------------------------------------
/deploy/koyeb/.gitkeep:
--------------------------------------------------------------------------------
```
```
--------------------------------------------------------------------------------
/tests/.gitkeep:
--------------------------------------------------------------------------------
```
```
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
```
dist
node_modules
coverage
```
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
```
dist
node_modules
coverage
```
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
```json
{
"singleQuote": true,
"semi": false,
"trailingComma": "es5",
"printWidth": 100
}
```
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
```
# Runtime
NODE_ENV=development
LOG_LEVEL=info
LOG_FORMAT=json
# Hosting
# If running behind a PaaS that sets PORT, the config loader will detect it.
PORT=3000
MASTER_HOSTING_PLATFORM=node
MASTER_HOSTING_PORT=3000
MASTER_BASE_URL=http://localhost:3000
# Security
# REQUIRED in production; if missing in dev, an ephemeral key is generated
TOKEN_ENC_KEY=replace-with-32+char-hex-or-random
# OAuth defaults (example values; override per provider)
MASTER_OAUTH_AUTHORIZATION_ENDPOINT=https://example.com/auth
MASTER_OAUTH_TOKEN_ENDPOINT=https://example.com/token
MASTER_OAUTH_CLIENT_ID=placeholder
MASTER_OAUTH_REDIRECT_URI=http://localhost:3000/oauth/callback
MASTER_OAUTH_SCOPES=openid
```
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
```
/* eslint-env node */
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
project: ['./tsconfig.node.json'],
tsconfigRootDir: __dirname,
sourceType: 'module',
ecmaVersion: 2022
},
env: {
node: true,
es2022: true
},
plugins: ['@typescript-eslint'],
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
rules: {
'no-console': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'off'
},
overrides: [
{
files: ['**/*.ts', '**/*.tsx'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: ['./tsconfig.node.json'],
tsconfigRootDir: __dirname,
sourceType: 'module',
ecmaVersion: 2022,
},
},
{
files: ['src/runtime/worker.ts'],
parserOptions: {
project: ['./tsconfig.worker.json'],
},
},
],
ignorePatterns: ['dist/**', 'node_modules/**']
}
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Master MCP Server - Comprehensive .gitignore
# Node.js & npm
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Package manager lock files (keep package-lock.json for reproducible builds)
yarn.lock
pnpm-lock.yaml
# Runtime data
pids/
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
*.lcov
.nyc_output/
.coverage/
# Compiled output
dist/
build/
lib/
out/
*.tsbuildinfo
# TypeScript cache
*.tsbuildinfo
# ESLint cache
.eslintcache
# Prettier cache
.prettiercache
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
.env.*.local
.env.production
# Secrets and sensitive configuration
config/secrets.json
config/production-secrets.json
.secrets/
secrets/
# IDE and Editor files
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
Thumbs.db
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Runtime and process files
.pm2/
.pid
# Database
*.db
*.sqlite
*.sqlite3
# Cache directories
.cache/
.parcel-cache/
.next/
.nuxt/
.vuepress/dist
# Temporary directories
tmp/
temp/
.tmp/
.temp/
# Test artifacts
test-results/
junit.xml
coverage-final.json
# Documentation build artifacts
docs/.vitepress/dist/
docs/.vitepress/cache/
.typedoc/
# Deployment artifacts
deploy/secrets/
deploy/keys/
deploy/*.key
deploy/*.pem
# Docker
.dockerignore
docker-compose.override.yml
# Cloudflare Workers
.dev.vars
.wrangler/
worker-configuration.d.ts
# Koyeb
.koyeb/
# SSL certificates and keys
*.pem
*.key
*.crt
*.ca-bundle
*.p12
*.pfx
# API keys and tokens
.api-keys
*.token
auth.json
# Ouroboros and Swarm Agent Files
ouroboros-results/
ouroboros-logs/
ouroboros-cache/
ouroboros-data/
swarm-cache/
swarm-logs/
agent-logs/
agent-results/
agent-cache/
.ouroboros/
.swarm/
# Session and memory files
SESSION-MEMORY.md
session-*.md
.session/
memory/
# MCP specific
mcp-logs/
mcp-cache/
server-data/
server-logs/
backend-servers/
loaded-servers/
.mcp/
# OAuth and authentication
oauth-tokens/
oauth-cache/
.oauth/
tokens/
session-store/
# Performance and monitoring
metrics/
traces/
.monitoring/
perf-data/
# Local development
.dev/
dev-data/
local-config/
local-secrets/
# Husky hooks (if needed, can be uncommented)
.husky/
# Backup files
*.backup
*.bak
*~
# Temporary files
*.tmp
*.temp
# Archive files
*.zip
*.tar.gz
*.rar
*.7z
# Binary files (unless specifically needed)
*.exe
*.dll
*.so
*.dylib
# Cloud provider specific
.aws/
.gcp/
.azure/
# Terraform
*.tfstate
*.tfstate.*
.terraform/
.terraform.lock.hcl
# Kubernetes
*.kubeconfig
# Monitoring and observability
jaeger-data/
prometheus-data/
grafana-data/
# Load testing
artillery-output/
k6-results/
# Keep important files (negative patterns)
!.gitkeep
!.env.example
!.env.template
!config/default.json
!config/schema.json
!examples/**/*.yaml
!examples/**/*.json
!docs/**/*
!deploy/docker/Dockerfile
!deploy/cloudflare/wrangler.toml
!deploy/koyeb/koyeb.yaml
# MCP / local tooling
.crush/
.kiro/
.claude/
.ouroboros/
ouroboros-results/
# Keep uv.lock tracked (do not ignore)
# IDEs and Ouroboros
AGENTS.md
CLAUDE.md
CRUSH.md
GEMINI.md
QWEN.md
IMPLEMENTATION_PLAN.md
SESSION-MEMORY.md
task-definition.md
# Documentation application
.vitepress
# Random
.DS_Store
```
--------------------------------------------------------------------------------
/examples/advanced-routing/README.md:
--------------------------------------------------------------------------------
```markdown
# Advanced Routing Example
Demonstrates retry policy, circuit breaker, and load balancing configuration.
Run
- `CONFIG_PATH=examples/advanced-routing/config.yaml npm start`
```
--------------------------------------------------------------------------------
/examples/performance/README.md:
--------------------------------------------------------------------------------
```markdown
# Example: Performance Tuning
Demonstrates routing configuration tuned for high-throughput and resilience.
## Run
```
MASTER_CONFIG_PATH=examples/performance/config.yaml npm run dev
```
```
--------------------------------------------------------------------------------
/examples/multi-server/README.md:
--------------------------------------------------------------------------------
```markdown
# Example: Multi-Server with Resilience
Demonstrates multiple instances of the same service id with load balancing, retries, and circuit breaker.
## Run
```
MASTER_CONFIG_PATH=examples/multi-server/config.yaml npm run dev
```
```
--------------------------------------------------------------------------------
/examples/custom-auth/README.md:
--------------------------------------------------------------------------------
```markdown
# Example: Custom Authentication Strategy
Shows how to attach a custom `MultiAuthManager` that tweaks headers per backend.
## Run
```
MASTER_CONFIG_PATH=examples/custom-auth/config.yaml \
node --loader ts-node/esm examples/custom-auth/index.ts
```
```
--------------------------------------------------------------------------------
/examples/cloudflare-worker/README.md:
--------------------------------------------------------------------------------
```markdown
# Cloudflare Worker Example
Runs the Master MCP Worker handler with Wrangler.
Steps
1. Build worker bundle: `npm run build:worker`
2. Dev: `npx wrangler dev examples/cloudflare-worker/worker.ts`
3. Deploy: configure and run from `/deploy/cloudflare`.
```
--------------------------------------------------------------------------------
/docs/api/README.md:
--------------------------------------------------------------------------------
```markdown
**master-mcp-server**
***
# master-mcp-server
## Interfaces
- [RunningServer](interfaces/RunningServer.md)
## Functions
- [createServer](functions/createServer.md)
## References
### default
Renames and re-exports [createServer](functions/createServer.md)
```
--------------------------------------------------------------------------------
/examples/basic-node/README.md:
--------------------------------------------------------------------------------
```markdown
# Example: Basic Master MCP Server (Node)
This example runs the Node runtime with a single local backend.
## Run
```
MASTER_CONFIG_PATH=examples/basic-node/config.yaml npm run dev
```
Endpoints:
- http://localhost:3000/health
- http://localhost:3000/mcp/tools/list
```
--------------------------------------------------------------------------------
/examples/oauth-delegation/README.md:
--------------------------------------------------------------------------------
```markdown
# OAuth Delegation Example
Enable delegated OAuth for backends and complete flows via the built-in controller.
Run
1. Configure `oauth_delegation.enabled: true` in your config, and set provider entries if desired.
2. Start the server and visit `/oauth` to initiate flows.
```
--------------------------------------------------------------------------------
/examples/oauth-node/README.md:
--------------------------------------------------------------------------------
```markdown
# Example: OAuth Delegation (GitHub)
Demonstrates `delegate_oauth` for a GitHub-protected backend.
## Run
Set secrets:
```
export GITHUB_CLIENT_SECRET=... # from GitHub OAuth app
```
Start:
```
MASTER_CONFIG_PATH=examples/oauth-node/config.yaml npm run dev
```
Then open:
```
http://localhost:3000/oauth/authorize?server_id=github-tools
```
```
--------------------------------------------------------------------------------
/examples/security-hardening/README.md:
--------------------------------------------------------------------------------
```markdown
# Example: Security Hardening
This example outlines recommended environment and config for production.
## Environment
- `NODE_ENV=production`
- `LOG_FORMAT=json`
- `LOG_LEVEL=info`
- `TOKEN_ENC_KEY` set via secret store
- `MASTER_BASE_URL=https://your.domain`
## Config Snippet
```yaml
security:
audit: true
logging:
level: info
hosting:
platform: node
port: 3000
```
Deploy with Docker/Koyeb using read-only config mounts and non-root users (see Dockerfiles).
```
--------------------------------------------------------------------------------
/deploy/README.md:
--------------------------------------------------------------------------------
```markdown
# Deployment Overview
Artifacts and configs to deploy the Master MCP Server:
- Docker
- `deploy/docker/Dockerfile`: multi-stage build for Node runtime.
- `deploy/docker/docker-compose.yml`: local development runner.
- Cloudflare Workers
- `deploy/cloudflare/wrangler.toml`: Workers config (staging/production envs).
- `deploy/cloudflare/README.md`: usage notes.
- Koyeb
- `deploy/koyeb/koyeb.yaml`: Koyeb service and autoscaling settings.
CI/CD pipelines live in `.github/workflows`. See `docs/architecture/phase10-deployment-architecture.md` for full details.
```
--------------------------------------------------------------------------------
/deploy/cloudflare/README.md:
--------------------------------------------------------------------------------
```markdown
# Cloudflare Workers Deployment
## Prerequisites
- Cloudflare account and `CLOUDFLARE_API_TOKEN` with Workers permissions.
- `wrangler` installed locally or use GitHub Action for CI deploys.
## Local Dev
```
wrangler dev
```
## Configure Secrets
```
wrangler secret put TOKEN_ENC_KEY
# Add any OAuth client secrets as needed
```
## Deploy
```
wrangler deploy --env staging
wrangler deploy --env production
```
The worker entry is `src/runtime/worker.ts` which exports a `fetch` handler. Ensure your config values align with Workers (e.g., base URLs for OAuth callbacks).
```
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
```markdown
# Master MCP Server Documentation
Welcome to the Master MCP Server docs. This documentation covers architecture, configuration, authentication flows, request routing, deployment, and troubleshooting. It also includes end-to-end tutorials and working examples.
## Contents
- Getting Started: `../docs/getting-started.md`
- Guides:
- Authentication: `./guides/authentication.md`
- Configuration Mgmt: `./guides/configuration-management.md`
- Module Loading & Aggregation: `./guides/module-loading.md`
- Request Routing & Resilience: `./guides/request-routing.md`
- Server Management: `./guides/server-management.md`
- Tutorials:
- Beginner Setup: `./tutorials/beginner-getting-started.md`
- OAuth Delegation with GitHub: `./tutorials/oauth-delegation-github.md`
- Cloudflare Workers: `./tutorials/cloudflare-workers-tutorial.md`
- Load Balancing & Resilience: `./tutorials/load-balancing-and-resilience.md`
- API Reference: `./api/README.md` (generated via TypeDoc)
- Configuration:
- Schema & Reference: `./configuration/reference.md`
- Environment Variables: `./configuration/environment-variables.md`
- Security: `./configuration/security.md`
- Performance: `./configuration/performance.md`
- Deployment:
- Docker: `./deployment/docker.md`
- Cloudflare Workers: `./deployment/cloudflare-workers.md`
- Koyeb: `./deployment/koyeb.md`
- CI/CD: `./deployment/cicd.md`
- Troubleshooting:
- Common Issues: `./troubleshooting/common-issues.md`
- Error Reference: `./troubleshooting/errors.md`
- Performance Troubleshooting: `./troubleshooting/performance.md`
- Security Best Practices: `./troubleshooting/security-best-practices.md`
If you are looking for design notes per phase, see `./architecture/*` and testing strategy in `./testing/phase-9-testing-architecture.md`.
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Master MCP Server
Master MCP Server aggregates multiple MCP servers behind a single, secure endpoint. It provides configuration-driven module loading, unified capability discovery, request routing with resilience, and first-class OAuth flows for multi-backend authentication.
## Highlights
- Aggregates multiple MCP servers with tool/resource discovery and namespacing
- OAuth support: master token pass-through, delegated provider flows, proxy refresh
- Config-driven setup with JSON/YAML, schema validation, and secret resolution
- Resilient routing: load-balancing, retries with backoff/jitter, circuit-breakers
- Cross-platform: Node.js server and Cloudflare Workers runtime
- Production-ready deployment: Docker, Cloudflare Workers, Koyeb
- Testing strategy and CI-ready structure
## Quick Start (Node.js)
1) Install dependencies (requires network):
```plaintext
npm ci
```
2) Configure environment (copy and edit):
```plaintext
cp .env.example .env
```
3) Run in dev mode:
```plaintext
npm run dev
```
4) Health and Metrics:
- `GET /health` → `{ ok: true }`
- `GET /metrics` → basic system metrics
5) MCP endpoints (HTTP gateway):
- `POST /mcp/tools/list`
- `POST /mcp/tools/call` with `{ name, arguments }`
- `POST /mcp/resources/list`
- `POST /mcp/resources/read` with `{ uri }`
See `docs/` for full guides and end-to-end examples.
## Documentation
- Docs index: `docs/index.md`
- Getting started: `docs/getting-started/overview.md`
- Guides: `docs/guides/*`
- API reference: generated into `docs/api/reference/` (see below)
- Configuration reference: `docs/configuration/*`
- Deployment: `docs/deployment/*`
- Troubleshooting: `docs/troubleshooting/*`
- Contributing: `docs/contributing/*`
## Generate API Docs
We use TypeDoc (Markdown) to generate API docs from TypeScript.
1) Install (requires network):
```plaintext
npm i -D typedoc typedoc-plugin-markdown
```
2) Generate docs:
```plaintext
npm run docs:api
```
Outputs to `docs/api/`.
## Examples
Working examples live in `examples/`:
- Basic Node: `examples/basic-node`
- Cloudflare Worker: `examples/cloudflare-worker`
- Advanced Routing: `examples/advanced-routing`
- OAuth Delegation: `examples/oauth-delegation`
- Testing Patterns: see `/tests` and `docs/examples/testing.md`
Each example has a README with run instructions.
## Deployment
- Docker: `deploy/docker/*` and top-level `Dockerfile` / `docker-compose.yml`
- Cloudflare Workers: `deploy/cloudflare/*` with `wrangler.toml`
- Koyeb: `deploy/koyeb/koyeb.yaml`
- CI/CD examples: see `docs/deployment/cicd.md`
## Architecture

## Contributing & Support
- See `docs/contributing/*` for development workflow and guidelines
- See `docs/troubleshooting/index.md` for solutions
- Open an issue or discussion for help and ideas
## License
See `LICENSE`. This repository currently uses UNLICENSED for private/internal use.
```
--------------------------------------------------------------------------------
/docs/advanced/security.md:
--------------------------------------------------------------------------------
```markdown
---
title: Security Hardening
---
# Security Hardening
- Rotate secrets regularly (`security.rotation_days`).
- Use `security.audit` for config change logging.
- Lock down OAuth redirect URIs and audiences.
- Enforce strict TLS and CSP in hosting platform.
- Validate inputs and sanitize logs (see `utils/security` patterns).
```
--------------------------------------------------------------------------------
/docs/configuration/security.md:
--------------------------------------------------------------------------------
```markdown
# Security Configuration & Practices
## Secrets in Config
Use either of the following in your config:
- `env:VARNAME` → value is read from `process.env.VARNAME`
- `enc:gcm:<base64>` → value is decrypted using `MASTER_CONFIG_KEY` (or `MASTER_SECRET_KEY`)
`SecretManager` handles both resolving and redacting sensitive values for logs.
## Token Encryption
`TokenManager` encrypts stored delegated/proxy tokens with `TOKEN_ENC_KEY`. In production this must be set; otherwise startup fails. In development, a warning is logged and an ephemeral key is generated.
## OAuth Best Practices
- Always set `hosting.base_url` correctly for accurate redirect URIs behind proxies.
- Use PKCE (enabled by default) and short-lived state tokens.
- Limit scopes in `servers[].auth_config.scopes` to the minimum required.
## Hardening Tips
- Drop container capabilities and run as non-root (see Dockerfiles)
- Use `LOG_FORMAT=json` in production for structured logs
- Ensure secrets are injected via platform secret stores (KMS, Workers Secrets, Koyeb Secrets)
- Enable `security.audit` to log config changes (redacted)
```
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
```markdown
# Contributing Guide
Thanks for your interest in contributing to the Master MCP Server. This guide outlines the development workflow and standards.
## Development Setup
1) Install Node >= 18.17
2) Install dependencies:
```
npm ci
```
3) Useful scripts:
```
npm run typecheck
npm run build
npm run dev
npm run test
npm run lint && npm run format
```
## Docs & API Reference
- Author guides and tutorials in `docs/`
- Generate API docs (requires network): `npm run docs:api`
- Keep examples in `examples/` runnable and minimal
## Coding Standards
- TypeScript strict mode; no `any` without justification
- Prefer small, composable modules and clear interfaces
- Avoid introducing runtime-only dependencies in shared modules (support both Node and Workers)
## Testing
- Unit tests under `tests/unit` and integration under `tests/integration`
- Add targeted tests for new modules and critical paths
## Commit Style
- Use clear, imperative messages (e.g., "Add OAuth state validation")
- Reference issues where applicable
## Security
- Never commit secrets
- Use `SecretManager` patterns for config secrets
```
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/package.json:
--------------------------------------------------------------------------------
```json
{
"type": "module"
}
```
--------------------------------------------------------------------------------
/config/production.json:
--------------------------------------------------------------------------------
```json
{
"logging": { "level": "info" }
}
```
--------------------------------------------------------------------------------
/config/development.json:
--------------------------------------------------------------------------------
```json
{
"logging": { "level": "debug" }
}
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.node.json"
}
```
--------------------------------------------------------------------------------
/examples/cloudflare-worker/worker.ts:
--------------------------------------------------------------------------------
```typescript
export { default as default } from '../../src/runtime/worker.js'
```
--------------------------------------------------------------------------------
/src/utils/validators.ts:
--------------------------------------------------------------------------------
```typescript
export { isNonEmptyString, isRecord } from './validation.js'
export * as Validation from './validation.js'
```
--------------------------------------------------------------------------------
/docs/advanced/index.md:
--------------------------------------------------------------------------------
```markdown
---
title: Advanced Topics
---
# Advanced Topics
Deep dives into security, performance, monitoring, and extensibility.
```
--------------------------------------------------------------------------------
/tests/fixtures/stdio-server.js:
--------------------------------------------------------------------------------
```javascript
process.stdout.write(JSON.stringify({ type: 'notification', message: 'server ready' }) + '\n');
setInterval(() => {
// Keep the process alive
}, 1000)
```
--------------------------------------------------------------------------------
/static/oauth/script.js:
--------------------------------------------------------------------------------
```javascript
// Placeholder for future enhancements like telemetry, animations, etc.
// Intentionally minimal to reduce attack surface.
document.documentElement.classList.add('js')
```
--------------------------------------------------------------------------------
/tests/fixtures/capabilities.json:
--------------------------------------------------------------------------------
```json
{
"tools": [
{ "name": "echo", "description": "Echo input" }
],
"resources": [
{ "uri": "docs.readme", "name": "Readme", "mimeType": "text/plain" }
]
}
```
--------------------------------------------------------------------------------
/examples/basic-node/server.ts:
--------------------------------------------------------------------------------
```typescript
import { createServer } from '../../src/index.js'
async function main() {
await createServer(true)
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
main()
```
--------------------------------------------------------------------------------
/docs/advanced/extensibility.md:
--------------------------------------------------------------------------------
```markdown
---
title: Extensibility & Plugins
---
# Extensibility & Plugins
- Extend `ModuleLoader` to support new sources.
- Add routing strategies by extending the load balancer.
- Use typed interfaces in `src/types/*` for compatibility.
```
--------------------------------------------------------------------------------
/docs/examples/testing.md:
--------------------------------------------------------------------------------
```markdown
---
title: Testing Patterns
---
# Example: Testing Patterns
Use `/tests` as the primary reference. This example demonstrates configuring Node vs Workers test environments and using utilities in `_utils`.
Run
- `npm run test` or target specific suites.
```
--------------------------------------------------------------------------------
/docs/contributing/index.md:
--------------------------------------------------------------------------------
```markdown
---
title: Contributing
---
# Contributing
We welcome contributions across code, documentation, and examples.
- Read Guidelines for coding and docs conventions
- Use Dev Setup to get your environment ready
- Open PRs with focused changes and include tests/docs
```
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"outDir": "dist/node",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"types": ["node"],
"lib": ["ES2022"],
"jsx": "react-jsx"
},
"exclude": ["src/runtime/worker.ts"]
}
```
--------------------------------------------------------------------------------
/docs/advanced/performance.md:
--------------------------------------------------------------------------------
```markdown
---
title: Performance & Scalability
---
# Performance & Scalability
- Tune retry/backoff to avoid thundering herd.
- Right-size circuit breaker thresholds per backend latency.
- Use caching (`utils/cache`) where safe.
- Monitor p95/p99 latencies and error rates.
```
--------------------------------------------------------------------------------
/docs/advanced/monitoring.md:
--------------------------------------------------------------------------------
```markdown
---
title: Monitoring & Logging
---
# Monitoring & Logging
- Use `/metrics` endpoint and `collectSystemMetrics()` for basic telemetry.
- Integrate logs with your platform (stdout in Docker/Koyeb, Workers logs).
- Centralize error handling in `utils/errors` and `utils/logger`.
```
--------------------------------------------------------------------------------
/tests/_setup/miniflare.setup.ts:
--------------------------------------------------------------------------------
```typescript
// Miniflare/Vitest setup for Workers-targeted suites
import { Logger } from '../../src/utils/logger.js'
Logger.configure({ level: 'error', json: true })
// Miniflare provides global fetch/Request/Response.
// If we need to start auxiliary Node HTTP stubs, do it within tests.
```
--------------------------------------------------------------------------------
/docs/troubleshooting/deployment.md:
--------------------------------------------------------------------------------
```markdown
---
title: Deployment
---
# Troubleshooting: Deployment
- Docker: ensure env files loaded and ports exposed.
- Workers: align bundling target and avoid Node APIs.
- Koyeb: ensure service listens on the configured port.
- Secrets: verify platform secret injection and `config_key_env`.
```
--------------------------------------------------------------------------------
/docs/examples/oauth-delegation.md:
--------------------------------------------------------------------------------
```markdown
---
title: OAuth Delegation
---
# Example: OAuth Delegation
Folder: `/examples/oauth-delegation`
What it shows
- Enabling `oauth_delegation` and providers
- Using `OAuthFlowController` to complete flows
Run
1. Configure providers and redirect URIs
2. Start Node server and visit `/oauth` UI
```
--------------------------------------------------------------------------------
/docs/examples/cloudflare-worker.md:
--------------------------------------------------------------------------------
```markdown
---
title: Cloudflare Worker
---
# Example: Cloudflare Worker
Folder: `/examples/cloudflare-worker`
What it shows
- Worker `fetch` handler delegating to runtime adapter
- Using `wrangler.toml` from `/deploy/cloudflare`
Run
1. `npm run build:worker`
2. `npx wrangler dev examples/cloudflare-worker/worker.ts`
```
--------------------------------------------------------------------------------
/src/types/jose-shim.d.ts:
--------------------------------------------------------------------------------
```typescript
declare module 'jose' {
export function createRemoteJWKSet(url: URL): any
export function jwtVerify(
jwt: string,
key: any,
options?: { issuer?: string | string[]; audience?: string | string[] }
): Promise<{ payload: any; protectedHeader: any }>
export function decodeJwt(jwt: string): any
}
```
--------------------------------------------------------------------------------
/tsconfig.worker.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"outDir": "dist/worker",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": [],
"lib": ["ES2022", "WebWorker"]
},
"exclude": [
"src/runtime/node.ts",
"src/utils/crypto.ts",
"src/config/**",
"src/auth/token-manager.ts"
]
}
```
--------------------------------------------------------------------------------
/docs/deployment/index.md:
--------------------------------------------------------------------------------
```markdown
---
title: Deployment Overview
---
# Deployment Overview
Supported targets
- Docker (Node runtime)
- Cloudflare Workers
- Koyeb (Node runtime)
Deployment assets live in `/deploy/*`. Platform-specific guides reference these files and environment variable requirements.
See architecture: `docs/architecture/phase10-deployment-architecture.md`.
```
--------------------------------------------------------------------------------
/deploy/docker/entrypoint.sh:
--------------------------------------------------------------------------------
```bash
#!/bin/sh
set -e
# Map PaaS-provided PORT to MASTER_HOSTING_PORT if not explicitly set.
if [ -n "${PORT}" ] && [ -z "${MASTER_HOSTING_PORT}" ]; then
export MASTER_HOSTING_PORT="${PORT}"
fi
# Default to json logs in production if not set
if [ "${NODE_ENV}" = "production" ] && [ -z "${LOG_FORMAT}" ]; then
export LOG_FORMAT=json
fi
exec "$@"
```
--------------------------------------------------------------------------------
/docs/guides/index.md:
--------------------------------------------------------------------------------
```markdown
---
title: Guides
---
# User Guides
Structured, task-based guides for common scenarios across auth, routing, configuration, testing, and operations.
Recommended path by persona
- Developers: Authentication → Module Loading → Request Routing
- DevOps: Configuration → Deployment → Monitoring
- Integrators: OAuth Delegation → Routing policies → Security
```
--------------------------------------------------------------------------------
/docs/examples/advanced-routing.md:
--------------------------------------------------------------------------------
```markdown
---
title: Advanced Routing
---
# Example: Advanced Routing
Folder: `/examples/advanced-routing`
What it shows
- Configure retry policy with backoff/jitter
- Circuit breaker thresholds and recovery
- Load balancer strategies
Run
1. Use `config.yaml` from the example
2. Start the server with `CONFIG_PATH=examples/advanced-routing/config.yaml npm start`
```
--------------------------------------------------------------------------------
/docs/configuration/environment.md:
--------------------------------------------------------------------------------
```markdown
---
title: Environment Variables
---
# Environment Variables
Common variables
- `NODE_ENV`: `development` | `production`
- `PORT`: overrides `hosting.port`
- OAuth secrets: `MASTER_OAUTH_CLIENT_SECRET`, provider secrets
- Encryption: key name from `security.config_key_env`
Use `.env` for local development and set secrets via platform secrets in production.
```
--------------------------------------------------------------------------------
/examples/basic-node/config.yaml:
--------------------------------------------------------------------------------
```yaml
hosting:
platform: node
port: 3000
master_oauth:
authorization_endpoint: https://example.com/oauth/authorize
token_endpoint: https://example.com/oauth/token
client_id: master-mcp
redirect_uri: http://localhost:3000/oauth/callback
scopes: [openid]
servers:
- id: tools
type: local
auth_strategy: bypass_auth
config:
port: 3333
```
--------------------------------------------------------------------------------
/tests/factories/mcpFactory.ts:
--------------------------------------------------------------------------------
```typescript
import type { ToolDefinition, ResourceDefinition } from '../../src/types/mcp.js'
export function makeTools(...names: string[]): ToolDefinition[] {
return names.map((n) => ({ name: n, description: `${n} tool` }))
}
export function makeResources(...uris: string[]): ResourceDefinition[] {
return uris.map((u) => ({ uri: u, name: u, mimeType: 'text/plain' }))
}
```
--------------------------------------------------------------------------------
/static/oauth/success.html:
--------------------------------------------------------------------------------
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>OAuth Success</title>
<link rel="stylesheet" href="/static/oauth/style.css" />
</head>
<body>
<main class="container">
<h1>Success</h1>
<p>Authorization completed successfully.</p>
</main>
</body>
</html>
```
--------------------------------------------------------------------------------
/typedoc.json:
--------------------------------------------------------------------------------
```json
{
"$schema": "https://typedoc.org/schema.json",
"entryPoints": [
"src"
],
"tsconfig": "tsconfig.node.json",
"plugin": ["typedoc-plugin-markdown"],
"out": "docs/api",
"cleanOutputDir": false,
"categorizeByGroup": false,
"hideBreadcrumbs": true,
"excludePrivate": true,
"excludeProtected": false,
"excludeInternal": true,
"readme": "none"
}
```
--------------------------------------------------------------------------------
/docs/troubleshooting/routing.md:
--------------------------------------------------------------------------------
```markdown
---
title: Routing & Modules
---
# Troubleshooting: Routing & Modules
- Missing capabilities: call `discoverAllCapabilities()` after loading servers.
- Unhealthy backend: check health checks and restart via `restartServer(id)`.
- Retry storms: reduce `maxRetries` or increase `baseDelayMs`.
- Circuit breaker open: lower `failureThreshold` or increase recovery timeout.
```
--------------------------------------------------------------------------------
/docs/contributing/dev-setup.md:
--------------------------------------------------------------------------------
```markdown
---
title: Development Setup
---
# Development Setup
Prerequisites
- Node 18.17+
- npm or pnpm
Common commands
- `npm run build` — build Node and Worker bundles
- `npm run dev` — start local server
- `npm run test` — run test suites
- `npm run lint` / `npm run format` — code quality
Docs
- `npm run docs:api` — generate API reference
- `npm run docs:dev` — serve docs locally
```
--------------------------------------------------------------------------------
/examples/sample-configs/basic.yaml:
--------------------------------------------------------------------------------
```yaml
hosting:
platform: node
port: 3000
master_oauth:
authorization_endpoint: https://example.com/auth
token_endpoint: https://example.com/token
client_id: demo-client
redirect_uri: http://localhost:3000/callback
scopes:
- openid
servers:
- id: example
type: local
auth_strategy: master_oauth
config:
environment: {}
args: []
port: 3333
```
--------------------------------------------------------------------------------
/static/oauth/error.html:
--------------------------------------------------------------------------------
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>OAuth Error</title>
<link rel="stylesheet" href="/static/oauth/style.css" />
</head>
<body>
<main class="container error">
<h1>Authorization Error</h1>
<p>Something went wrong during the OAuth flow.</p>
</main>
</body>
</html>
```
--------------------------------------------------------------------------------
/docs/examples/basic-node.md:
--------------------------------------------------------------------------------
```markdown
---
title: Basic Node Aggregator
---
# Example: Basic Node Aggregator
Folder: `/examples/basic-node`
What it shows
- Load two MCP servers from config
- Expose health, metrics, and MCP endpoints
- Validate configuration with schema
Run
1. `npm run build`
2. `node examples/basic-node/server.ts` (ts-node recommended for dev)
Config
- Copy and adapt `examples/sample-configs/basic.yaml`.
```
--------------------------------------------------------------------------------
/docs/getting-started/quickstart-workers.md:
--------------------------------------------------------------------------------
```markdown
---
title: Quickstart (Workers)
---
# Quickstart (Cloudflare Workers)
Run Master MCP in Cloudflare Workers runtime using `src/runtime/worker.ts`.
Steps
1. Copy `deploy/cloudflare/wrangler.toml` and adjust env vars.
2. Build worker bundle: `npm run build:worker`.
3. Publish with Wrangler: `npx wrangler deploy`.
See also: Deployment → Cloudflare Workers and Examples → Cloudflare Worker.
```
--------------------------------------------------------------------------------
/docs/getting-started/installation.md:
--------------------------------------------------------------------------------
```markdown
---
title: Installation
---
# Installation
Prerequisites
- Node.js >= 18.17
- npm or pnpm
Install
1. Clone the repository and install deps: `npm install`
2. Copy `.env.example` to `.env` and adjust values.
3. Build Node and Worker bundles: `npm run build`
Run (Node)
- Dev: `npm run dev` or `npm run dev:watch`
- Prod: `npm run start:prod`
Next: Quickstart (Node) or Quickstart (Workers)
```
--------------------------------------------------------------------------------
/examples/sample-configs/simple-setup.yaml:
--------------------------------------------------------------------------------
```yaml
hosting:
platform: node
port: 3000
master_oauth:
authorization_endpoint: https://auth.example.com/authorize
token_endpoint: https://auth.example.com/token
client_id: master-mcp
redirect_uri: http://localhost:3000/oauth/callback
scopes: [openid, profile]
servers:
- id: local-simple
type: local
auth_strategy: bypass_auth
config:
port: 4001
environment: {}
```
--------------------------------------------------------------------------------
/tests/factories/oauthFactory.ts:
--------------------------------------------------------------------------------
```typescript
export function makeToken(overrides?: Partial<{ access_token: string; expires_in: number; scope: string | string[] }>) {
const scope = overrides?.scope ?? ['openid']
return {
access_token: overrides?.access_token ?? `at_${Math.random().toString(36).slice(2)}`,
token_type: 'bearer',
expires_in: overrides?.expires_in ?? 3600,
scope: Array.isArray(scope) ? scope.join(' ') : scope,
}
}
```
--------------------------------------------------------------------------------
/src/runtime/node.ts:
--------------------------------------------------------------------------------
```typescript
import express from 'express'
import { createServer } from '../index.js'
export async function startNode() {
const app = express()
await createServer()
const port = process.env.PORT ? Number(process.env.PORT) : 3000
app.get('/health', (_req, res) => res.json({ ok: true }))
app.listen(port, () => {
// eslint-disable-next-line no-console
console.log(`Master MCP (Node) on ${port}`)
})
}
```
--------------------------------------------------------------------------------
/docs/contributing/guidelines.md:
--------------------------------------------------------------------------------
```markdown
---
title: Coding & Docs Guidelines
---
# Coding & Docs Guidelines
Code
- Follow TypeScript strictness and ESLint rules.
- Prefer TSDoc comments for public APIs with `@example` where useful.
- Keep modules cohesive and avoid unrelated changes.
Docs
- Write in Markdown under `/docs`.
- Add pages to VitePress sidebar via `.vitepress/config.ts`.
- For API docs, rely on TypeDoc output under `/docs/api/reference`.
```
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
```markdown
# Changelog
All notable changes to this project will be documented in this file.
## 0.1.0 – Phase 11 (Final)
- Complete documentation set (guides, tutorials, configuration, deployment)
- API reference pipeline via TypeDoc
- Working examples for Node, OAuth delegation, multi-server, custom auth, performance, security
- Deployment guides for Docker, Cloudflare Workers, and Koyeb
- Troubleshooting and best practices
```
--------------------------------------------------------------------------------
/docs/contributing/maintenance.md:
--------------------------------------------------------------------------------
```markdown
---
title: Maintenance Procedures
---
# Maintenance & Updates
- After adding/changing public APIs: update TSDoc and run `npm run docs:api`.
- After changing config schema or examples: run `npm run docs:config`.
- Before releases: run `npm run docs:all` and preview with `npm run docs:preview`.
- Keep `.vitepress/config.ts` nav/sidebars in sync with new pages.
- Update examples under `/examples/*` and link from `/docs/examples`.
```
--------------------------------------------------------------------------------
/docs/api/functions/createServer.md:
--------------------------------------------------------------------------------
```markdown
[**master-mcp-server**](../README.md)
***
# Function: createServer()
> **createServer**(`startHttp`): `Promise`\<[`RunningServer`](../interfaces/RunningServer.md)\>
Defined in: [index.ts:18](https://github.com/solita-internal/master-mcp-server/blob/cd13e0009f7a1b7f244de882dc738bbf1f90f2c2/src/index.ts#L18)
## Parameters
### startHttp
`boolean` = `true`
## Returns
`Promise`\<[`RunningServer`](../interfaces/RunningServer.md)\>
```
--------------------------------------------------------------------------------
/docs/troubleshooting/oauth.md:
--------------------------------------------------------------------------------
```markdown
---
title: OAuth & Tokens
---
# Troubleshooting: OAuth & Tokens
- Symptom: `invalid_grant` — Check code exchange timing and redirect URI match.
- Symptom: `invalid_client` — Verify client_id/secret; rotate if leaked.
- Symptom: missing `Authorization` — Ensure client token is passed to router/handler.
- Symptom: provider callback 404 — Mount `OAuthFlowController` endpoints.
Logs
- Increase log level to `debug` to see flow details.
```
--------------------------------------------------------------------------------
/docs/troubleshooting/security-best-practices.md:
--------------------------------------------------------------------------------
```markdown
# Security Best Practices
- Store secrets in environment or platform secret stores; avoid plaintext in config
- Set `TOKEN_ENC_KEY` in production and rotate periodically
- Use minimal OAuth scopes and avoid long-lived tokens when possible
- Prefer `LOG_FORMAT=json` and sanitize logs; `SecretManager.redact` prevents secret leakage in config logs
- Enforce `https` at the edge and set `MASTER_BASE_URL=https://...` to ensure secure redirects
```
--------------------------------------------------------------------------------
/tests/_setup/vitest.setup.ts:
--------------------------------------------------------------------------------
```typescript
// Global test setup for Node-targeted suites
import { Logger } from '../../src/utils/logger.js'
// Reduce log noise; allow tests to opt into capture
Logger.configure({ level: 'error', json: true })
// Node 18 has global fetch; ensure it exists for all tests
if (typeof fetch !== 'function') {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const nodeFetch = require('node-fetch')
// @ts-ignore
globalThis.fetch = nodeFetch
}
```
--------------------------------------------------------------------------------
/docs/examples/index.md:
--------------------------------------------------------------------------------
```markdown
---
title: Examples
---
# Examples
Real-world usage patterns for Node and Workers with routing, OAuth, and configuration.
- Basic Node Aggregator — minimal end-to-end setup
- Cloudflare Worker — Workers runtime entry
- Advanced Routing — retries, circuit-breaking, load balancing
- OAuth Delegation — per-backend OAuth flows
- Testing Patterns — integrate with the test utilities
Each example has a runnable skeleton under `/examples/*` with a README.
```
--------------------------------------------------------------------------------
/tests/perf/artillery/auth-routing.yaml:
--------------------------------------------------------------------------------
```yaml
config:
target: "http://127.0.0.1:3000"
phases:
- duration: 30
arrivalRate: 10
- duration: 60
arrivalRate: 25
scenarios:
- name: "Call tool echo"
flow:
- post:
url: "/mcp/tools/call"
json:
name: "backend.echo"
arguments: { msg: "hello" }
- think: 1
- name: "Read resource"
flow:
- post:
url: "/mcp/resources/read"
json:
uri: "backend.docs.readme"
```
--------------------------------------------------------------------------------
/docs/guides/oauth-delegation.md:
--------------------------------------------------------------------------------
```markdown
---
title: OAuth Delegation
---
# OAuth Delegation Guide
Enable per-server OAuth by delegating authorization flows.
- Configure `oauth_delegation.enabled: true` and optional `providers` map.
- Implement callback base URL and provider-specific overrides.
- Use `FlowController` with Express or Workers runtime to complete flows.
Security
- Use state and PKCE to prevent CSRF and code interception.
- Restrict allowed redirect URIs.
See: `src/oauth/*` and `examples/oauth-delegation/`.
```
--------------------------------------------------------------------------------
/static/oauth/consent.html:
--------------------------------------------------------------------------------
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Connect Account</title>
<link rel="stylesheet" href="/static/oauth/style.css" />
<script src="/static/oauth/script.js" defer></script>
</head>
<body>
<main class="container">
<h1>Connect Your Account</h1>
<p>This page is a placeholder template. The server may use dynamic pages for provider redirect.</p>
</main>
</body>
</html>
```
--------------------------------------------------------------------------------
/docs/guides/testing.md:
--------------------------------------------------------------------------------
```markdown
---
title: Testing Strategy
---
# Testing Strategy
Phase 9 established a comprehensive strategy with unit, integration, e2e, perf, and security tests.
- Structure: see `/tests` directories by test type.
- Runtimes: run both Node and Workers (Miniflare) where applicable.
- Utilities: `_utils` helpers for HTTP, logging, and test servers.
Commands
- `npm run test` — run all tests
- `npm run test:unit|integration|e2e|perf|security`
See also: `docs/testing/phase-9-testing-architecture.md`.
```
--------------------------------------------------------------------------------
/docs/deployment/koyeb.md:
--------------------------------------------------------------------------------
```markdown
# Deploy on Koyeb
Use the CI-built image and the service manifest in `deploy/koyeb/koyeb.yaml`.
## Steps
1) Build and push an image to GHCR or Docker Hub
2) In Koyeb, create a new service from container image
3) Set environment variables and secrets:
- `NODE_ENV=production`
- `TOKEN_ENC_KEY` (secret)
- `MASTER_BASE_URL` set to your Koyeb app URL
4) Configure autoscaling (see example manifest)
The platform-provided `PORT` is mapped automatically to `MASTER_HOSTING_PORT` by `deploy/docker/entrypoint.sh`.
```
--------------------------------------------------------------------------------
/docs/getting-started/concepts.md:
--------------------------------------------------------------------------------
```markdown
---
title: Core Concepts
---
# Core Concepts
- Master OAuth: centralized OAuth for aggregated servers.
- Delegated OAuth: per-server OAuth via delegation.
- Module Loader: fetches and manages external MCP servers.
- Request Router: registers routes and dispatches requests, with retries and circuit breaking.
- Runtime Abstraction: Node vs Workers environment differences.
- Config System: schema validation, secrets, and environment.
Architecture references are available under `docs/architecture/phase*-architecture.md`.
```
--------------------------------------------------------------------------------
/docs/getting-started/quickstart-node.md:
--------------------------------------------------------------------------------
```markdown
---
title: Quickstart (Node)
---
# Quickstart (Node)
This minimal example runs Master MCP Server in Node, aggregating two servers.
Steps
1. Create a config file (e.g., `config/master.yaml`) based on `examples/sample-configs/basic.yaml`.
2. Start the server: `npm run start`.
3. Connect your MCP client to the exposed endpoint.
Highlights
- Uses `src/runtime/node.ts` to bootstrap.
- Validates config with `SchemaValidator`.
- Routes requests via `RouteRegistry` and `RequestRouter`.
See also: Examples → Basic Node Aggregator.
```
--------------------------------------------------------------------------------
/docs/configuration/overview.md:
--------------------------------------------------------------------------------
```markdown
---
title: Configuration Overview
---
# Configuration Overview
The Master MCP configuration is validated against a JSON Schema (`SchemaValidator`).
Key Sections
- `master_oauth`: top-level OAuth issuer/client settings
- `hosting`: platform and runtime options
- `logging`, `security`: operational controls
- `routing`: load balancing, retries, circuit breakers
- `servers[]`: aggregated MCP servers with per-entry config
Start with examples in `/examples/sample-configs/*.yaml`.
Generate the full reference with `npm run docs:config`.
```
--------------------------------------------------------------------------------
/docs/guides/configuration.md:
--------------------------------------------------------------------------------
```markdown
---
title: Configuration
---
# Configuration Guide
Centralized configuration with schema validation and secrets.
- Types: `MasterConfig`, `HostingConfig`, `RoutingConfig`, `ServerConfig`, `SecurityConfig`.
- Validation: `SchemaValidator` with built-in default schema.
- Secrets: `SecretManager` decrypts protected values; keys from `SecurityConfig.config_key_env`.
- Environments: `EnvironmentManager` to load/merge env vars.
Commands
- Generate reference from schema: `npm run docs:config`
See: Configuration → Reference for full schema.
```
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"rootDir": "src",
"strict": true,
"noImplicitOverride": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": false,
"sourceMap": true,
"declaration": true,
"declarationMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"moduleResolution": "NodeNext",
"types": []
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "tests"]
}
```
--------------------------------------------------------------------------------
/docs/.vitepress/theme/index.ts:
--------------------------------------------------------------------------------
```typescript
import DefaultTheme from 'vitepress/theme'
import type { Theme } from 'vitepress'
import CodeTabs from './components/CodeTabs.vue'
import ConfigGenerator from './components/ConfigGenerator.vue'
import AuthFlowDemo from './components/AuthFlowDemo.vue'
import ApiPlayground from './components/ApiPlayground.vue'
import './style.css'
const theme: Theme = {
...DefaultTheme,
enhanceApp({ app }) {
app.component('CodeTabs', CodeTabs)
app.component('ConfigGenerator', ConfigGenerator)
app.component('AuthFlowDemo', AuthFlowDemo)
app.component('ApiPlayground', ApiPlayground)
},
}
export default theme
```
--------------------------------------------------------------------------------
/docs/api/index.md:
--------------------------------------------------------------------------------
```markdown
---
title: API Overview
---
# API Overview
The API reference is generated from TypeScript sources using TypeDoc and the `typedoc-plugin-markdown` plugin.
- Generated docs output: `/api/reference/`
- Generation config: `typedoc.json`
- Command: `npm run docs:api`
Key entry points
- `MasterServer` — main orchestration server
- `ModuleLoader`, `RequestRouter`, `CapabilityAggregator` — module and routing
- `MultiAuthManager`, `TokenManager` — authentication core
- `FlowController` and related OAuth utilities — OAuth flows
- `SchemaValidator`, `ConfigLoader` — configuration
See `/api/reference/` for the full API.
```
--------------------------------------------------------------------------------
/docs/deployment/cloudflare-workers.md:
--------------------------------------------------------------------------------
```markdown
# Deploy on Cloudflare Workers
Configuration lives in `deploy/cloudflare/wrangler.toml`.
## Setup
```
wrangler whoami
wrangler login
```
Set secrets:
```
wrangler secret put TOKEN_ENC_KEY
# Optional provider secrets as needed
```
Bind KV for token persistence (optional but recommended):
```
wrangler kv:namespace create TOKENS
# Add binding to wrangler.toml and your environments
```
## Dev and Deploy
```
wrangler dev
wrangler deploy --env staging
wrangler deploy --env production
```
Ensure your config uses `hosting.platform=cloudflare-workers` (auto-detected) and `hosting.base_url` is set for OAuth redirects.
```
--------------------------------------------------------------------------------
/docs/examples/overview.md:
--------------------------------------------------------------------------------
```markdown
# Examples Overview
This directory groups runnable examples demonstrating common scenarios:
- `examples/basic-node` — Minimal Node runtime with a local backend
- `examples/oauth-node` — OAuth delegation using GitHub
- `examples/multi-server` — Multiple instances, load balancing, retries, circuit breaker
- `examples/custom-auth` — Custom `MultiAuthManager` that tweaks backend headers
- `examples/performance` — Tuning routing for throughput and resilience
- `examples/security-hardening` — Production environment configuration tips
Run each by pointing `MASTER_CONFIG_PATH` and using `npm run dev`, unless a custom launcher is provided.
```
--------------------------------------------------------------------------------
/docs/troubleshooting/performance.md:
--------------------------------------------------------------------------------
```markdown
# Performance Troubleshooting
## Symptoms
- Elevated latency on tool calls
- Increased error rates or timeouts
## Checks
- Inspect `/metrics` (Node) and platform dashboards (Workers, Koyeb)
- Verify backends’ `/health` and logs
- Confirm load-balancing strategy is appropriate for your topology
## Tuning
- Increase `retry.maxRetries` and `baseDelayMs` judiciously
- Switch to `health` strategy and feed health scores when available
- Tighten circuit breaker thresholds to fail fast on unhealthy instances
## Environment
- Run Node with adequate CPU/memory; consider horizontal scaling
- Use KV-backed tokens in Workers to reduce token cache misses
```
--------------------------------------------------------------------------------
/examples/oauth-node/config.yaml:
--------------------------------------------------------------------------------
```yaml
hosting:
platform: node
port: 3000
master_oauth:
authorization_endpoint: https://example.com/oauth/authorize
token_endpoint: https://example.com/oauth/token
client_id: master-mcp
redirect_uri: http://localhost:3000/oauth/callback
scopes: [openid]
servers:
- id: github-tools
type: local
auth_strategy: delegate_oauth
auth_config:
provider: github
authorization_endpoint: https://github.com/login/oauth/authorize
token_endpoint: https://github.com/login/oauth/access_token
client_id: ${GITHUB_CLIENT_ID}
client_secret: env:GITHUB_CLIENT_SECRET
scopes: [repo, read:user]
config:
port: 4100
```
--------------------------------------------------------------------------------
/deploy/cloudflare/wrangler.toml:
--------------------------------------------------------------------------------
```toml
name = "master-mcp-server"
main = "src/runtime/worker.ts"
compatibility_date = "2024-07-01"
workers_dev = true
# Optional: enable Node compat if needed (try to keep off)
# node_compat = false
[vars]
# Non-sensitive defaults; override per-env below or via dashboard
MASTER_HOSTING_PLATFORM = "workers"
LOG_LEVEL = "info"
LOG_FORMAT = "json"
[env.staging]
name = "master-mcp-server-staging"
[env.staging.vars]
LOG_LEVEL = "debug"
[env.production]
name = "master-mcp-server-prod"
[env.production.vars]
LOG_LEVEL = "info"
# Secrets to provision via: wrangler secret put TOKEN_ENC_KEY
# - TOKEN_ENC_KEY
# - Any OAuth client secrets required by configured backends
```
--------------------------------------------------------------------------------
/examples/advanced-routing/config.yaml:
--------------------------------------------------------------------------------
```yaml
master_oauth:
authorization_endpoint: https://example.com/auth
token_endpoint: https://example.com/token
client_id: example
redirect_uri: http://localhost:3000/oauth/callback
scopes: [openid, profile]
hosting:
platform: node
port: 3000
routing:
loadBalancer:
strategy: round_robin
retry:
maxRetries: 3
baseDelayMs: 250
maxDelayMs: 4000
backoffFactor: 2
jitter: full
circuitBreaker:
failureThreshold: 5
successThreshold: 2
recoveryTimeoutMs: 30000
servers:
- id: local-a
type: local
auth_strategy: bypass_auth
config: {}
- id: local-b
type: local
auth_strategy: bypass_auth
config: {}
```
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
```typescript
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
globals: true,
include: ['tests/**/*.{test,spec}.ts'],
exclude: ['tests/workers/**/*', 'dist', 'node_modules'],
setupFiles: ['tests/_setup/vitest.setup.ts'],
environment: 'node',
coverage: {
reporter: ['text', 'lcov', 'cobertura'],
provider: 'v8',
reportsDirectory: './coverage/node',
all: true,
include: ['src/**/*.ts'],
exclude: ['src/runtime/worker.ts', 'src/types/**', 'src/**/index.ts'],
thresholds: {
lines: 0.85,
functions: 0.85,
branches: 0.8,
statements: 0.85,
},
},
},
})
```
--------------------------------------------------------------------------------
/docs/getting-started/overview.md:
--------------------------------------------------------------------------------
```markdown
---
title: Overview
---
# Overview
Master MCP Server aggregates multiple MCP servers and exposes a unified endpoint with:
- Authentication: Master OAuth, delegated OAuth, token management.
- Module loading: Git, NPM, PyPI, Docker, local.
- Request routing: load balancing, retries, circuit breakers.
- Configuration: schema validation, secrets, environment management.
- Runtimes: Node and Cloudflare Workers.
- Testing and Deployment: comprehensive strategy with CI.
Who is this for?
- Developers integrating MCP into applications.
- DevOps engineers deploying/managing the server.
- System integrators configuring auth and routing.
Continue with Installation and Quickstarts.
```
--------------------------------------------------------------------------------
/docs/troubleshooting/errors.md:
--------------------------------------------------------------------------------
```markdown
# Error Reference
## Configuration validation failed
The config did not conform to the schema. The error lists the path and reason. Fix the offending property and reload.
## CircuitOpenError
The circuit breaker is open for the selected upstream instance. Reduce upstream failures or increase `recoveryTimeoutMs`. Calls will resume after a successful half-open trial.
## OAuthError
An upstream OAuth provider response was invalid. Check client credentials, scopes, and redirect URIs.
## Token decryption failed
Persisted token ciphertext could not be decrypted with `TOKEN_ENC_KEY`. Ensure the key has not changed unexpectedly. Clear token storage if necessary and re-authorize.
```
--------------------------------------------------------------------------------
/docs/tutorials/cloudflare-workers-tutorial.md:
--------------------------------------------------------------------------------
```markdown
# Tutorial: Cloudflare Workers
Run Master MCP Server on Cloudflare Workers with the provided runtime.
## 1) Prerequisites
- Cloudflare account
- `wrangler` CLI
## 2) Configure
Update `deploy/cloudflare/wrangler.toml` as needed. Secrets:
```
wrangler secret put TOKEN_ENC_KEY
```
If you need persistent token storage, bind a KV namespace named `TOKENS` and pass it via environment bindings.
## 3) Dev
```
wrangler dev
```
## 4) Deploy
```
wrangler deploy --env staging
wrangler deploy --env production
```
Ensure `hosting.platform` resolves to `cloudflare-workers` (detected automatically) and that `hosting.base_url` is set appropriately if you rely on absolute redirects for OAuth.
```
--------------------------------------------------------------------------------
/tests/unit/config.secret-manager.test.ts:
--------------------------------------------------------------------------------
```typescript
import '../setup/test-setup.js'
import test from 'node:test'
import assert from 'node:assert/strict'
import { SecretManager } from '../../src/config/secret-manager.js'
test('SecretManager encrypt/decrypt and resolve env placeholders', () => {
const sm = new SecretManager({ key: 'k123' })
const enc = sm.encrypt('s3cr3t')
assert.ok(sm.isEncrypted(enc))
assert.equal(sm.decrypt(enc), 's3cr3t')
process.env.MY_TOKEN = 'abc'
const cfg = { a: enc, b: 'env:MY_TOKEN', c: { password: 'x' } }
const resolved = sm.resolveSecrets(cfg)
assert.equal(resolved.a, 's3cr3t')
assert.equal(resolved.b, 'abc')
const redacted = sm.redact(resolved)
assert.equal(redacted.c.password, '***')
})
```
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
```json
{
"hosting": {
"platform": "node",
"port": 3005,
"base_url": "http://localhost:3005"
},
"master_oauth": {
"authorization_endpoint": "https://github.com/login/oauth/authorize",
"token_endpoint": "https://github.com/login/oauth/access_token",
"client_id": "Ov23li2S7w1LYoM4yLJP",
"client_secret": "9f2cebe77974976e5fe7125eb50edefa81ee5219",
"redirect_uri": "http://localhost:3005/oauth/callback",
"scopes": ["read:user", "user:email"]
},
"servers": [
{
"id": "test-server",
"type": "local",
"url": "http://localhost:3006",
"auth_strategy": "bypass_auth",
"config": {
"environment": {},
"args": []
}
}
]
}
```
--------------------------------------------------------------------------------
/tests/utils/token-storages.ts:
--------------------------------------------------------------------------------
```typescript
import type { TokenStorage } from '../../src/auth/token-manager.js'
export class MemoryKVStorage implements TokenStorage {
private kv = new Map<string,string>()
async set(key: string, value: string) { this.kv.set(key, value) }
async get(key: string) { return this.kv.get(key) }
async delete(key: string) { this.kv.delete(key) }
async *entries() { for (const e of this.kv.entries()) yield e }
}
export class RedisLikeStorage implements TokenStorage {
private map = new Map<string, string>()
async set(key: string, value: string) { this.map.set(key, value) }
async get(key: string) { return this.map.get(key) }
async delete(key: string) { this.map.delete(key) }
async *entries() { for (const e of this.map.entries()) yield e }
}
```
--------------------------------------------------------------------------------
/docs/tutorials/beginner-getting-started.md:
--------------------------------------------------------------------------------
```markdown
# Tutorial: Beginner Setup
Goal: Run Master MCP Server with a single local backend and call a tool.
## 1) Install and Start
```
npm ci
cp .env.example .env
npm run dev
```
## 2) Add a Backend
Create `examples/basic-node/config.yaml` (already provided) and run with:
```
MASTER_CONFIG_PATH=examples/basic-node/config.yaml npm run dev
```
## 3) Verify Health and Capabilities
```
curl http://localhost:3000/health
curl -X POST http://localhost:3000/mcp/tools/list -H 'content-type: application/json' -d '{}'
```
## 4) Call a Tool
```
curl -X POST http://localhost:3000/mcp/tools/call \
-H 'content-type: application/json' \
-d '{"name":"tools.echo","arguments":{"text":"hello"}}'
```
Replace `tools.echo` with a tool exposed by your backend.
```
--------------------------------------------------------------------------------
/examples/custom-auth/config.yaml:
--------------------------------------------------------------------------------
```yaml
hosting:
platform: node
port: 3000
master_oauth:
authorization_endpoint: https://example.com/oauth/authorize
token_endpoint: https://example.com/oauth/token
client_id: master-mcp
redirect_uri: http://localhost:3000/oauth/callback
scopes: [openid]
servers:
- id: public-tools
type: local
auth_strategy: bypass_auth
config:
port: 4200
- id: custom-proxy
type: local
auth_strategy: proxy_oauth
auth_config:
provider: google
authorization_endpoint: https://accounts.google.com/o/oauth2/v2/auth
token_endpoint: https://oauth2.googleapis.com/token
client_id: ${GOOGLE_CLIENT_ID}
client_secret: env:GOOGLE_CLIENT_SECRET
scopes: [openid, profile, email]
config:
port: 4201
```
--------------------------------------------------------------------------------
/deploy/koyeb/koyeb.yaml:
--------------------------------------------------------------------------------
```yaml
version: v1
services:
- name: master-mcp
type: web
ports:
- port: 80
protocol: http
instance_types:
- micro
min_instances: 2
max_instances: 10
scaling:
metric: cpu
target: 70
stabilization_window: 60
health_check:
path: /health
interval: 10s
timeout: 3s
unhealthy_threshold: 3
healthy_threshold: 1
docker:
image: ghcr.io/OWNER/REPO:latest # replaced by CI/CD
env:
- key: NODE_ENV
value: production
- key: LOG_FORMAT
value: json
- key: LOG_LEVEL
value: info
# Koyeb provides PORT; entrypoint maps it to hosting.port
- key: TOKEN_ENC_KEY
value: ${TOKEN_ENC_KEY} # set via Koyeb secrets
```
--------------------------------------------------------------------------------
/vitest.worker.config.ts:
--------------------------------------------------------------------------------
```typescript
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
globals: true,
include: ['tests/workers/**/*.{test,spec}.ts'],
setupFiles: ['tests/_setup/miniflare.setup.ts'],
environment: 'miniflare',
environmentOptions: {
modules: true,
script: undefined, // tests import modules directly
bindings: {},
kvNamespaces: [],
durableObjects: {},
},
coverage: {
reporter: ['text', 'lcov'],
provider: 'v8',
reportsDirectory: './coverage/workers',
all: true,
include: ['src/**/*.ts'],
exclude: ['src/runtime/node.ts', 'src/types/**'],
thresholds: {
lines: 0.8,
functions: 0.8,
branches: 0.75,
statements: 0.8,
},
},
},
})
```
--------------------------------------------------------------------------------
/tests/unit/modules/stdio-capability-discovery.test.ts:
--------------------------------------------------------------------------------
```typescript
import { test } from 'node:test'
import assert from 'node:assert'
import { StdioCapabilityDiscovery } from '../../../src/modules/stdio-capability-discovery.js'
import { StdioManager } from '../../../src/modules/stdio-manager.js'
test('StdioCapabilityDiscovery should instantiate correctly', async () => {
const manager = new StdioManager()
const discovery = new StdioCapabilityDiscovery(manager)
assert.ok(discovery)
})
test('StdioCapabilityDiscovery should have required methods', async () => {
const manager = new StdioManager()
const discovery = new StdioCapabilityDiscovery(manager)
assert.strictEqual(typeof discovery.discoverCapabilities, 'function')
assert.strictEqual(typeof discovery.callTool, 'function')
assert.strictEqual(typeof discovery.readResource, 'function')
})
```
--------------------------------------------------------------------------------
/docs/configuration/performance.md:
--------------------------------------------------------------------------------
```markdown
# Performance & Tuning
Tune routing and runtime to handle your workload.
## Routing
- Prefer `health` strategy to route to instances with better health scores (when provided)
- Increase `retry.maxRetries` for flaky networks, but cap `maxDelayMs`
- Use `jitter: full` to avoid thundering herds
- Adjust `circuitBreaker` thresholds based on observed upstream reliability
## Node Runtime
- Use `NODE_ENV=production`
- Run behind a reverse proxy (nginx, Cloudflare) for TLS termination
- Set `LOG_LEVEL=info` or `warn`
## Workers Runtime
- Bind KV storage for token persistence to avoid in-memory losses across isolates
- Avoid large responses; stream when possible
## Observability
- Use `/metrics` to scrape basic system stats in Node
- Add platform logs/metrics (Cloudflare, Koyeb dashboards)
```
--------------------------------------------------------------------------------
/examples/multi-server/config.yaml:
--------------------------------------------------------------------------------
```yaml
hosting:
platform: node
port: 3000
master_oauth:
authorization_endpoint: https://example.com/oauth/authorize
token_endpoint: https://example.com/oauth/token
client_id: master-mcp
redirect_uri: http://localhost:3000/oauth/callback
scopes: [openid]
routing:
loadBalancer:
strategy: health
circuitBreaker:
failureThreshold: 3
successThreshold: 2
recoveryTimeoutMs: 10000
retry:
maxRetries: 3
baseDelayMs: 200
maxDelayMs: 3000
backoffFactor: 2
jitter: full
retryOn:
networkErrors: true
httpStatuses: [408, 429]
httpStatusClasses: [5]
servers:
- id: compute
type: local
auth_strategy: bypass_auth
config:
port: 4101
- id: compute
type: local
auth_strategy: bypass_auth
config:
port: 4102
```
--------------------------------------------------------------------------------
/deploy/docker/docker-compose.yml:
--------------------------------------------------------------------------------
```yaml
version: '3.9'
services:
master-mcp:
build:
context: ../..
dockerfile: deploy/docker/Dockerfile
args:
- NODE_VERSION=20
image: master-mcp:dev
environment:
- NODE_ENV=development
- LOG_LEVEL=debug
- MASTER_HOSTING_PLATFORM=node
- MASTER_BASE_URL=http://localhost:3000
# TOKEN_ENC_KEY is recommended even in dev to persist tokens across restarts
- TOKEN_ENC_KEY=dev-dev-dev-dev-dev-dev-dev-dev
ports:
- "3000:3000"
volumes:
- ../../config:/app/config:ro
healthcheck:
test: ["CMD", "node", "-e", "const http=require('http');http.get('http://127.0.0.1:3000/health',r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))"]
interval: 5s
timeout: 3s
retries: 20
start_period: 10s
```
--------------------------------------------------------------------------------
/docs/troubleshooting/common-issues.md:
--------------------------------------------------------------------------------
```markdown
# Troubleshooting: Common Issues
## Config validation failed
- Error shows `<path>: <reason>` from `SchemaValidator`
- Verify your file is valid JSON/YAML
- Check required fields under `master_oauth`, `hosting`, `servers`
## Missing TOKEN_ENC_KEY in production
- Set `TOKEN_ENC_KEY` to a strong random string
- In development, an ephemeral key is generated with a warning
## OAuth callback mismatch
- Ensure `master_oauth.redirect_uri` matches your runtime base URL
- If behind a proxy, set `MASTER_BASE_URL` to the external URL
## 401 Unauthorized to backends
- Ensure client token is valid or delegated tokens are stored
- For delegated flows, complete `/oauth/authorize` and `/oauth/callback` first
## Workers runtime odd redirects
- Always set `hosting.base_url` in Workers to generate correct absolute URLs
```
--------------------------------------------------------------------------------
/examples/performance/config.yaml:
--------------------------------------------------------------------------------
```yaml
hosting:
platform: node
port: 3000
master_oauth:
authorization_endpoint: https://example.com/oauth/authorize
token_endpoint: https://example.com/oauth/token
client_id: master-mcp
redirect_uri: http://localhost:3000/oauth/callback
scopes: [openid]
logging:
level: info
routing:
loadBalancer:
strategy: weighted
retry:
maxRetries: 4
baseDelayMs: 100
maxDelayMs: 1500
backoffFactor: 1.8
jitter: full
retryOn:
networkErrors: true
httpStatuses: [408, 429]
httpStatusClasses: [5]
circuitBreaker:
failureThreshold: 4
successThreshold: 2
recoveryTimeoutMs: 8000
servers:
- id: compute
type: local
auth_strategy: bypass_auth
config:
port: 4301
- id: compute
type: local
auth_strategy: bypass_auth
config:
port: 4302
```
--------------------------------------------------------------------------------
/tests/setup/test-setup.ts:
--------------------------------------------------------------------------------
```typescript
// Global test setup for Node test runner with ts-node ESM
// - Configures logger to reduce noise
// - Provides minimal polyfills and deterministic behavior where helpful
import { Logger } from '../../src/utils/logger.js'
// Quiet logs during tests unless DEBUG is set
Logger.configure({ level: (process.env.DEBUG ? 'debug' : 'error') as any, json: false })
// Ensure process.env defaults for tests
process.env.NODE_ENV = process.env.NODE_ENV || 'test'
// Deterministic Math.random for some tests (can be overridden locally)
const seed = 42
let state = seed
const origRandom = Math.random
globalThis.Math.random = () => {
if (process.env.TEST_NON_DETERMINISTIC === '1') return origRandom()
state = (1103515245 * state + 12345) % 0x100000000
return state / 0x100000000
}
// Export nothing; imported by tests as side-effect
export {}
```
--------------------------------------------------------------------------------
/tests/_utils/log-capture.ts:
--------------------------------------------------------------------------------
```typescript
export class LogCapture {
private originalLog = console.log
private originalError = console.error
private logs: string[] = []
private errors: string[] = []
start(): void {
console.log = (...args: any[]) => {
try { this.logs.push(args.map(String).join(' ')) } catch {}
this.originalLog.apply(console, args as any)
}
console.error = (...args: any[]) => {
try { this.errors.push(args.map(String).join(' ')) } catch {}
this.originalError.apply(console, args as any)
}
}
stop(): void {
console.log = this.originalLog
console.error = this.originalError
}
find(substr: string): boolean {
return this.logs.concat(this.errors).some((l) => l.includes(substr))
}
dump(): { logs: string[]; errors: string[] } {
return { logs: [...this.logs], errors: [...this.errors] }
}
}
```
--------------------------------------------------------------------------------
/src/types/auth.ts:
--------------------------------------------------------------------------------
```typescript
export type AuthHeaders = Record<string, string>
export interface ClientInfo {
client_id?: string
redirect_uri?: string
scopes?: string[]
metadata?: Record<string, unknown>
}
export interface OAuthDelegation {
type: 'oauth_delegation'
auth_endpoint: string
token_endpoint: string
client_info: ClientInfo
required_scopes: string[]
redirect_after_auth: boolean
}
export interface OAuthToken {
access_token: string
refresh_token?: string
expires_at: number // epoch millis
scope: string[]
user_info?: unknown
}
export interface AuthInfo {
type: 'bearer'
token: string
}
export interface TokenValidationResult {
valid: boolean
expiresAt?: number
scopes?: string[]
error?: string
}
export interface UserInfo {
id: string
name?: string
email?: string
avatarUrl?: string
[key: string]: unknown
}
```
--------------------------------------------------------------------------------
/static/oauth/style.css:
--------------------------------------------------------------------------------
```css
/* Minimal, responsive styles for OAuth pages */
:root { --bg: #0b0d10; --fg: #e8edf2; --muted: #a9b3bd; --accent: #52b788; }
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, Noto Sans, "Apple Color Emoji", "Segoe UI Emoji";
background: var(--bg); color: var(--fg);
display: grid; place-items: center;
}
.container { width: 100%; max-width: 640px; padding: 24px; }
h1 { font-size: 1.5rem; margin: 0 0 0.5rem; }
p { color: var(--muted); line-height: 1.5; }
.button {
display: inline-block; background: var(--accent); color: #02120c; font-weight: 600;
padding: 10px 14px; border-radius: 8px; text-decoration: none; margin-top: 12px;
}
.error h1 { color: #ff8692; }
@media (max-width: 480px) { .container { padding: 16px; } }
```
--------------------------------------------------------------------------------
/tests/servers/test-debug.js:
--------------------------------------------------------------------------------
```javascript
import { MultiAuthManager } from '../../src/auth/multi-auth-manager.js'
import { AuthStrategy } from '../../src/types/config.js'
const masterCfg = {
authorization_endpoint: 'http://localhost/auth',
token_endpoint: 'http://localhost/token',
client_id: 'master',
redirect_uri: 'http://localhost/cb',
scopes: ['openid'],
}
try {
console.log('Creating MultiAuthManager...')
const mam = new MultiAuthManager(masterCfg)
console.log('MultiAuthManager created successfully')
console.log('Registering server auth...')
mam.registerServerAuth('srv1', AuthStrategy.MASTER_OAUTH)
console.log('Server auth registered')
console.log('Preparing auth for backend...')
const h = await mam.prepareAuthForBackend('srv1', 'CLIENT')
console.log('Result:', h)
} catch (error) {
console.error('Error:', error)
console.error('Stack:', error.stack)
}
```
--------------------------------------------------------------------------------
/tests/utils/oauth-mocks.ts:
--------------------------------------------------------------------------------
```typescript
import { createMockServer, MockServer } from './mock-http.js'
export async function createGitHubMock(): Promise<MockServer> {
return createMockServer([
{ method: 'GET', path: '/user', handler: () => ({ headers: { 'x-oauth-scopes': 'read:user, repo' }, body: { id: 1, login: 'octocat' } }) },
])
}
export async function createGoogleMock(): Promise<MockServer> {
return createMockServer([
{ method: 'GET', path: '/userinfo', handler: () => ({ body: { sub: '123', name: 'Alice', email: '[email protected]', picture: 'http://x' } }) },
])
}
export async function createCustomOIDCMock(): Promise<MockServer> {
return createMockServer([
{ method: 'POST', path: '/token', handler: () => ({ body: { access_token: 'AT', expires_in: 3600, scope: 'openid' } }) },
{ method: 'GET', path: '/userinfo', handler: () => ({ body: { sub: 'abc', name: 'Bob' } }) },
])
}
```
--------------------------------------------------------------------------------
/tests/integration/modules.module-loader-health.test.ts:
--------------------------------------------------------------------------------
```typescript
import '../setup/test-setup.js'
import test from 'node:test'
import assert from 'node:assert/strict'
import { DefaultModuleLoader } from '../../src/modules/module-loader.js'
import { createMockServer } from '../utils/mock-http.js'
test('DefaultModuleLoader performHealthCheck against /health JSON', async () => {
const srv = await createMockServer([
{ method: 'GET', path: '/health', handler: () => ({ body: { ok: true } }) },
])
try {
const loader = new DefaultModuleLoader()
const ls: any = { id: 'a', type: 'node', endpoint: srv.url, config: {} as any, status: 'starting', lastHealthCheck: 0 }
const ok = await loader.performHealthCheck(ls)
assert.equal(ok, true)
// performHealthCheck doesn't update status, that's done by the load method
assert(ls.lastHealthCheck > 0, 'lastHealthCheck should be updated')
} finally {
await srv.close()
}
})
```
--------------------------------------------------------------------------------
/docs/deployment/docker.md:
--------------------------------------------------------------------------------
```markdown
# Deploy with Docker
Two Docker setups are provided:
1) Top-level `Dockerfile` (multi-stage) and `docker-compose.yml` for local dev
2) `deploy/docker/Dockerfile` optimized for CI-built images and Koyeb
## Local Development
```
docker compose up --build
```
This uses the dev target with hot reload (`nodemon`) and maps your working directory into the container.
## Production Image (CI)
Build and push an image:
```
docker build -f deploy/docker/Dockerfile -t ghcr.io/OWNER/REPO:latest .
docker push ghcr.io/OWNER/REPO:latest
```
Run:
```
docker run -p 3000:3000 \
-e NODE_ENV=production \
-e TOKEN_ENC_KEY=... \
-e MASTER_BASE_URL=https://your.domain \
ghcr.io/OWNER/REPO:latest
```
## Environment
- Set `TOKEN_ENC_KEY` in production
- Set `MASTER_BASE_URL` if serving behind a proxy to ensure correct OAuth redirects
- Inject `MASTER_OAUTH_CLIENT_SECRET` and other provider secrets via env
```
--------------------------------------------------------------------------------
/src/types/server.ts:
--------------------------------------------------------------------------------
```typescript
import type { ToolDefinition, ResourceDefinition, PromptDefinition } from './mcp.js'
import type { ServerConfig } from './config.js'
export type ServerType = 'python' | 'node' | 'typescript' | 'stdio' | 'unknown'
export interface ServerProcess {
pid?: number
port?: number
url?: string
stop: () => Promise<void>
}
export interface ServerCapabilities {
tools: ToolDefinition[]
resources: ResourceDefinition[]
prompts?: PromptDefinition[]
}
export interface LoadedServer {
id: string
type: ServerType
process?: ServerProcess
endpoint: string
config: ServerConfig
capabilities?: ServerCapabilities
status: 'starting' | 'running' | 'stopped' | 'error'
lastHealthCheck: number
// Optional: when a server has multiple deploys/instances
instances?: ServerInstance[]
}
export interface ServerInstance {
id: string
url: string
weight?: number
healthScore?: number // 0..100, used by health-based LB
}
```
--------------------------------------------------------------------------------
/docs/guides/module-loading.md:
--------------------------------------------------------------------------------
```markdown
# Module Loading & Capability Aggregation
Master MCP loads backend servers from multiple sources and aggregates their capabilities.
## Sources
- `local`: A locally running server exposing HTTP endpoints
- `git`, `npm`, `pypi`, `docker`: Stubs for different origins; endpoint resolution is config-driven (e.g., `config.port` or `url`).
Example server block:
```yaml
servers:
- id: search
type: local
auth_strategy: master_oauth
config:
port: 4100
```
## Health Checks
`DefaultModuleLoader` pings each server’s `/health` endpoint when loading to set an initial status (`running` or `error`).
## Capability Aggregation
`CapabilityAggregator` discovers tools and resources via:
- `GET /capabilities` (optional if provided by backend)
- `POST /mcp/tools/list`
- `POST /mcp/resources/list`
Capabilities can be prefixed by server id (default) to avoid naming conflicts. Use the aggregated names in requests, e.g., `serverId.toolName`.
```
--------------------------------------------------------------------------------
/docs/guides/request-routing.md:
--------------------------------------------------------------------------------
```markdown
# Request Routing & Resilience
Requests are routed by `RequestRouter`, which uses a `RouteRegistry`, `LoadBalancer`, `RetryHandler`, and `CircuitBreaker` to provide resilient upstream calls.
## Endpoints
- Tools: `POST /mcp/tools/call` with `{ name, arguments }`
- Resources: `POST /mcp/resources/read` with `{ uri }`
Names and URIs may be prefixed by server id when aggregated.
## Load Balancing
Configure strategy under `routing.loadBalancer.strategy`:
- `round_robin` (default)
- `weighted`
- `health`
## Retries
`routing.retry` controls attempts with backoff and jitter:
- `maxRetries`, `baseDelayMs`, `maxDelayMs`, `backoffFactor`, `jitter` (`full` or `none`)
- `retryOn.httpStatuses`, `retryOn.httpStatusClasses`, `retryOn.networkErrors`
## Circuit Breaker
`routing.circuitBreaker` manages failure thresholds and recovery:
- `failureThreshold`, `successThreshold`, `recoveryTimeoutMs`
When open, requests fail fast with a retry-after hint.
```
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
```yaml
version: "3.9"
services:
master-mcp:
build:
context: .
target: dev
image: master-mcp-server:dev
container_name: master-mcp-dev
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- MASTER_ENV=development
- MASTER_HOSTING_PLATFORM=node
- MASTER_HOSTING_PORT=3000
# Uncomment and set for local secrets
# - MASTER_OAUTH_CLIENT_SECRET=${MASTER_OAUTH_CLIENT_SECRET}
# - TOKEN_ENC_KEY=${TOKEN_ENC_KEY}
env_file:
- .env
volumes:
- ./:/app
- /app/node_modules
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/health"]
interval: 10s
timeout: 3s
retries: 3
start_period: 20s
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
deploy:
resources:
limits:
cpus: "0.75"
memory: 768M
reservations:
cpus: "0.25"
memory: 256M
```
--------------------------------------------------------------------------------
/tests/_utils/mock-fetch.ts:
--------------------------------------------------------------------------------
```typescript
// Lightweight Node fetch mocking via Undici MockAgent (Node 18)
// Falls back to no-op if Undici not available.
type RemoveFn = () => void
export function withMockFetch(routes: Array<{ method: string; url: RegExp | string; reply: (body?: any) => any }>): RemoveFn {
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const undici = require('undici')
const agent = new undici.MockAgent()
agent.disableNetConnect()
const pool = agent.get('http://localhost')
for (const r of routes) {
const method = r.method.toUpperCase()
const matcher = typeof r.url === 'string' ? new RegExp(r.url.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) : r.url
pool.intercept({ path: matcher, method }).reply(200, (_opts: any) => r.reply(_opts?.body))
}
undici.setGlobalDispatcher(agent)
return () => undici.setGlobalDispatcher(new undici.Agent())
} catch {
// No undici; do nothing.
return () => void 0
}
}
```
--------------------------------------------------------------------------------
/tests/unit/config.environment-manager.test.ts:
--------------------------------------------------------------------------------
```typescript
import '../setup/test-setup.js'
import test from 'node:test'
import assert from 'node:assert/strict'
import { EnvironmentManager } from '../../src/config/environment-manager.js'
test('EnvironmentManager parseCliArgs dotted keys', () => {
const orig = process.argv
process.argv = ['node', 'script', '--hosting.port=4001', '--logging.level=debug', '--config-path=./x.json']
try {
const parsed = EnvironmentManager.parseCliArgs()
assert.equal((parsed as any).hosting.port, 4001)
assert.equal((parsed as any).logging.level, 'debug')
assert.equal((parsed as any).configPath, './x.json')
} finally {
process.argv = orig
}
})
test('EnvironmentManager loadEnvOverrides maps vars', () => {
process.env.MASTER_HOSTING_PORT = '1234'
process.env.MASTER_OAUTH_SCOPES = 'a,b'
const ov = EnvironmentManager.loadEnvOverrides()
// @ts-ignore
assert.equal(ov.hosting?.port, 1234)
// @ts-ignore
assert.deepEqual(ov.master_oauth?.scopes, ['a','b'])
})
```
--------------------------------------------------------------------------------
/tests/integration/modules.capability-aggregator.test.ts:
--------------------------------------------------------------------------------
```typescript
import '../setup/test-setup.js'
import test from 'node:test'
import assert from 'node:assert/strict'
import { CapabilityAggregator } from '../../src/modules/capability-aggregator.js'
import { createMockServer } from '../utils/mock-http.js'
test('CapabilityAggregator discovers via /capabilities', async () => {
const caps = {
tools: [{ name: 't1' }],
resources: [{ uri: 'r1' }],
}
const srv = await createMockServer([
{ method: 'GET', path: '/capabilities', handler: () => ({ body: caps }) },
])
try {
const servers = new Map<string, any>([[
's1', { id: 's1', type: 'node', endpoint: srv.url, config: {} as any, status: 'running', lastHealthCheck: 0 }
]])
const ag = new CapabilityAggregator()
await ag.discoverCapabilities(servers as any)
const tools = ag.getAllTools(servers as any)
assert.equal(tools[0].name, 's1.t1')
const map = ag.getMappingForTool('s1.t1')
assert.equal(map?.originalName, 't1')
} finally {
await srv.close()
}
})
```
--------------------------------------------------------------------------------
/docs/api/interfaces/RunningServer.md:
--------------------------------------------------------------------------------
```markdown
[**master-mcp-server**](../README.md)
***
# Interface: RunningServer
Defined in: [index.ts:7](https://github.com/solita-internal/master-mcp-server/blob/cd13e0009f7a1b7f244de882dc738bbf1f90f2c2/src/index.ts#L7)
## Properties
### container
> **container**: `DependencyContainer`
Defined in: [index.ts:10](https://github.com/solita-internal/master-mcp-server/blob/cd13e0009f7a1b7f244de882dc738bbf1f90f2c2/src/index.ts#L10)
***
### name
> **name**: `string`
Defined in: [index.ts:8](https://github.com/solita-internal/master-mcp-server/blob/cd13e0009f7a1b7f244de882dc738bbf1f90f2c2/src/index.ts#L8)
***
### stop()
> **stop**: () => `Promise`\<`void`\>
Defined in: [index.ts:11](https://github.com/solita-internal/master-mcp-server/blob/cd13e0009f7a1b7f244de882dc738bbf1f90f2c2/src/index.ts#L11)
#### Returns
`Promise`\<`void`\>
***
### version
> **version**: `string`
Defined in: [index.ts:9](https://github.com/solita-internal/master-mcp-server/blob/cd13e0009f7a1b7f244de882dc738bbf1f90f2c2/src/index.ts#L9)
```
--------------------------------------------------------------------------------
/tests/unit/utils.monitoring.test.ts:
--------------------------------------------------------------------------------
```typescript
import '../setup/test-setup.js'
import test from 'node:test'
import assert from 'node:assert/strict'
import { MetricRegistry, HealthCheckRegistry, monitorEventLoopLag } from '../../src/utils/monitoring.js'
test('MetricRegistry counters/gauges/histograms', () => {
const R = new MetricRegistry()
R.counter('c').inc()
R.gauge('g').set(5)
R.histogram('h').observe(0.02)
const snap = R.list()
assert.equal(snap.counters.c, 1)
assert.equal(snap.gauges.g, 5)
assert.ok(Array.isArray(snap.histograms.h.counts))
})
test('HealthCheckRegistry aggregates ok/degraded', async () => {
const H = new HealthCheckRegistry()
H.register('ok', async () => ({ ok: true }))
H.register('bad', async () => ({ ok: false, info: 'x' }))
const res = await H.run()
assert.equal(res.status, 'degraded')
})
test('monitorEventLoopLag provides callback and stopper', async () => {
let called = 0
const stop = monitorEventLoopLag(() => { called++ }, 5)
await new Promise((r) => setTimeout(r, 20))
stop()
assert.ok(called >= 1)
})
```
--------------------------------------------------------------------------------
/tests/unit/stdio-capability-discovery.test.ts:
--------------------------------------------------------------------------------
```typescript
import { test } from 'node:test'
import assert from 'node:assert'
import { StdioCapabilityDiscovery } from '../src/modules/stdio-capability-discovery.js'
test('StdioCapabilityDiscovery should discover capabilities from a STDIO server', async () => {
// This test would require a running STDIO server
// For now, we'll just test that the class can be instantiated
const discovery = new StdioCapabilityDiscovery()
assert.ok(discovery)
})
test('StdioCapabilityDiscovery should have discoverCapabilities method', async () => {
const discovery = new StdioCapabilityDiscovery()
assert.strictEqual(typeof discovery.discoverCapabilities, 'function')
})
test('StdioCapabilityDiscovery should have callTool method', async () => {
const discovery = new StdioCapabilityDiscovery()
assert.strictEqual(typeof discovery.callTool, 'function')
})
test('StdioCapabilityDiscovery should have readResource method', async () => {
const discovery = new StdioCapabilityDiscovery()
assert.strictEqual(typeof discovery.readResource, 'function')
})
```
--------------------------------------------------------------------------------
/tests/unit/modules/stdio-manager.test.ts:
--------------------------------------------------------------------------------
```typescript
import { test } from 'node:test'
import assert from 'node:assert'
import { StdioManager } from '../../../src/modules/stdio-manager.js'
import path from 'node:path'
test('StdioManager should handle notifications', async () => {
const manager = new StdioManager()
const serverId = 'test-server'
const serverPath = path.resolve(process.cwd(), 'tests/fixtures/stdio-server.js')
let notificationReceived = null
const notificationPromise = new Promise(resolve => {
manager.onNotification(serverId, (message) => {
notificationReceived = message
resolve(message)
})
})
await manager.startServer(serverId, serverPath)
// Give the server a moment to start and send a notification
await new Promise(resolve => setTimeout(resolve, 500))
// The test server should send a notification on start
// Let's wait for it
await notificationPromise
assert.deepStrictEqual(notificationReceived, { type: 'notification', message: 'server ready' })
const server = manager['processes'].get(serverId)
server?.kill()
})
```
--------------------------------------------------------------------------------
/tests/unit/modules.route-registry.test.ts:
--------------------------------------------------------------------------------
```typescript
import '../setup/test-setup.js'
import test from 'node:test'
import assert from 'node:assert/strict'
import { RouteRegistry } from '../../src/routing/route-registry.js'
import { CircuitBreaker } from '../../src/routing/circuit-breaker.js'
import { LoadBalancer } from '../../src/routing/load-balancer.js'
test('RouteRegistry resolves and caches, bumps health', () => {
const servers = new Map([
['s1', { id: 's1', type: 'node', endpoint: 'http://localhost:1234', config: {} as any, status: 'running', lastHealthCheck: Date.now(), instances: [
{ id: 'i1', url: 'http://localhost:1', healthScore: 50 },
{ id: 'i2', url: 'http://localhost:2', healthScore: 50 },
] }]
])
const reg = new RouteRegistry(servers as any, new CircuitBreaker({ failureThreshold: 5, successThreshold: 1, recoveryTimeoutMs: 10 }), new LoadBalancer())
const r1 = reg.resolve('s1')!
assert.ok(r1.instance.id === 'i1' || r1.instance.id === 'i2')
// mark success and failure adjust health without throwing
reg.markSuccess('s1', r1.instance.id)
reg.markFailure('s1', r1.instance.id)
})
```
--------------------------------------------------------------------------------
/tests/unit/auth.token-manager.test.ts:
--------------------------------------------------------------------------------
```typescript
import '../setup/test-setup.js'
import test from 'node:test'
import assert from 'node:assert/strict'
import { TokenManager, InMemoryTokenStorage } from '../../src/auth/token-manager.js'
test('TokenManager stores, retrieves and cleans up', async () => {
const storage = new InMemoryTokenStorage()
const tm = new TokenManager({ storage, secret: 'k' })
const key = 'user::server'
await tm.storeToken(key, { access_token: 't', expires_at: Date.now() + 50, scope: [] })
const tok = await tm.getToken(key)
assert.equal(tok?.access_token, 't')
await new Promise((r) => setTimeout(r, 60))
await tm.cleanupExpiredTokens()
const tok2 = await tm.getToken(key)
assert.equal(tok2, null)
})
test('TokenManager works with custom KV-like storage', async () => {
const { MemoryKVStorage } = await import('../utils/token-storages.js')
const storage = new MemoryKVStorage()
const tm = new TokenManager({ storage, secret: 'k' })
await tm.storeToken('k1', { access_token: 'Z', expires_at: Date.now() + 1000, scope: [] })
const tok = await tm.getToken('k1')
assert.equal(tok?.access_token, 'Z')
})
```
--------------------------------------------------------------------------------
/docs/.vitepress/theme/components/CodeTabs.vue:
--------------------------------------------------------------------------------
```vue
<template>
<div class="mcp-tabs" role="tablist" aria-label="Code Tabs">
<div class="mcp-tabs__nav">
<button
v-for="opt in options"
:key="opt.value"
class="mcp-tabs__btn"
role="tab"
:aria-selected="active === opt.value"
@click="active = opt.value"
>
{{ opt.label }}
</button>
</div>
<div class="mcp-tabs__panel" role="tabpanel">
<slot :name="active" />
</div>
</div>
<div v-if="note" class="mcp-callout" style="margin-top:8px">{{ note }}</div>
<div v-if="footnote" style="margin-top:6px;color:var(--vp-c-text-2);font-size:.9rem">{{ footnote }}</div>
</template>
<script setup lang="ts">
import { ref, watchEffect } from 'vue'
interface Option { label: string; value: string }
const props = defineProps<{
options: Option[]
modelValue?: string
note?: string
footnote?: string
}>()
const active = ref(props.modelValue || (props.options[0]?.value ?? ''))
watchEffect(() => {
if (!props.options.find(o => o.value === active.value)) {
active.value = props.options[0]?.value ?? ''
}
})
</script>
```
--------------------------------------------------------------------------------
/tests/unit/utils.logger.test.ts:
--------------------------------------------------------------------------------
```typescript
import '../setup/test-setup.js'
import test from 'node:test'
import assert from 'node:assert/strict'
import { Logger } from '../../src/utils/logger.js'
test('Logger emits human log line', () => {
const lines: string[] = []
const orig = console.log
console.log = (s: any) => { lines.push(String(s)) }
try {
Logger.configure({ json: false, level: 'debug' })
Logger.info('hello', { a: 1 })
assert.ok(lines.length >= 1)
assert.match(lines[0], /\[INFO\].*hello/)
} finally {
console.log = orig
Logger.configure({ json: false, level: 'error' })
}
})
test('Logger child with base fields', () => {
const lines: string[] = []
const orig = console.log
console.log = (s: any) => { lines.push(String(s)) }
try {
Logger.configure({ json: true, level: 'debug', base: { svc: 'x' } })
const L = Logger.with({ reqId: 'r1' })
L.debug('dbg', { extra: 2 })
assert.ok(lines.length)
const parsed = JSON.parse(lines[0])
assert.equal(parsed.svc, 'x')
assert.equal(parsed.reqId, 'r1')
assert.equal(parsed.msg, 'dbg')
} finally {
console.log = orig
}
})
```
--------------------------------------------------------------------------------
/docs/getting-started/quick-start.md:
--------------------------------------------------------------------------------
```markdown
---
title: Quick Start
---
# Quick Start
Get from zero to a running Master MCP Server in under 10 minutes.
<CodeTabs
:options="[
{ label: 'Node.js', value: 'node' },
{ label: 'Docker', value: 'docker' },
{ label: 'Cloudflare Workers', value: 'workers' }
]"
note="Follow one tab end-to-end."
>
<template #node>
```bash
npm install
cp .env.example .env
npm run build && npm run start
```
Minimal `config/master.yaml`:
```yaml
hosting:
port: 3000
servers:
- id: search
type: local
auth_strategy: master_oauth
config:
port: 4100
```
Verify:
```bash
curl -s http://localhost:3000/health
curl -s http://localhost:3000/mcp/tools/list | jq
```
</template>
<template #docker>
```bash
docker compose up --build
```
Production image:
```bash
docker run -p 3000:3000 \
-e NODE_ENV=production \
-e TOKEN_ENC_KEY=... \
ghcr.io/OWNER/REPO:latest
```
</template>
<template #workers>
```bash
npm run build:worker
npx wrangler deploy deploy/cloudflare
```
</template>
</CodeTabs>
## Generate a Config
<ConfigGenerator />
## Test Requests
<ApiPlayground />
```
--------------------------------------------------------------------------------
/src/types/mcp.ts:
--------------------------------------------------------------------------------
```typescript
// Minimal MCP-like types for Phase 1 compilation.
// Replace with @modelcontextprotocol/sdk imports in later phases.
export interface ToolDefinition {
name: string
description?: string
inputSchema?: unknown
}
export interface ResourceDefinition {
uri: string
name?: string
description?: string
mimeType?: string
}
export interface PromptDefinition {
name: string
description?: string
input?: unknown
}
export interface ListToolsRequest {
type: 'list_tools'
}
export interface ListToolsResult {
tools: ToolDefinition[]
}
export interface CallToolRequest {
name: string
arguments?: Record<string, unknown> | undefined
}
export interface CallToolResult {
content: unknown
isError?: boolean
}
export interface ListResourcesRequest {
type: 'list_resources'
}
export interface ListResourcesResult {
resources: ResourceDefinition[]
}
export interface ReadResourceRequest {
uri: string
}
export interface ReadResourceResult {
contents: string | Uint8Array
mimeType?: string
}
export interface SubscribeRequest {
target: string
}
export interface SubscribeResult {
ok: boolean
}
```
--------------------------------------------------------------------------------
/tests/factories/configFactory.ts:
--------------------------------------------------------------------------------
```typescript
import type { MasterConfig, ServerConfig, AuthStrategy } from '../../src/types/config.js'
export function makeServerConfig(id: string, endpoint: string, authStrategy: AuthStrategy = 0 as any): ServerConfig {
return {
id,
type: 'local',
url: endpoint,
auth_strategy: authStrategy || 'bypass_auth',
config: { port: new URL(endpoint).port ? Number(new URL(endpoint).port) : undefined },
}
}
export function makeMasterConfig(params: {
servers: Array<{ id: string; endpoint: string }>
hosting?: Partial<MasterConfig['hosting']>
routing?: MasterConfig['routing']
master_oauth?: Partial<MasterConfig['master_oauth']>
}): MasterConfig {
const servers: ServerConfig[] = params.servers.map((s) => makeServerConfig(s.id, s.endpoint))
return {
master_oauth: {
authorization_endpoint: 'http://localhost/authorize',
token_endpoint: 'http://localhost/token',
client_id: 'local',
redirect_uri: 'http://localhost/oauth/callback',
scopes: ['openid'],
...(params.master_oauth ?? {}),
},
servers,
hosting: { platform: 'node', port: 0, ...(params.hosting ?? {}) },
routing: params.routing,
}
}
```
--------------------------------------------------------------------------------
/tests/unit/config.schema-validator.test.ts:
--------------------------------------------------------------------------------
```typescript
import '../setup/test-setup.js'
import test from 'node:test'
import assert from 'node:assert/strict'
import { SchemaValidator } from '../../src/config/schema-validator.js'
test('SchemaValidator accepts minimal valid config', async () => {
const schema = await SchemaValidator.loadSchema('config/schema.json')
const cfg = {
master_oauth: {
authorization_endpoint: 'https://auth.local/authorize',
token_endpoint: 'https://auth.local/token',
client_id: 'x',
redirect_uri: 'http://localhost/cb',
scopes: ['openid'],
},
hosting: { platform: 'node' },
servers: [],
}
assert.doesNotThrow(() => SchemaValidator.assertValid(cfg, schema!))
})
test('SchemaValidator rejects invalid platform', async () => {
const schema = await SchemaValidator.loadSchema('config/schema.json')
const cfg: any = {
master_oauth: {
authorization_endpoint: 'https://auth.local/authorize',
token_endpoint: 'https://auth.local/token',
client_id: 'x',
redirect_uri: 'http://localhost/cb',
scopes: ['openid'],
},
hosting: { platform: 'nope' },
servers: [],
}
assert.throws(() => SchemaValidator.assertValid(cfg, schema!))
})
```
--------------------------------------------------------------------------------
/config/default.json:
--------------------------------------------------------------------------------
```json
{
"master_oauth": {
"authorization_endpoint": "https://example.com/oauth/authorize",
"token_endpoint": "https://example.com/oauth/token",
"client_id": "master-mcp",
"client_secret": "env:MASTER_OAUTH_CLIENT_SECRET",
"redirect_uri": "http://localhost:3000/callback",
"scopes": ["openid", "profile"],
"audience": "master-mcp"
},
"hosting": {
"platform": "node",
"port": 3000
},
"logging": {
"level": "info"
},
"routing": {
"loadBalancer": { "strategy": "round_robin" },
"circuitBreaker": { "failureThreshold": 5, "successThreshold": 2, "recoveryTimeoutMs": 30000 },
"retry": { "maxRetries": 2, "baseDelayMs": 250, "maxDelayMs": 4000, "backoffFactor": 2, "jitter": "full" }
},
"servers": [
{
"id": "test-server",
"type": "local",
"url": "http://localhost:3006",
"auth_strategy": "bypass_auth",
"config": {
"environment": {},
"args": []
}
},
{
"id": "stdio-server",
"type": "local",
"url": "file://./examples/stdio-mcp-server.cjs",
"auth_strategy": "bypass_auth",
"config": {
"environment": {},
"args": []
}
}
]
}
```
--------------------------------------------------------------------------------
/docs/deployment/docs-site.md:
--------------------------------------------------------------------------------
```markdown
---
title: Deploy the Docs Site
---
# Deploy the Docs Site
The documentation is built with VitePress and lives under `docs/`.
## Build Locally
```bash
npm run docs:build
```
The static site is emitted to `docs/.vitepress/dist`.
## Preview Locally
```bash
npm run docs:preview
```
## GitHub Pages
Setup workflow (example):
```yaml
name: Deploy Docs
on:
push:
branches: [ main ]
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- run: npm run docs:build
- uses: actions/upload-pages-artifact@v3
with: { path: docs/.vitepress/dist }
deploy:
needs: build
runs-on: ubuntu-latest
environment: { name: github-pages, url: ${{ steps.deployment.outputs.page_url }} }
steps:
- id: deployment
uses: actions/deploy-pages@v4
```
## Cloudflare Pages
- Framework preset: None
- Build command: `npm run docs:build`
- Build output directory: `docs/.vitepress/dist`
## Netlify
- Build command: `npm run docs:build`
- Publish directory: `docs/.vitepress/dist`
```
--------------------------------------------------------------------------------
/docs/tutorials/load-balancing-and-resilience.md:
--------------------------------------------------------------------------------
```markdown
# Tutorial: Load Balancing & Resilience
Demonstrates multiple backends with load balancing, retries, and circuit breaker tuning.
## Configuration
`examples/multi-server/config.yaml` (provided):
```yaml
hosting:
platform: node
port: 3000
master_oauth:
authorization_endpoint: https://example.com/oauth/authorize
token_endpoint: https://example.com/oauth/token
client_id: master-mcp
redirect_uri: http://localhost:3000/oauth/callback
scopes: [openid]
routing:
loadBalancer:
strategy: health
circuitBreaker:
failureThreshold: 3
successThreshold: 2
recoveryTimeoutMs: 10000
retry:
maxRetries: 3
baseDelayMs: 200
maxDelayMs: 3000
backoffFactor: 2
jitter: full
retryOn:
networkErrors: true
httpStatuses: [408, 429]
httpStatusClasses: [5]
servers:
- id: compute
type: local
auth_strategy: bypass_auth
config:
port: 4101
- id: compute
type: local
auth_strategy: bypass_auth
config:
port: 4102
```
Run master with:
```
MASTER_CONFIG_PATH=examples/multi-server/config.yaml npm run dev
```
The router will choose an instance per call, retry on transient errors, and open the circuit if failures breach the threshold.
```
--------------------------------------------------------------------------------
/docs/deployment/cicd.md:
--------------------------------------------------------------------------------
```markdown
# CI/CD Pipelines
Below are example steps you can adapt for your CI provider.
## Build & Publish Docker Image (GitHub Actions)
```yaml
name: build-and-push
on:
push:
branches: [ main ]
jobs:
docker:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build
run: |
docker build -f deploy/docker/Dockerfile -t ghcr.io/${{ github.repository }}:latest .
- name: Push
run: docker push ghcr.io/${{ github.repository }}:latest
```
## Deploy to Cloudflare Workers (GitHub Actions)
```yaml
name: deploy-workers
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: deploy --env production
```
## Deploy to Koyeb (CLI from CI)
Use Koyeb’s GitHub Action or the CLI with an API token to update the service image tag after push to GHCR.
```
--------------------------------------------------------------------------------
/src/runtime/worker.ts:
--------------------------------------------------------------------------------
```typescript
// Worker runtime with minimal surface to avoid Node-specific modules
import { ConfigLoader } from '../config/config-loader.js'
import { OAuthFlowController } from '../oauth/flow-controller.js'
import { collectSystemMetrics } from '../utils/monitoring.js'
export default {
async fetch(_req: Request, env?: Record<string, unknown>): Promise<Response> {
;(globalThis as any).__WORKER_ENV = env || (globalThis as any).__WORKER_ENV || {}
try {
const url = new URL(_req.url)
if (url.pathname === '/health') {
return new Response(JSON.stringify({ ok: true }), { headers: { 'content-type': 'application/json' } })
}
if (url.pathname === '/metrics') {
return new Response(
JSON.stringify({ ok: true, system: collectSystemMetrics() }),
{ headers: { 'content-type': 'application/json' } }
)
}
if (url.pathname.startsWith('/oauth')) {
const cfg = await ConfigLoader.loadFromEnv()
const ctrl = new OAuthFlowController({ getConfig: () => cfg })
return await ctrl.handleRequest(_req)
}
return new Response(JSON.stringify({ ok: true }), {
headers: { 'content-type': 'application/json' },
})
} finally {
// keep server warm for now; no-op
}
},
}
```
--------------------------------------------------------------------------------
/docs/public/diagrams/architecture.svg:
--------------------------------------------------------------------------------
```
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 280">
<defs>
<style>
.box{fill:#fff;stroke:#0ea5e9;stroke-width:2;rx:10;}
.t{font:16px sans-serif;dominant-baseline:middle;text-anchor:middle;}
</style>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="6" refY="3" orient="auto">
<path d="M0,0 L0,6 L6,3 z" fill="#0ea5e9" />
</marker>
</defs>
<rect x="40" y="40" width="160" height="60" class="box"/>
<text x="120" y="70" class="t">MCP Client(s)</text>
<rect x="320" y="40" width="180" height="60" class="box"/>
<text x="410" y="60" class="t">Master MCP Server</text>
<text x="410" y="80" class="t">Auth • Routing • Config</text>
<rect x="620" y="20" width="140" height="40" class="box"/>
<text x="690" y="40" class="t">Server A</text>
<rect x="620" y="80" width="140" height="40" class="box"/>
<text x="690" y="100" class="t">Server B</text>
<rect x="620" y="140" width="140" height="40" class="box"/>
<text x="690" y="160" class="t">Server C</text>
<path d="M200,70 L320,70" stroke="#0ea5e9" stroke-width="2" marker-end="url(#arrow)"/>
<path d="M500,60 L620,40" stroke="#0ea5e9" stroke-width="2" marker-end="url(#arrow)"/>
<path d="M500,70 L620,100" stroke="#0ea5e9" stroke-width="2" marker-end="url(#arrow)"/>
<path d="M500,80 L620,160" stroke="#0ea5e9" stroke-width="2" marker-end="url(#arrow)"/>
</svg>
```
--------------------------------------------------------------------------------
/docs/configuration/environment-variables.md:
--------------------------------------------------------------------------------
```markdown
# Environment Variables
Environment variables can override configuration at load time. Key variables:
## Hosting
- `MASTER_HOSTING_PLATFORM` → `hosting.platform`
- `MASTER_HOSTING_PORT` → `hosting.port`
- `MASTER_BASE_URL` → `hosting.base_url`
## Logging
- `MASTER_LOG_LEVEL` → `logging.level`
## Master OAuth
- `MASTER_OAUTH_ISSUER` → `master_oauth.issuer`
- `MASTER_OAUTH_AUTHORIZATION_ENDPOINT` → `master_oauth.authorization_endpoint`
- `MASTER_OAUTH_TOKEN_ENDPOINT` → `master_oauth.token_endpoint`
- `MASTER_OAUTH_JWKS_URI` → `master_oauth.jwks_uri`
- `MASTER_OAUTH_CLIENT_ID` → `master_oauth.client_id`
- `MASTER_OAUTH_CLIENT_SECRET` → `master_oauth.client_secret` (stored as `env:MASTER_OAUTH_CLIENT_SECRET`)
- `MASTER_OAUTH_REDIRECT_URI` → `master_oauth.redirect_uri`
- `MASTER_OAUTH_SCOPES` → comma-separated list → `master_oauth.scopes[]`
- `MASTER_OAUTH_AUDIENCE` → `master_oauth.audience`
## Servers (bulk)
- `MASTER_SERVERS` → JSON array of servers
- `MASTER_SERVERS_YAML` → YAML array of servers
## Config discovery and env
- `MASTER_CONFIG_PATH` → explicit path to YAML/JSON config file
- `MASTER_ENV` / `NODE_ENV` → selects env-specific overrides and affects runtime behavior
## Secrets & Tokens
- `MASTER_CONFIG_KEY` (or `MASTER_SECRET_KEY`) → decrypts `enc:gcm:` config values
- `TOKEN_ENC_KEY` → encrypts stored delegated/proxy tokens (REQUIRED in production)
See `.env.example` for a template.
```
--------------------------------------------------------------------------------
/docs/guides/server-sharing.md:
--------------------------------------------------------------------------------
```markdown
---
title: Server Sharing
---
# Server Sharing
Expose multiple MCP backends through the master server and share a single, consistent endpoint with your team or applications.
## Add Backends
Backends are defined in your master config under `servers`.
```yaml
servers:
- id: search
type: local
auth_strategy: master_oauth
config: { port: 4100 }
- id: github-tools
type: local
auth_strategy: delegate_oauth
auth_config:
provider: github
authorization_endpoint: https://github.com/login/oauth/authorize
token_endpoint: https://github.com/login/oauth/access_token
client_id: ${GITHUB_CLIENT_ID}
client_secret: env:GITHUB_CLIENT_SECRET
scopes: [repo, read:user]
config: { port: 4010 }
```
Name collisions are avoided by prefixing capabilities with the server id (e.g., `search.query`, `github-tools.repo.read`).
## Auth Strategies per Server
Choose one per server: `master_oauth`, `delegate_oauth`, `proxy_oauth`, `bypass_auth`.
<AuthFlowDemo />
## Share the Endpoint
- Local: `http://localhost:<port>`
- Docker: container port mapped to host
- Workers: public URL from your deployment
Distribute the base URL along with any client token requirements.
## Health Monitoring & Logs
- `GET /health` and `GET /metrics`
- Container logs (Docker/Koyeb) or platform logs (Workers)
- Use `performHealthChecks()` from code if embedding the master as a library
```
--------------------------------------------------------------------------------
/docs/tutorials/oauth-delegation-github.md:
--------------------------------------------------------------------------------
```markdown
# Tutorial: OAuth Delegation (GitHub)
Goal: Use delegated OAuth for a backend requiring GitHub OAuth.
## 1) Create a GitHub OAuth App
- Homepage URL: `http://localhost:3000`
- Authorization callback URL: `http://localhost:3000/oauth/callback`
Record `Client ID` and `Client Secret`.
## 2) Configuration
`examples/oauth-node/config.yaml` (provided):
```yaml
hosting:
platform: node
port: 3000
master_oauth:
authorization_endpoint: https://example.com/oauth/authorize
token_endpoint: https://example.com/oauth/token
client_id: master-mcp
redirect_uri: http://localhost:3000/oauth/callback
scopes: [openid]
servers:
- id: github-tools
type: local
auth_strategy: delegate_oauth
auth_config:
provider: github
authorization_endpoint: https://github.com/login/oauth/authorize
token_endpoint: https://github.com/login/oauth/access_token
client_id: ${GITHUB_CLIENT_ID}
client_secret: env:GITHUB_CLIENT_SECRET
scopes: [repo, read:user]
config:
port: 4100
```
Set environment variable:
```
export GITHUB_CLIENT_SECRET=... # from GitHub app
```
Run with:
```
MASTER_CONFIG_PATH=examples/oauth-node/config.yaml npm run dev
```
## 3) Start the Flow
Navigate to:
```
http://localhost:3000/oauth/authorize?server_id=github-tools
```
Complete the GitHub consent, then you should see a success page. Calls to tools under `github-tools.*` will now include the delegated token.
```
--------------------------------------------------------------------------------
/tests/unit/utils.crypto.test.ts:
--------------------------------------------------------------------------------
```typescript
import '../setup/test-setup.js'
import test from 'node:test'
import assert from 'node:assert/strict'
import { CryptoUtils } from '../../src/utils/crypto.js'
test('CryptoUtils encrypt/decrypt roundtrip', () => {
const key = 'super-secret-key'
const text = 'hello world ' + Date.now()
const enc = CryptoUtils.encrypt(text, key)
assert.ok(typeof enc === 'string' && enc.length > 16)
const dec = CryptoUtils.decrypt(enc, key)
assert.equal(dec, text)
})
test('CryptoUtils hash/verify', () => {
const h = CryptoUtils.hash('abc')
assert.ok(h.length === 64)
assert.ok(CryptoUtils.verify('abc', h))
assert.equal(CryptoUtils.verify('abcd', h), false)
})
test('CryptoUtils pbkdf2 and scrypt hashing', () => {
const p = 'password'
const pb = CryptoUtils.pbkdf2Hash(p, 10_000, 8)
assert.ok(pb.startsWith('pbkdf2$sha256$'))
assert.ok(CryptoUtils.pbkdf2Verify(p, pb))
assert.equal(CryptoUtils.pbkdf2Verify('nope', pb), false)
const sc = CryptoUtils.scryptHash(p, { N: 1024, r: 8, p: 1, saltLen: 8, keyLen: 16 })
assert.ok(sc.startsWith('scrypt$'))
assert.ok(CryptoUtils.scryptVerify(p, sc))
assert.equal(CryptoUtils.scryptVerify('nope', sc), false)
})
test('CryptoUtils bcryptHash falls back but verifies', async () => {
const p = 'topsecret'
const h = await CryptoUtils.bcryptHash(p)
assert.ok(typeof h === 'string')
assert.ok(await CryptoUtils.bcryptVerify(p, h))
assert.equal(await CryptoUtils.bcryptVerify('nope', h), false)
})
```
--------------------------------------------------------------------------------
/tests/security/security.oauth-and-input.test.ts:
--------------------------------------------------------------------------------
```typescript
import '../setup/test-setup.js'
import test from 'node:test'
import assert from 'node:assert/strict'
import { CallbackHandler } from '../../src/oauth/callback-handler.js'
import { PKCEManager } from '../../src/oauth/pkce-manager.js'
import { StateManager } from '../../src/oauth/state-manager.js'
import { TokenManager, InMemoryTokenStorage } from '../../src/auth/token-manager.js'
test('CallbackHandler rejects missing/invalid state', async () => {
const cb = new CallbackHandler({
config: { master_oauth: { authorization_endpoint: 'http://a', token_endpoint: 'http://t', client_id: 'x', redirect_uri: 'http://l', scopes: ['openid'] }, hosting: { platform: 'node' }, servers: [] } as any,
stateManager: new StateManager(),
pkceManager: new PKCEManager(),
baseUrl: 'http://localhost',
})
const res = await cb.handleCallback(new URLSearchParams({ state: 'nope', code: 'x' }), { provider: 'custom', authorization_endpoint: 'http://a', token_endpoint: 'http://t', client_id: 'x' })
assert.ok(res.error)
})
test('TokenManager decryption failure is handled and entry removed', async () => {
const storage = new InMemoryTokenStorage()
const tm1 = new TokenManager({ storage, secret: 'a' })
const tm2 = new TokenManager({ storage, secret: 'b' })
const key = 'k'
await tm1.storeToken(key, { access_token: 'X', expires_at: Date.now() + 1000, scope: [] })
const before = await tm2.getToken(key)
assert.equal(before, null) // decryption failed => deleted
})
```
--------------------------------------------------------------------------------
/docs/troubleshooting/index.md:
--------------------------------------------------------------------------------
```markdown
---
title: Common Issues
---
# Troubleshooting: Common Issues
- Invalid configuration: run `npm run docs:config` and compare with schema.
- OAuth callback fails: verify redirect URIs, state/PKCE, and client secrets.
- Workers runtime errors: avoid Node-only APIs; use Web Crypto and Fetch.
- Routing loop or failure: check circuit breaker status and retry limits.
- CORS/Networking: ensure your hosting platform permits required egress.
## FAQ
<details>
<summary>How do I connect a GUI client like Claude Desktop?</summary>
If your version supports remote/HTTP MCP servers, point it at your master base URL and include any required bearer token. Otherwise, run a light stdio→HTTP bridge that forwards MCP requests to the master’s `/mcp/*` endpoints.
</details>
<details>
<summary>Why do I get 401/403 responses?</summary>
The backend server may require a different token than your client token. Configure `delegate_oauth` or `proxy_oauth` on that backend, then complete the OAuth flow via `/oauth/authorize?server_id=<id>`.
</details>
<details>
<summary>Tools or resources are missing in the client.</summary>
Confirm each backend is healthy and exposes capabilities. Check `/capabilities` and `/mcp/tools/list`. Prefix names with the server id (e.g., `serverId.toolName`).
</details>
<details>
<summary>Requests time out under load.</summary>
Tune retries and circuit breaker thresholds in `routing`, and monitor p95/p99 latencies. See Advanced → Performance & Scalability.
</details>
```
--------------------------------------------------------------------------------
/tests/mocks/mcp/fake-backend.ts:
--------------------------------------------------------------------------------
```typescript
import type http from 'node:http'
import { createTestServer } from '../../_utils/test-server.js'
export interface FakeBackendOptions {
id: string
tools?: Array<{ name: string; description?: string }>
resources?: Array<{ uri: string; description?: string; mimeType?: string }>
}
export async function startFakeMcpBackend(opts: FakeBackendOptions): Promise<{ url: string; stop: () => Promise<void> }> {
const srv = await createTestServer()
const tools = opts.tools ?? [{ name: 'echo', description: 'Echo input' }]
const resources = opts.resources ?? []
srv.register('GET', '/health', () => ({ body: { ok: true } }))
srv.register('GET', '/capabilities', () => ({ body: { tools, resources } }))
srv.register('POST', '/mcp/tools/list', () => ({ body: { tools } }))
srv.register('POST', '/mcp/resources/list', () => ({ body: { resources } }))
srv.register('POST', '/mcp/tools/call', (_req: http.IncomingMessage, raw) => {
const body = safeParse(raw)
if (body?.name === 'echo') return { body: { content: body?.arguments ?? {}, isError: false } }
return { body: { content: { error: 'unknown tool' }, isError: true } }
})
srv.register('POST', '/mcp/resources/read', (_req, raw) => {
const body = safeParse(raw)
return { body: { contents: `content:${body?.uri ?? ''}`, mimeType: 'text/plain' } }
})
return { url: srv.url, stop: srv.close }
}
function safeParse(raw?: string): any {
try { return raw ? JSON.parse(raw) : undefined } catch { return undefined }
}
```
--------------------------------------------------------------------------------
/docs/getting-started.md:
--------------------------------------------------------------------------------
```markdown
# Getting Started
This guide walks you through running Master MCP Server locally, configuring backends, and discovering capabilities.
## Prerequisites
- Node.js >= 18.17
- npm
- Network access to install dependencies and reach your OAuth providers/backends
## Install
```
npm ci
```
## Configure
1) Copy env template and edit as needed:
```
cp .env.example .env
```
2) Put your configuration in `config/` or pass a YAML/JSON path via `MASTER_CONFIG_PATH`. The built-in schema lives in `config/schema.json` and is also embedded as a fallback.
Example minimal YAML (single local server):
```yaml
hosting:
platform: node
port: 3000
master_oauth:
authorization_endpoint: https://example.com/oauth/authorize
token_endpoint: https://example.com/oauth/token
client_id: master-mcp
redirect_uri: http://localhost:3000/oauth/callback
scopes: [openid]
servers:
- id: tools
type: local
auth_strategy: bypass_auth
config:
port: 3333
```
## Run Dev Server
```
npm run dev
```
If your config is outside `config/`, set:
```
MASTER_CONFIG_PATH=examples/sample-configs/basic.yaml npm run dev
```
## Verify
- `GET http://localhost:3000/health` → `{ ok: true }`
- `POST http://localhost:3000/mcp/tools/list`
- `POST http://localhost:3000/mcp/resources/list`
When calling tools/resources on protected backends, include `Authorization: Bearer <token>`.
## Next Steps
- Read `docs/guides/authentication.md` for OAuth flows
- See `examples/*` to run end-to-end scenarios
- Deploy using `docs/deployment/*`
```
--------------------------------------------------------------------------------
/tests/unit/auth.multi-auth-manager.test.ts:
--------------------------------------------------------------------------------
```typescript
import '../setup/test-setup.js'
import test from 'node:test'
import assert from 'node:assert/strict'
import { MultiAuthManager } from '../../src/auth/multi-auth-manager.js'
import { AuthStrategy } from '../../src/types/config.js'
const masterCfg = {
authorization_endpoint: 'http://localhost/auth',
token_endpoint: 'http://localhost/token',
client_id: 'master',
redirect_uri: 'http://localhost/cb',
scopes: ['openid'],
}
test('MultiAuthManager pass-through and delegation', async (t) => {
try {
const mam = new MultiAuthManager(masterCfg as any)
mam.registerServerAuth('srv1', AuthStrategy.MASTER_OAUTH)
const h = await mam.prepareAuthForBackend('srv1', 'CLIENT')
assert.equal(h.Authorization, 'Bearer CLIENT')
mam.registerServerAuth('srv2', AuthStrategy.DELEGATE_OAUTH, {
provider: 'custom', authorization_endpoint: 'http://p/auth', token_endpoint: 'http://p/token', client_id: 'c'
})
const d = await mam.prepareAuthForBackend('srv2', 'CLIENT') as any
assert.equal(d.type, 'oauth_delegation')
} catch (error) {
console.error('Test failed:', error)
throw error
}
})
test('MultiAuthManager stores delegated server token', async (t) => {
try {
const mam = new MultiAuthManager(masterCfg as any)
await mam.storeDelegatedToken('CLIENT', 'srv', { access_token: 'S', expires_at: Date.now() + 1000, scope: [] })
const tok = await mam.getStoredServerToken('srv', 'CLIENT')
assert.equal(tok, 'S')
} catch (error) {
console.error('Test failed:', error)
throw error
}
})
```
--------------------------------------------------------------------------------
/debug-stdio.js:
--------------------------------------------------------------------------------
```javascript
import { StdioManager } from './src/modules/stdio-manager.js'
import { Logger } from './src/utils/logger.js'
// Set logger to debug level
Logger.configure({ level: 'debug' })
async function testStdio() {
const stdioManager = new StdioManager()
const serverId = 'test-stdio-server'
const filePath = './examples/stdio-mcp-server.cjs'
try {
console.log('Starting STDIO server...')
const serverProcess = await stdioManager.startServer(serverId, filePath)
console.log('STDIO server started successfully:', serverProcess)
// Send a simple request to see if we can communicate
console.log('Sending initialize request...')
const initializeRequestId = Date.now()
const initializeRequest = {
jsonrpc: "2.0",
id: initializeRequestId,
method: "initialize",
params: {
protocolVersion: "2025-06-18",
capabilities: {},
clientInfo: {
name: "debug-script",
version: "1.0.0"
}
}
}
await stdioManager.sendMessage(serverId, initializeRequest)
console.log('Initialize request sent')
// Wait for response
console.log('Waiting for response...')
const response = await stdioManager.waitForResponse(serverId, initializeRequestId, 5000) // 5 second timeout
console.log('Received response:', response)
// Stop the server
console.log('Stopping server...')
await serverProcess.stop()
console.log('Server stopped')
} catch (error) {
console.error('Error in STDIO test:', error)
}
}
testStdio()
```
--------------------------------------------------------------------------------
/debug-stdio.cjs:
--------------------------------------------------------------------------------
```
const { StdioManager } = require('./dist/node/modules/stdio-manager.js')
const { Logger } = require('./dist/node/utils/logger.js')
// Set logger to debug level
Logger.configure({ level: 'debug' })
async function testStdio() {
const stdioManager = new StdioManager()
const serverId = 'test-stdio-server'
const filePath = './examples/stdio-mcp-server.cjs'
try {
console.log('Starting STDIO server...')
const serverProcess = await stdioManager.startServer(serverId, filePath)
console.log('STDIO server started successfully:', serverProcess)
// Send a simple request to see if we can communicate
console.log('Sending initialize request...')
const initializeRequestId = Date.now()
const initializeRequest = {
jsonrpc: "2.0",
id: initializeRequestId,
method: "initialize",
params: {
protocolVersion: "2025-06-18",
capabilities: {},
clientInfo: {
name: "debug-script",
version: "1.0.0"
}
}
}
await stdioManager.sendMessage(serverId, initializeRequest)
console.log('Initialize request sent')
// Wait for response
console.log('Waiting for response...')
const response = await stdioManager.waitForResponse(serverId, initializeRequestId, 5000) // 5 second timeout
console.log('Received response:', response)
// Stop the server
console.log('Stopping server...')
await serverProcess.stop()
console.log('Server stopped')
} catch (error) {
console.error('Error in STDIO test:', error)
}
}
testStdio()
```
--------------------------------------------------------------------------------
/tests/utils/fake-express.ts:
--------------------------------------------------------------------------------
```typescript
type Handler = (req: any, res: any) => void | Promise<void>
export class FakeExpressApp {
routes: Record<string, { method: 'GET'|'POST'; handler: Handler }> = {}
use(_arg: any): void { /* ignore middleware */ }
get(path: string, handler: Handler): void { this.routes[`GET ${path}`] = { method: 'GET', handler } }
post(path: string, handler: Handler): void { this.routes[`POST ${path}`] = { method: 'POST', handler } }
async invoke(method: 'GET'|'POST', path: string, options?: { query?: Record<string,string>, headers?: Record<string,string>, body?: any }) {
const key = `${method} ${path}`
const route = this.routes[key]
if (!route) throw new Error(`Route not found: ${key}`)
const req = {
method,
query: options?.query ?? {},
headers: options?.headers ?? {},
body: options?.body ?? undefined,
protocol: 'http',
get: (h: string) => (options?.headers?.[h.toLowerCase()] ?? options?.headers?.[h] ?? undefined),
}
let statusCode = 200
let sentHeaders: Record<string,string> = {}
let payload: any
const res = {
set: (k: string, v: string) => { sentHeaders[k.toLowerCase()] = v },
status: (c: number) => { statusCode = c; return res },
send: (b: any) => { payload = b },
json: (b: any) => { sentHeaders['content-type'] = 'application/json'; payload = JSON.stringify(b) },
redirect: (loc: string) => { statusCode = 302; sentHeaders['location'] = loc; payload = '' },
}
await route.handler(req, res)
return { status: statusCode, headers: sentHeaders, body: payload }
}
}
```
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/_metadata.json:
--------------------------------------------------------------------------------
```json
{
"hash": "3c026b6f",
"configHash": "1d5e3756",
"lockfileHash": "b26962c5",
"browserHash": "5431331e",
"optimized": {
"vue": {
"src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js",
"fileHash": "7cfbfc66",
"needsInterop": false
},
"vitepress > @vue/devtools-api": {
"src": "../../../../node_modules/@vue/devtools-api/dist/index.js",
"file": "vitepress___@vue_devtools-api.js",
"fileHash": "89ef8781",
"needsInterop": false
},
"vitepress > @vueuse/core": {
"src": "../../../../node_modules/@vueuse/core/index.mjs",
"file": "vitepress___@vueuse_core.js",
"fileHash": "12a4fb0b",
"needsInterop": false
},
"vitepress > @vueuse/integrations/useFocusTrap": {
"src": "../../../../node_modules/@vueuse/integrations/useFocusTrap.mjs",
"file": "vitepress___@vueuse_integrations_useFocusTrap.js",
"fileHash": "b5a95a87",
"needsInterop": false
},
"vitepress > mark.js/src/vanilla.js": {
"src": "../../../../node_modules/mark.js/src/vanilla.js",
"file": "vitepress___mark__js_src_vanilla__js.js",
"fileHash": "38188df1",
"needsInterop": false
},
"vitepress > minisearch": {
"src": "../../../../node_modules/minisearch/dist/es/index.js",
"file": "vitepress___minisearch.js",
"fileHash": "996cffe0",
"needsInterop": false
}
},
"chunks": {
"chunk-P2XGSYO7": {
"file": "chunk-P2XGSYO7.js"
},
"chunk-HVR2FF6M": {
"file": "chunk-HVR2FF6M.js"
}
}
}
```
--------------------------------------------------------------------------------
/tests/e2e/flow-controller.worker.test.ts:
--------------------------------------------------------------------------------
```typescript
import '../setup/test-setup.js'
import test from 'node:test'
import assert from 'node:assert/strict'
import { OAuthFlowController } from '../../src/oauth/flow-controller.js'
import { createMockServer } from '../utils/mock-http.js'
test('OAuthFlowController Worker-style authorize and callback', async () => {
const tokenSrv = await createMockServer([
{ method: 'POST', path: '/token', handler: () => ({ body: { access_token: 'AT', expires_in: 60, scope: 'openid' } }) },
])
try {
const cfg = {
master_oauth: {
authorization_endpoint: tokenSrv.url + '/authorize',
token_endpoint: tokenSrv.url + '/token',
client_id: 'cid',
redirect_uri: 'http://localhost/oauth/callback',
scopes: ['openid'],
},
hosting: { platform: 'cloudflare-workers', base_url: 'http://localhost' },
servers: [],
}
const ctrl = new OAuthFlowController({ getConfig: () => cfg as any })
const base = 'http://localhost'
const authRes = await ctrl.handleRequest(new Request(base + '/oauth/authorize?provider=master', { method: 'GET' }))
assert.equal(authRes.status, 200)
const html = await authRes.text()
const m = html.match(/url=([^"\s]+)/)
assert.ok(m && m[1])
const urlStr = m[1].replace(/&/g, '&') // Decode HTML entities
const state = new URL(urlStr).searchParams.get('state')!
const cbRes = await ctrl.handleRequest(new Request(base + `/oauth/callback?state=${encodeURIComponent(state)}&code=good&provider=master`, { method: 'GET' }))
assert.equal(cbRes.status, 200)
} finally {
await tokenSrv.close()
}
})
```
--------------------------------------------------------------------------------
/tests/unit/oauth.pkce-state.test.ts:
--------------------------------------------------------------------------------
```typescript
import '../setup/test-setup.js'
import test from 'node:test'
import assert from 'node:assert/strict'
import { PKCEManager } from '../../src/oauth/pkce-manager.js'
import { StateManager } from '../../src/oauth/state-manager.js'
import { FlowValidator } from '../../src/oauth/flow-validator.js'
test('PKCEManager generates and verifies', async () => {
const pkce = new PKCEManager({ ttlMs: 1000 })
const state = 'abc'
const { challenge, method, verifier } = await pkce.generate(state)
assert.ok(challenge.length > 16)
assert.equal(method, 'S256')
const v = pkce.getVerifier(state)
assert.equal(v, verifier)
// consumed; second time should be undefined
assert.equal(pkce.getVerifier(state), undefined)
})
test('StateManager create/consume with TTL', async () => {
const sm = new StateManager({ ttlMs: 10 })
const s = sm.create({ provider: 'p', issuedAt: 0 } as any)
const peek = sm.peek(s)
assert.ok(peek && peek.provider === 'p')
const used = sm.consume(s)
assert.ok(used)
assert.equal(sm.consume(s), null)
})
test('FlowValidator validateReturnTo prevents open redirects', () => {
const fv = new FlowValidator(() => ({
master_oauth: {
authorization_endpoint: 'https://a', token_endpoint: 'https://t', client_id: 'x', redirect_uri: 'http://l', scopes: ['openid']
}, hosting: { platform: 'node' }, servers: []
} as any))
assert.equal(fv.validateReturnTo('http://evil.com', 'http://localhost:3000'), undefined)
assert.equal(fv.validateReturnTo('http://localhost:3000/path', 'http://localhost:3000'), '/path')
assert.equal(fv.validateReturnTo('/ok', 'http://x'), '/ok')
})
```
--------------------------------------------------------------------------------
/docs/guides/configuration-management.md:
--------------------------------------------------------------------------------
```markdown
# Configuration Management
Master MCP Server supports JSON or YAML configuration files, environment variable overrides, CLI overrides, schema validation, and secret resolution.
## Files
- Default paths: `config/default.json`, `config/<env>.json`
- Explicit path: set `MASTER_CONFIG_PATH=/path/to/config.yaml`
- Schema: `config/schema.json` (also embedded in code as a fallback)
## Environment Overrides
Environment variables map to config fields. Key ones:
- `MASTER_HOSTING_PLATFORM`, `MASTER_HOSTING_PORT`, `MASTER_BASE_URL`
- `MASTER_LOG_LEVEL`
- `MASTER_OAUTH_*` (ISSUER, AUTHORIZATION_ENDPOINT, TOKEN_ENDPOINT, CLIENT_ID, CLIENT_SECRET, REDIRECT_URI, SCOPES, AUDIENCE)
- `MASTER_SERVERS` (JSON) or `MASTER_SERVERS_YAML` (YAML)
See `docs/configuration/environment-variables.md` and `.env.example`.
## CLI Overrides
You can override nested fields with dotted keys:
```
node dist/node/index.js --hosting.port=4000 --routing.retry.maxRetries=3
```
## Secrets
- `env:VARNAME` → replaced by `process.env.VARNAME` at load time
- `enc:gcm:<base64>` → decrypted using `MASTER_CONFIG_KEY` (or `MASTER_SECRET_KEY`)
Use `SecretManager` to encrypt/decrypt/rotate secrets safely.
## Hot Reload (Node)
When `ConfigManager` is created with `{ watch: true }`, changes to `config/default.json`, `config/<env>.json`, or an explicit path will be validated and emitted. Some changes (e.g., hosting.port) still require a restart.
## Validation
Configs are validated using a lightweight schema validator (`SchemaValidator`) with support for types, enums, required fields, arrays, and formats (`url`, `integer`). On failure, the error lists the exact path and reason.
```
--------------------------------------------------------------------------------
/src/utils/cache.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Simple in-memory TTL cache with optional memoization helpers.
*/
export interface CacheEntry<V> {
value: V
expiresAt: number
}
export class TTLCache<K, V> {
private store = new Map<K, CacheEntry<V>>()
constructor(private defaultTtlMs = 60_000) {}
set(key: K, value: V, ttlMs?: number): void {
const ttl = ttlMs ?? this.defaultTtlMs
const expiresAt = Date.now() + ttl
this.store.set(key, { value, expiresAt })
}
get(key: K): V | undefined {
const hit = this.store.get(key)
if (!hit) return undefined
if (hit.expiresAt < Date.now()) {
this.store.delete(key)
return undefined
}
return hit.value
}
has(key: K): boolean {
return this.get(key) !== undefined
}
delete(key: K): void {
this.store.delete(key)
}
clear(): void {
this.store.clear()
}
size(): number {
return this.store.size
}
sweep(): void {
const now = Date.now()
for (const [k, v] of this.store.entries()) {
if (v.expiresAt < now) this.store.delete(k)
}
}
async getOrSet(key: K, loader: () => Promise<V>, ttlMs?: number): Promise<V> {
const existing = this.get(key)
if (existing !== undefined) return existing
const v = await loader()
this.set(key, v, ttlMs)
return v
}
}
export function memoizeAsync<A extends unknown[], R>(fn: (...args: A) => Promise<R>, ttlMs = 60_000): (...args: A) => Promise<R> {
const cache = new TTLCache<string, R>(ttlMs)
return async (...args: A) => {
const key = JSON.stringify(args)
const hit = cache.get(key)
if (hit !== undefined) return hit
const res = await fn(...args)
cache.set(key, res, ttlMs)
return res
}
}
```
--------------------------------------------------------------------------------
/docs/.vitepress/theme/style.css:
--------------------------------------------------------------------------------
```css
:root {
--mcp-accent: #0ea5e9;
--mcp-accent-600: #0284c7;
--mcp-bg-soft: color-mix(in oklab, var(--vp-c-bg) 90%, var(--mcp-accent) 10%);
}
.mcp-kicker {
font-size: .9rem;
color: var(--vp-c-text-2);
text-transform: uppercase;
letter-spacing: .08em;
}
/* Tabs */
.mcp-tabs {
border: 1px solid var(--vp-c-divider);
border-radius: 10px;
overflow: hidden;
background: var(--vp-c-bg-soft);
}
.mcp-tabs__nav {
display: flex;
gap: 6px;
padding: 8px;
background: var(--vp-c-bg);
border-bottom: 1px solid var(--vp-c-divider);
}
.mcp-tabs__btn {
appearance: none;
border: 1px solid var(--vp-c-divider);
padding: 6px 12px;
border-radius: 8px;
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
cursor: pointer;
font-weight: 500;
}
.mcp-tabs__btn[aria-selected="true"] {
background: var(--mcp-accent);
border-color: var(--mcp-accent);
color: white;
}
.mcp-tabs__panel {
padding: 12px 14px;
}
/* Utility blocks */
.mcp-callout {
border-left: 3px solid var(--mcp-accent);
padding: 10px 14px;
margin: 10px 0;
background: var(--vp-c-bg-soft);
}
.mcp-grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 16px;
}
.mcp-col-6 { grid-column: span 6; }
.mcp-col-12 { grid-column: span 12; }
@media (max-width: 960px) {
.mcp-col-6 { grid-column: span 12; }
}
.mcp-diagram {
width: 100%;
border: 1px dashed var(--vp-c-divider);
border-radius: 10px;
padding: 12px;
}
.mcp-cta {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--mcp-accent);
color: white;
padding: 8px 12px;
border-radius: 8px;
text-decoration: none;
}
.mcp-cta:hover { background: var(--mcp-accent-600); }
```
--------------------------------------------------------------------------------
/tests/unit/routing.core.test.ts:
--------------------------------------------------------------------------------
```typescript
import '../setup/test-setup.js'
import test from 'node:test'
import assert from 'node:assert/strict'
import { CircuitBreaker, CircuitOpenError } from '../../src/routing/circuit-breaker.js'
import { RetryHandler } from '../../src/routing/retry-handler.js'
import { LoadBalancer } from '../../src/routing/load-balancer.js'
test('CircuitBreaker opens and recovers', async () => {
const cb = new CircuitBreaker({ failureThreshold: 2, successThreshold: 1, recoveryTimeoutMs: 10 })
const key = 'svc::inst'
await assert.rejects(cb.execute(key, async () => { throw new Error('fail') }))
await assert.rejects(cb.execute(key, async () => { throw new Error('fail') }))
// Now circuit open
await assert.rejects(cb.execute(key, async () => 'ok'), (e: any) => e instanceof CircuitOpenError)
// Wait for half-open
await new Promise((r) => setTimeout(r, 12))
const res = await cb.execute(key, async () => 'ok')
assert.equal(res, 'ok')
})
test('RetryHandler retries on 5xx and succeeds', async () => {
const rh = new RetryHandler({ maxRetries: 2, baseDelayMs: 1, maxDelayMs: 2, jitter: 'none' })
let n = 0
const res = await rh.execute(async () => {
n++
if (n < 3) { const err: any = new Error('HTTP 500'); err.status = 500; throw err }
return 'ok'
})
assert.equal(res, 'ok')
assert.equal(n, 3)
})
test('LoadBalancer round-robin selection', () => {
const lb = new LoadBalancer({ strategy: 'round_robin' })
const pool = [ { id: 'a' }, { id: 'b' }, { id: 'c' } ] as any
const chosen = [
lb.select('svc', pool)!.id,
lb.select('svc', pool)!.id,
lb.select('svc', pool)!.id,
lb.select('svc', pool)!.id,
]
assert.deepEqual(chosen, ['a','b','c','a'])
})
```
--------------------------------------------------------------------------------
/src/utils/time.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Date/time utilities, duration parsing and timezone helpers.
*/
export function now(): number {
if (typeof performance !== 'undefined' && typeof performance.now === 'function') return performance.now()
return Date.now()
}
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms))
}
/** Parses durations like "500ms", "2s", "5m", "1h", "1d". */
export function parseDuration(input: string): number {
const m = String(input).trim().match(/^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/i)
if (!m) throw new Error('Invalid duration')
const n = parseFloat(m[1])
const u = m[2].toLowerCase()
switch (u) {
case 'ms':
return n
case 's':
return n * 1000
case 'm':
return n * 60_000
case 'h':
return n * 3_600_000
case 'd':
return n * 86_400_000
default:
throw new Error('Invalid duration unit')
}
}
export function formatDuration(ms: number): string {
if (ms < 1000) return `${ms}ms`
if (ms < 60_000) return `${(ms / 1000).toFixed(ms % 1000 === 0 ? 0 : 2)}s`
if (ms < 3_600_000) return `${(ms / 60_000).toFixed(ms % 60_000 === 0 ? 0 : 2)}m`
if (ms < 86_400_000) return `${(ms / 3_600_000).toFixed(ms % 3_600_000 === 0 ? 0 : 2)}h`
return `${(ms / 86_400_000).toFixed(ms % 86_400_000 === 0 ? 0 : 2)}d`
}
export function toUTC(date: Date): string {
return date.toISOString()
}
export function fromUnix(seconds: number): Date {
return new Date(seconds * 1000)
}
export function formatInTimeZone(date: Date, timeZone: string, opts?: Intl.DateTimeFormatOptions): string {
const formatter = new Intl.DateTimeFormat('en-US', { timeZone, ...opts })
return formatter.format(date)
}
```
--------------------------------------------------------------------------------
/tests/perf/perf.auth-and-routing.test.ts:
--------------------------------------------------------------------------------
```typescript
import '../setup/test-setup.js'
import test from 'node:test'
import { performance } from 'node:perf_hooks'
import { MultiAuthManager } from '../../src/auth/multi-auth-manager.js'
import { AuthStrategy } from '../../src/types/config.js'
import { createMockServer } from '../utils/mock-http.js'
import { RequestRouter } from '../../src/modules/request-router.js'
import { CapabilityAggregator } from '../../src/modules/capability-aggregator.js'
test('Perf: validateClientToken and routeCallTool throughput (smoke)', async (t) => {
const mam = new MultiAuthManager({ authorization_endpoint: 'http://a', token_endpoint: 'http://t', client_id: 'x', redirect_uri: 'http://l', scopes: ['openid'] } as any)
mam.registerServerAuth('s', AuthStrategy.BYPASS_AUTH)
const N = 1000
const t0 = performance.now()
for (let i = 0; i < N; i++) await mam.validateClientToken('opaque-token')
const dt = performance.now() - t0
t.diagnostic(`validateClientToken x${N}: ${Math.round(dt)}ms (${Math.round((N/dt)*1000)} ops/sec)`) // eslint-disable-line
const upstream = await createMockServer([
{ method: 'POST', path: '/mcp/tools/call', handler: () => ({ body: { content: { ok: true } } }) },
])
const servers = new Map<string, any>([[ 's', { id: 's', type: 'node', endpoint: upstream.url, config: {} as any, status: 'running', lastHealthCheck: 0 } ]])
const rr = new RequestRouter(servers as any, new CapabilityAggregator())
const M = 200
const t1 = performance.now()
for (let i = 0; i < M; i++) await rr.routeCallTool({ name: 's.ping' })
const dt2 = performance.now() - t1
t.diagnostic(`routeCallTool x${M}: ${Math.round(dt2)}ms (${Math.round((M/dt2)*1000)} rps)`) // eslint-disable-line
await upstream.close()
})
```
--------------------------------------------------------------------------------
/docs/public/logo.svg:
--------------------------------------------------------------------------------
```
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 120 120"
version="1.1"
id="svg4"
sodipodi:docname="logo.svg"
inkscape:export-filename="logo.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
inkscape:version="1.4 (e7c3feb1, 2024-10-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview4"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="2.9371694"
inkscape:cx="38.131951"
inkscape:cy="32.68453"
inkscape:window-width="1512"
inkscape:window-height="945"
inkscape:window-x="0"
inkscape:window-y="37"
inkscape:window-maximized="0"
inkscape:current-layer="svg4" />
<defs
id="defs2">
<linearGradient
id="g"
x1="0"
y1="0"
x2="1"
y2="1">
<stop
offset="0%"
stop-color="#7C3AED"
id="stop1" />
<stop
offset="100%"
stop-color="#06B6D4"
id="stop2" />
</linearGradient>
</defs>
<circle
cx="60"
cy="60"
r="56"
fill="url(#g)"
id="circle2" />
<g
fill="#fff"
id="g4">
<circle
cx="60"
cy="60"
r="10"
id="circle3" />
<path
d="M60 20 a40 40 0 0 1 0 80"
fill="none"
stroke="#fff"
stroke-width="6"
id="path3" />
<path
d="M20 60 a40 40 0 0 1 80 0"
fill="none"
stroke="#fff"
stroke-width="6"
id="path4" />
</g>
Sorry, your browser does not support inline SVG.
</svg>
```
--------------------------------------------------------------------------------
/docs/configuration/examples.md:
--------------------------------------------------------------------------------
```markdown
---
title: Configuration Examples
---
# Configuration Examples
Real-world scenarios to use as starting points.
## Minimal Local Aggregation
```yaml
hosting:
port: 3000
servers:
- id: search
type: local
auth_strategy: master_oauth
config: { port: 4100 }
```
## Mixed Auth Strategies (GitHub Delegation)
```yaml
hosting:
port: 3000
base_url: https://your.domain
servers:
- id: search
type: local
auth_strategy: master_oauth
config: { port: 4100 }
- id: github-tools
type: local
auth_strategy: delegate_oauth
auth_config:
provider: github
authorization_endpoint: https://github.com/login/oauth/authorize
token_endpoint: https://github.com/login/oauth/access_token
client_id: ${GITHUB_CLIENT_ID}
client_secret: env:GITHUB_CLIENT_SECRET
scopes: [repo, read:user]
config: { port: 4010 }
routing:
retry:
maxRetries: 2
baseDelayMs: 200
circuitBreaker:
failureThreshold: 5
successThreshold: 2
recoveryTimeoutMs: 10000
```
## Dockerized Production
```yaml
hosting:
port: 3000
servers:
- id: search
type: local
auth_strategy: bypass_auth
config: { url: http://search:4100 }
```
Run with env:
```bash
TOKEN_ENC_KEY=... MASTER_BASE_URL=https://master.example.com docker compose up -d
```
## Multi-tenant (Advanced)
In multi-tenant deployments, use separate configs per tenant and map them under different base URLs or headers. Keep secrets isolated and rotate regularly.
```yaml
# tenant-a.yaml
hosting: { port: 3001 }
servers: [ { id: search, type: local, auth_strategy: master_oauth, config: { port: 4110 } } ]
# tenant-b.yaml
hosting: { port: 3002 }
servers: [ { id: search, type: local, auth_strategy: master_oauth, config: { port: 4120 } } ]
```
```
--------------------------------------------------------------------------------
/tests/servers/test-auth-simple.js:
--------------------------------------------------------------------------------
```javascript
import { MultiAuthManager } from '../../src/auth/multi-auth-manager.js'
import { AuthStrategy } from '../../src/types/config.js'
import '../setup/test-setup.js'
const masterCfg = {
authorization_endpoint: 'http://localhost/auth',
token_endpoint: 'http://localhost/token',
client_id: 'master',
redirect_uri: 'http://localhost/cb',
scopes: ['openid'],
}
async function testAuth() {
console.log('Testing MultiAuthManager...')
try {
const mam = new MultiAuthManager(masterCfg)
mam.registerServerAuth('srv1', AuthStrategy.MASTER_OAUTH)
const h = await mam.prepareAuthForBackend('srv1', 'CLIENT')
if (h.Authorization === 'Bearer CLIENT') {
console.log('✅ Test 1 passed: Master OAuth pass-through')
} else {
console.log('❌ Test 1 failed:', h)
}
// Test delegation
mam.registerServerAuth('srv2', AuthStrategy.DELEGATE_OAUTH, {
provider: 'custom',
authorization_endpoint: 'http://p/auth',
token_endpoint: 'http://p/token',
client_id: 'c'
})
const d = await mam.prepareAuthForBackend('srv2', 'CLIENT')
if (d.type === 'oauth_delegation') {
console.log('✅ Test 2 passed: OAuth delegation')
} else {
console.log('❌ Test 2 failed:', d)
}
// Test storage
await mam.storeDelegatedToken('CLIENT', 'srv', { access_token: 'S', expires_at: Date.now() + 1000, scope: [] })
const tok = await mam.getStoredServerToken('srv', 'CLIENT')
if (tok === 'S') {
console.log('✅ Test 3 passed: Token storage')
} else {
console.log('❌ Test 3 failed:', tok)
}
console.log('All tests completed successfully!')
} catch (error) {
console.error('Test failed:', error)
console.error('Stack:', error.stack)
}
}
testAuth()
```
--------------------------------------------------------------------------------
/tests/e2e/flow-controller.express.test.ts:
--------------------------------------------------------------------------------
```typescript
import '../setup/test-setup.js'
import test from 'node:test'
import assert from 'node:assert/strict'
import { OAuthFlowController } from '../../src/oauth/flow-controller.js'
import { FakeExpressApp } from '../utils/fake-express.js'
import { createMockServer } from '../utils/mock-http.js'
test('OAuthFlowController Express flow: authorize -> token -> callback', async () => {
const tokenSrv = await createMockServer([
{ method: 'POST', path: '/token', handler: (_req, _body) => ({ body: { access_token: 'AT', expires_in: 60, scope: 'openid' } }) },
])
try {
const cfg = {
master_oauth: {
authorization_endpoint: tokenSrv.url + '/authorize',
token_endpoint: tokenSrv.url + '/token',
client_id: 'cid',
redirect_uri: 'http://localhost/oauth/callback',
scopes: ['openid'],
},
hosting: { platform: 'node', base_url: 'http://localhost' },
servers: [],
}
const ctrl = new OAuthFlowController({ getConfig: () => cfg as any })
const app = new FakeExpressApp()
ctrl.registerExpress(app as any)
const auth = await app.invoke('GET', '/oauth/authorize', { query: { provider: 'master' } })
assert.equal(auth.status, 200)
assert.match(String(auth.body), /Redirecting/)
const m = String(auth.body).match(/url=([^"\s]+)/)
assert.ok(m && m[1])
const urlStr = m[1].replace(/&/g, '&') // Decode HTML entities
const url = new URL(urlStr)
const state = url.searchParams.get('state')!
assert.ok(state)
const cb = await app.invoke('GET', '/oauth/callback', { query: { state, code: 'good', provider: 'master' } })
assert.equal(cb.status, 200)
assert.match(String(cb.body), /Authorization complete|You may close this window/)
} finally {
await tokenSrv.close()
}
})
```
--------------------------------------------------------------------------------
/deploy/docker/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
# syntax=docker/dockerfile:1.7
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}-slim AS base
ENV PNPM_HOME=/usr/local/share/pnpm \
NODE_ENV=production \
APP_HOME=/app
WORKDIR ${APP_HOME}
# ---------- Builder ----------
FROM base AS builder
ENV NODE_ENV=development
SHELL ["/bin/sh", "-lc"]
RUN --mount=type=cache,target=/var/cache/apt \
--mount=type=cache,target=/var/lib/apt/lists \
apt-get update && apt-get install -y --no-install-recommends git ca-certificates && rm -rf /var/lib/apt/lists/*
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY tsconfig*.json ./
COPY src ./src
COPY config ./config
COPY static ./static
RUN npm run build:node
# ---------- Runtime ----------
FROM base AS runtime
SHELL ["/bin/sh", "-lc"]
# Create non-root user
RUN useradd -r -u 10001 -g root nodejs && mkdir -p /app && chown -R nodejs:root /app
# Only production deps
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci --omit=dev --ignore-scripts && npm cache clean --force
# Copy build artifacts and minimal runtime assets
COPY --from=builder /app/dist/node ./dist/node
COPY --from=builder /app/config ./config
COPY --from=builder /app/static ./static
COPY deploy/docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENV NODE_ENV=production \
LOG_FORMAT=json \
PORT=3000 \
MASTER_HOSTING_PLATFORM=node
EXPOSE 3000
USER nodejs
ENTRYPOINT ["/entrypoint.sh"]
CMD ["node", "dist/node/index.js"]
# Healthcheck without adding curl/wget: use Node's http module
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s --retries=3 \
CMD node -e "const http=require('http');const p=process.env.PORT||3000;http.get({host:'127.0.0.1',port:p,path:'/health'},r=>{process.exit(r.statusCode===200?0:1)}).on('error',()=>process.exit(1))"
```
--------------------------------------------------------------------------------
/src/utils/dev.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Development and debugging helpers.
*/
export function isDev(): boolean {
const env = (globalThis as any)?.process?.env
return env?.NODE_ENV !== 'production'
}
export function debugLog(...args: unknown[]): void {
if (!isDev()) return
// eslint-disable-next-line no-console
console.debug('[DEV]', ...args)
}
export function invariant(condition: unknown, message = 'Invariant failed'): asserts condition {
if (!condition) throw new Error(message)
}
export function assertNever(x: never, message = 'Unexpected object'): never {
throw new Error(`${message}: ${String(x)}`)
}
export function pretty(value: unknown): string {
try {
const util = (globalThis as any).require ? (globalThis as any).require('node:util') : undefined
if (util?.inspect) return util.inspect(value, { depth: 4, colors: true })
} catch {
// ignore
}
try {
return JSON.stringify(value, null, 2)
} catch {
return String(value)
}
}
export function deprecate(fn: (...args: any[]) => any, message: string): (...args: any[]) => any {
let warned = false
return (...args: any[]) => {
if (!warned) {
warned = true
// eslint-disable-next-line no-console
console.warn(`[DEPRECATED] ${message}`)
}
return fn(...args)
}
}
export function withTiming<T>(name: string, fn: () => T): { result: T; durationMs: number; name: string } {
const start = typeof performance !== 'undefined' ? performance.now() : Date.now()
const result = fn()
const durationMs = (typeof performance !== 'undefined' ? performance.now() : Date.now()) - start
return { result, durationMs, name }
}
export function sleep(ms: number): Promise<void> {
return new Promise((res) => setTimeout(res, ms))
}
export function mockRandom(fn: () => number): () => void {
const original = Math.random
;(Math as any).random = fn
return () => {
;(Math as any).random = original
}
}
```
--------------------------------------------------------------------------------
/tests/servers/test-master-mcp.js:
--------------------------------------------------------------------------------
```javascript
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
async function runTest() {
try {
console.log('Testing Master MCP Server...')
// Create a streamable HTTP transport to connect to our MCP server
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3005/mcp'))
// Create the MCP client
const client = new Client({
name: 'master-mcp-test-client',
version: '1.0.0'
})
// Initialize the client
await client.connect(transport)
console.log('✅ Server initialized')
console.log('Server info:', client.getServerVersion())
console.log('Protocol version:', client.getServerCapabilities())
// List tools
console.log('\n--- Testing tools/list ---')
const toolsResult = await client.listTools({})
console.log('✅ tools/list successful')
console.log('Number of tools:', toolsResult.tools.length)
console.log('Tools:', toolsResult.tools.map(t => t.name))
// List resources
console.log('\n--- Testing resources/list ---')
const resourcesResult = await client.listResources({})
console.log('✅ resources/list successful')
console.log('Number of resources:', resourcesResult.resources.length)
console.log('Resources:', resourcesResult.resources.map(r => r.uri))
// Test ping
console.log('\n--- Testing ping ---')
const pingResult = await client.ping()
console.log('✅ ping successful')
console.log('Ping result:', pingResult)
// Close the connection
await client.close()
console.log('\n✅ Disconnected from MCP server')
console.log('\n🎉 All tests completed successfully!')
} catch (error) {
console.error('❌ Test failed:', error)
console.error('Error stack:', error.stack)
}
}
// Run the test
runTest()
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
# syntax=docker/dockerfile:1.7
# Multi-stage build for production and dev with multi-arch support.
# Targets:
# - base: common base image
# - deps: install all dependencies (including dev)
# - build: compile TypeScript to dist
# - prod-deps: install only production deps
# - runner: minimal runtime image
# - dev: development image with hot-reload support
ARG NODE_VERSION=20.14.0
ARG ALPINE_VERSION=3.19
FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS base
ENV NODE_ENV=production \
APP_HOME=/app \
PNPM_HOME=/pnpm
WORKDIR ${APP_HOME}
RUN addgroup -g 1001 -S nodejs && adduser -S node -u 1001 -G nodejs
FROM base AS deps
ENV NODE_ENV=development
COPY package*.json ./
# Prefer npm ci for reproducible installs
RUN --mount=type=cache,target=/root/.npm \
npm ci
FROM deps AS build
COPY tsconfig*.json ./
COPY src ./src
COPY config ./config
COPY static ./static
RUN npm run build
FROM deps AS prod-deps
ENV NODE_ENV=production
RUN --mount=type=cache,target=/root/.npm \
npm prune --omit=dev
FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS runner
ENV NODE_ENV=production \
APP_HOME=/app \
PORT=3000
WORKDIR ${APP_HOME}
# Busybox wget is enough for healthcheck; curl can be used if preferred
RUN apk add --no-cache wget
# Copy built app and production deps
COPY --from=prod-deps ${APP_HOME}/node_modules ./node_modules
COPY --from=build ${APP_HOME}/dist ./dist
COPY package*.json ./
COPY config ./config
COPY static ./static
# Use non-root user
USER 1001
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=20s --retries=3 \
CMD wget -qO- http://127.0.0.1:${PORT}/health || exit 1
# Default start command
CMD ["node", "dist/node/index.js"]
# Development image with hot reloading (nodemon)
FROM deps AS dev
ENV NODE_ENV=development \
PORT=3000
RUN npm pkg set scripts.dev:watch="nodemon --watch src --ext ts,tsx,json --exec 'node --loader ts-node/esm src/index.ts'" && \
npm i -D nodemon@^3
CMD ["npm", "run", "dev:watch"]
```
--------------------------------------------------------------------------------
/src/oauth/web-interface.ts:
--------------------------------------------------------------------------------
```typescript
export class WebInterface {
// Minimal, accessible pages for success and error. CSS served from /static/oauth/style.css
renderRedirectPage(providerName: string, redirectUrl: string): string {
const esc = (s: string) => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
return `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Continue to ${esc(providerName)}</title>
<link rel="stylesheet" href="/static/oauth/style.css" />
<meta http-equiv="refresh" content="0;url=${esc(redirectUrl)}" />
<script>location.replace(${JSON.stringify(redirectUrl)})</script>
</head>
<body>
<main class="container">
<h1>Redirecting…</h1>
<p>Taking you to ${esc(providerName)} to sign in.</p>
<p><a class="button" href="${esc(redirectUrl)}">Continue</a></p>
</main>
</body>
</html>`
}
renderSuccessPage(message = 'Authorization completed successfully.'): string {
const esc = (s: string) => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
return `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>OAuth Success</title>
<link rel="stylesheet" href="/static/oauth/style.css" />
</head>
<body>
<main class="container">
<h1>Success</h1>
<p>${esc(message)}</p>
</main>
</body>
</html>`
}
renderErrorPage(error = 'Authorization failed.'): string {
const esc = (s: string) => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
return `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>OAuth Error</title>
<link rel="stylesheet" href="/static/oauth/style.css" />
</head>
<body>
<main class="container error">
<h1>Authorization Error</h1>
<p>${esc(error)}</p>
</main>
</body>
</html>`
}
}
```
--------------------------------------------------------------------------------
/tests/servers/test-streaming.js:
--------------------------------------------------------------------------------
```javascript
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
async function runStreamingTest() {
try {
console.log('Testing Master MCP Server with HTTP Streaming...')
// Create a streamable HTTP transport to connect to our MCP server
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3005/mcp'))
// Create the MCP client
const client = new Client({
name: 'master-mcp-streaming-test-client',
version: '1.0.0'
})
// Initialize the client
await client.connect(transport)
console.log('✅ Server initialized with streaming transport')
console.log('Server info:', client.getServerVersion())
console.log('Server capabilities:', client.getServerCapabilities())
// List tools using streaming
console.log('\n--- Testing tools/list with streaming ---')
const toolsResult = await client.listTools({})
console.log('✅ tools/list successful with streaming')
console.log('Number of tools:', toolsResult.tools.length)
// List resources using streaming
console.log('\n--- Testing resources/list with streaming ---')
const resourcesResult = await client.listResources({})
console.log('✅ resources/list successful with streaming')
console.log('Number of resources:', resourcesResult.resources.length)
// Test ping
console.log('\n--- Testing ping with streaming ---')
const pingResult = await client.ping()
console.log('✅ ping successful with streaming')
console.log('Ping result:', pingResult)
// Close the connection
await client.close()
console.log('\n✅ Disconnected from MCP server')
console.log('\n🎉 All streaming tests completed successfully!')
} catch (error) {
console.error('❌ Streaming test failed:', error)
console.error('Error stack:', error.stack)
}
}
// Run the streaming test
runStreamingTest()
```
--------------------------------------------------------------------------------
/docs/guides/server-management.md:
--------------------------------------------------------------------------------
```markdown
# Server Management
The `MasterServer` orchestrates backend servers and exposes convenience APIs.
## Key APIs
- `startFromConfig(config, clientToken?)`: Load and health-check backends, discover capabilities
- `performHealthChecks(clientToken?)`: Returns `{ [serverId]: boolean }`
- `restartServer(id)`: Restarts a backend (when supported)
- `unloadAll()`: Stops and clears all backends
- `getRouter()`: Access to `RequestRouter`
- `getAggregatedTools()/getAggregatedResources()`: Current aggregated definitions
- `attachAuthManager(multiAuth)`: Injects a `MultiAuthManager`
- `getOAuthFlowController()`: Provides an OAuth controller to mount in your runtime
## Node Runtime
`src/index.ts` creates an Express app exposing health, metrics, OAuth endpoints, and MCP HTTP endpoints. Use `npm run dev` during development.
## Workers Runtime
`src/runtime/worker.ts` exports a `fetch` handler integrating the protocol and OAuth flows. Configure via `deploy/cloudflare/wrangler.toml`.
## Adding Backends by Source
> Note: Some origin types (git/npm/pypi/docker) are treated as config-driven endpoints in the current loader. Provide an explicit `url` or `port` for the running backend.
<CodeTabs :options="[
{ label: 'Local', value: 'local' },
{ label: 'Git', value: 'git' },
{ label: 'NPM', value: 'npm' },
{ label: 'Docker', value: 'docker' }
]">
<template #local>
```yaml
servers:
- id: search
type: local
auth_strategy: master_oauth
config: { port: 4100 }
```
</template>
<template #git>
```yaml
servers:
- id: tools-from-git
type: git
auth_strategy: bypass_auth
config:
url: http://git-tools.internal:4010
```
</template>
<template #npm>
```yaml
servers:
- id: npm-tools
type: npm
auth_strategy: proxy_oauth
config:
url: http://npm-tools:4020
```
</template>
<template #docker>
```yaml
servers:
- id: containerized
type: docker
auth_strategy: master_oauth
config:
url: http://containerized:4030
```
</template>
</CodeTabs>
```
--------------------------------------------------------------------------------
/src/oauth/state-manager.ts:
--------------------------------------------------------------------------------
```typescript
// State Manager for OAuth CSRF protection
// Generates random opaque state tokens and tracks associated payload with TTL
export interface OAuthStatePayload {
provider?: string
serverId?: string
clientToken?: string
returnTo?: string
issuedAt: number
}
export interface StateRecord {
payload: OAuthStatePayload
expiresAt: number
}
export interface StateManagerOptions {
ttlMs?: number
}
function getCrypto(): any {
const g: any = globalThis as any
if (g.crypto && g.crypto.subtle && g.crypto.getRandomValues) return g.crypto as any
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const nodeCrypto = require('node:crypto')
return nodeCrypto.webcrypto as any
} catch {
throw new Error('Secure crypto not available in this environment')
}
}
function randomId(bytes = 32): string {
const crypto = getCrypto()
const arr = new Uint8Array(bytes)
crypto.getRandomValues(arr)
let str = ''
for (let i = 0; i < arr.length; i++) str += arr[i].toString(16).padStart(2, '0')
return str
}
export class StateManager {
private readonly store = new Map<string, StateRecord>()
private readonly ttl: number
constructor(options?: StateManagerOptions) {
this.ttl = options?.ttlMs ?? 10 * 60_000
}
create(payload: Omit<OAuthStatePayload, 'issuedAt'>): string {
const state = randomId(32)
const now = Date.now()
this.store.set(state, { payload: { ...payload, issuedAt: now }, expiresAt: now + this.ttl })
return state
}
consume(state: string): OAuthStatePayload | null {
const rec = this.store.get(state)
if (!rec) return null
this.store.delete(state)
if (rec.expiresAt <= Date.now()) return null
return rec.payload
}
peek(state: string): OAuthStatePayload | null {
const rec = this.store.get(state)
if (!rec || rec.expiresAt <= Date.now()) return null
return rec.payload
}
cleanup(): void {
const now = Date.now()
for (const [k, v] of this.store) if (v.expiresAt <= now) this.store.delete(k)
}
}
```
--------------------------------------------------------------------------------
/tests/utils/mock-http.ts:
--------------------------------------------------------------------------------
```typescript
import http from 'node:http'
export type Handler = (req: http.IncomingMessage, body: any) => { status?: number; headers?: Record<string, string>; body?: any }
export interface Route {
method: string
path: string | RegExp
handler: Handler
}
export interface MockServer {
url: string
port: number
close: () => Promise<void>
}
export function createMockServer(routes: Route[], opts?: { port?: number }): Promise<MockServer> {
const server = http.createServer(async (req, res) => {
const url = new URL(req.url || '/', `http://${req.headers.host}`)
const chunks: Buffer[] = []
for await (const c of req) chunks.push(c as Buffer)
let body: any = Buffer.concat(chunks).toString('utf8')
const ct = (req.headers['content-type'] || '').toString()
try {
if (ct.includes('application/json') && body) body = JSON.parse(body)
else if (ct.includes('application/x-www-form-urlencoded') && body) body = Object.fromEntries(new URLSearchParams(body))
} catch {
// leave as raw string
}
const route = routes.find((r) => r.method.toUpperCase() === (req.method || '').toUpperCase() &&
(typeof r.path === 'string' ? r.path === url.pathname : r.path.test(url.pathname)))
const result = route ? route.handler(req, body) : { status: 404, body: { error: 'not found' } }
const status = result.status ?? 200
const headers = result.headers ?? { 'content-type': 'application/json' }
const payload = result.body ?? { ok: true }
res.statusCode = status
for (const [k, v] of Object.entries(headers)) res.setHeader(k, v)
res.end(typeof payload === 'string' || Buffer.isBuffer(payload) ? payload : JSON.stringify(payload))
})
return new Promise((resolve) => {
server.listen(opts?.port ?? 0, () => {
const addr = server.address()
const port = typeof addr === 'object' && addr ? addr.port : (opts?.port ?? 0)
resolve({
url: `http://localhost:${port}`,
port,
close: () => new Promise((r) => server.close(() => r())),
})
})
})
}
```
--------------------------------------------------------------------------------
/src/routing/load-balancer.ts:
--------------------------------------------------------------------------------
```typescript
export type LoadBalancingStrategy = 'round_robin' | 'weighted' | 'health'
export interface LoadBalancingInstance {
id: string
weight?: number
healthScore?: number // 0..100; higher is better
}
export interface LoadBalancerOptions {
strategy: LoadBalancingStrategy
}
export class LoadBalancer {
private readonly opts: Required<LoadBalancerOptions>
private rrIndex: Map<string, number> = new Map()
constructor(options?: Partial<LoadBalancerOptions>) {
this.opts = { strategy: options?.strategy ?? 'round_robin' }
}
select<T extends LoadBalancingInstance>(key: string, instances: T[]): T | undefined {
if (!instances.length) return undefined
switch (this.opts.strategy) {
case 'weighted':
return this.selectWeighted(instances)
case 'health':
return this.selectHealth(instances, key)
case 'round_robin':
default:
return this.selectRoundRobin(key, instances)
}
}
private selectRoundRobin<T extends LoadBalancingInstance>(key: string, instances: T[]): T {
const idx = this.rrIndex.get(key) ?? 0
const chosen = instances[idx % instances.length]
this.rrIndex.set(key, (idx + 1) % instances.length)
return chosen
}
private selectWeighted<T extends LoadBalancingInstance>(instances: T[]): T {
const weights = instances.map((i) => Math.max(1, Math.floor(i.weight ?? 1)))
const total = weights.reduce((a, b) => a + b, 0)
let r = Math.random() * total
for (let i = 0; i < instances.length; i++) {
if (r < weights[i]) return instances[i]
r -= weights[i]
}
return instances[0]
}
private selectHealth<T extends LoadBalancingInstance>(instances: T[], key: string): T {
// Choose highest health; tie-break with RR for stability
const sorted = [...instances].sort((a, b) => (b.healthScore ?? 0) - (a.healthScore ?? 0))
const topScore = sorted[0].healthScore ?? 0
const top = sorted.filter((i) => (i.healthScore ?? 0) === topScore)
if (top.length === 1) return top[0]
return this.selectRoundRobin(key + '::health', top)
}
}
```
--------------------------------------------------------------------------------
/tests/_utils/test-server.ts:
--------------------------------------------------------------------------------
```typescript
import http from 'node:http'
export interface RouteHandler {
(req: http.IncomingMessage, body: string | undefined): { status?: number; headers?: Record<string, string>; body?: any }
}
export interface TestServer {
url: string
port: number
close: () => Promise<void>
register: (method: string, path: string, handler: RouteHandler) => void
}
export function createTestServer(): Promise<TestServer> {
const routes = new Map<string, RouteHandler>()
const server = http.createServer(async (req, res) => {
try {
const url = new URL(req.url || '/', 'http://localhost')
const key = `${(req.method || 'GET').toUpperCase()} ${url.pathname}`
let body: string | undefined
if (req.method && ['POST', 'PUT', 'PATCH'].includes(req.method.toUpperCase())) {
body = await new Promise<string>((resolve) => {
const chunks: Buffer[] = []
req.on('data', (c) => chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(String(c))))
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')))
})
}
const handler = routes.get(key)
if (!handler) {
res.statusCode = 404
res.end('not found')
return
}
const result = handler(req, body)
const status = result.status ?? 200
const headers = { 'content-type': 'application/json', ...(result.headers ?? {}) }
const payload = typeof result.body === 'string' ? result.body : JSON.stringify(result.body ?? { ok: true })
res.writeHead(status, headers)
res.end(payload)
} catch (err: any) {
res.writeHead(500, { 'content-type': 'application/json' })
res.end(JSON.stringify({ error: err?.message ?? 'internal error' }))
}
})
return new Promise((resolve) => {
server.listen(0, '127.0.0.1', () => {
const addr = server.address()
const port = typeof addr === 'object' && addr ? addr.port : 0
resolve({
port,
url: `http://127.0.0.1:${port}`,
close: () => new Promise<void>((r) => server.close(() => r())),
register: (method: string, path: string, handler: RouteHandler) => {
routes.set(`${method.toUpperCase()} ${path}`, handler)
},
})
})
})
}
```
--------------------------------------------------------------------------------
/src/server/protocol-handler.ts:
--------------------------------------------------------------------------------
```typescript
import type {
CallToolRequest,
CallToolResult,
ListResourcesRequest,
ListResourcesResult,
ListToolsRequest,
ListToolsResult,
ReadResourceRequest,
ReadResourceResult,
SubscribeRequest,
SubscribeResult,
} from '../types/mcp.js'
import type { CapabilityAggregator } from '../modules/capability-aggregator.js'
import type { RequestRouter } from '../modules/request-router.js'
import { Logger } from '../utils/logger.js'
export interface ProtocolContext {
aggregator: CapabilityAggregator
router: RequestRouter
// Optional client bearer token provided by gateway
getClientToken?: () => string | undefined
}
export class ProtocolHandler {
constructor(private readonly ctx: ProtocolContext) {}
async handleListTools(_req: ListToolsRequest): Promise<ListToolsResult> {
try {
const tools = this.ctx.aggregator.getAllTools(this.ctx.router.getServers())
return { tools }
} catch (err) {
Logger.error('handleListTools failed', err)
return { tools: [] }
}
}
async handleCallTool(req: CallToolRequest): Promise<CallToolResult> {
try {
const token = this.ctx.getClientToken?.()
const res = await this.ctx.router.routeCallTool(req, token)
return res
} catch (err) {
Logger.warn('handleCallTool error', err)
return { content: { error: String(err) }, isError: true }
}
}
async handleListResources(_req: ListResourcesRequest): Promise<ListResourcesResult> {
try {
const resources = this.ctx.aggregator.getAllResources(this.ctx.router.getServers())
return { resources }
} catch (err) {
Logger.error('handleListResources failed', err)
return { resources: [] }
}
}
async handleReadResource(req: ReadResourceRequest): Promise<ReadResourceResult> {
try {
const token = this.ctx.getClientToken?.()
const res = await this.ctx.router.routeReadResource(req, token)
return res
} catch (err) {
Logger.warn('handleReadResource error', err)
return { contents: String(err), mimeType: 'text/plain' }
}
}
async handleSubscribe(_req: SubscribeRequest): Promise<SubscribeResult> {
// Event subscriptions not yet surfaced; return OK for MCP compatibility
return { ok: true }
}
}
```