This is page 1 of 10. Use http://codebase.md/jakedismo/master-mcp-server?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .env.example
├── .eslintignore
├── .eslintrc.cjs
├── .eslintrc.js
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .prettierrc.json
├── CHANGELOG.md
├── config
│ ├── default.json
│ ├── development.json
│ ├── production.json
│ └── schema.json
├── config.json
├── CONTRIBUTING.md
├── debug-stdio.cjs
├── debug-stdio.js
├── deploy
│ ├── cloudflare
│ │ ├── .gitkeep
│ │ ├── README.md
│ │ └── wrangler.toml
│ ├── docker
│ │ ├── .gitkeep
│ │ ├── docker-compose.yml
│ │ ├── Dockerfile
│ │ └── entrypoint.sh
│ ├── koyeb
│ │ ├── .gitkeep
│ │ └── koyeb.yaml
│ └── README.md
├── docker-compose.yml
├── Dockerfile
├── docs
│ ├── .DS_Store
│ ├── .vitepress
│ │ ├── cache
│ │ │ └── deps
│ │ │ ├── _metadata.json
│ │ │ ├── chunk-HVR2FF6M.js
│ │ │ ├── chunk-HVR2FF6M.js.map
│ │ │ ├── chunk-P2XGSYO7.js
│ │ │ ├── chunk-P2XGSYO7.js.map
│ │ │ ├── package.json
│ │ │ ├── vitepress___@vue_devtools-api.js
│ │ │ ├── vitepress___@vue_devtools-api.js.map
│ │ │ ├── vitepress___@vueuse_core.js
│ │ │ ├── vitepress___@vueuse_core.js.map
│ │ │ ├── vitepress___@vueuse_integrations_useFocusTrap.js
│ │ │ ├── vitepress___@vueuse_integrations_useFocusTrap.js.map
│ │ │ ├── vitepress___mark__js_src_vanilla__js.js
│ │ │ ├── vitepress___mark__js_src_vanilla__js.js.map
│ │ │ ├── vitepress___minisearch.js
│ │ │ ├── vitepress___minisearch.js.map
│ │ │ ├── vue.js
│ │ │ └── vue.js.map
│ │ ├── config.ts
│ │ ├── dist
│ │ │ ├── 404.html
│ │ │ ├── advanced
│ │ │ │ ├── extensibility.html
│ │ │ │ ├── index.html
│ │ │ │ ├── monitoring.html
│ │ │ │ ├── performance.html
│ │ │ │ └── security.html
│ │ │ ├── api
│ │ │ │ ├── index.html
│ │ │ │ └── README.html
│ │ │ ├── assets
│ │ │ │ ├── advanced_extensibility.md.TrXUn5w5.js
│ │ │ │ ├── advanced_extensibility.md.TrXUn5w5.lean.js
│ │ │ │ ├── advanced_index.md.CPcpUlw_.js
│ │ │ │ ├── advanced_index.md.CPcpUlw_.lean.js
│ │ │ │ ├── advanced_monitoring.md.DTybdNg-.js
│ │ │ │ ├── advanced_monitoring.md.DTybdNg-.lean.js
│ │ │ │ ├── advanced_performance.md.DKmzK0ia.js
│ │ │ │ ├── advanced_performance.md.DKmzK0ia.lean.js
│ │ │ │ ├── advanced_security.md.B-oBD7IB.js
│ │ │ │ ├── advanced_security.md.B-oBD7IB.lean.js
│ │ │ │ ├── api_index.md.Dl1JB08_.js
│ │ │ │ ├── api_index.md.Dl1JB08_.lean.js
│ │ │ │ ├── chunks
│ │ │ │ │ └── framework.CHl2ywxc.js
│ │ │ │ ├── configuration_environment-variables.md.Ddy3P_Wz.js
│ │ │ │ ├── configuration_environment-variables.md.Ddy3P_Wz.lean.js
│ │ │ │ ├── configuration_environment.md.DxcTQ623.js
│ │ │ │ ├── configuration_environment.md.DxcTQ623.lean.js
│ │ │ │ ├── configuration_overview.md.DIkVDv7V.js
│ │ │ │ ├── configuration_overview.md.DIkVDv7V.lean.js
│ │ │ │ ├── configuration_performance.md.DbJdmLrW.js
│ │ │ │ ├── configuration_performance.md.DbJdmLrW.lean.js
│ │ │ │ ├── configuration_reference.md.27IKWqtk.js
│ │ │ │ ├── configuration_reference.md.27IKWqtk.lean.js
│ │ │ │ ├── configuration_security.md.-OOlkzN4.js
│ │ │ │ ├── configuration_security.md.-OOlkzN4.lean.js
│ │ │ │ ├── contributing_dev-setup.md.Ceqh4w-R.js
│ │ │ │ ├── contributing_dev-setup.md.Ceqh4w-R.lean.js
│ │ │ │ ├── contributing_guidelines.md.ZEAX2yVh.js
│ │ │ │ ├── contributing_guidelines.md.ZEAX2yVh.lean.js
│ │ │ │ ├── contributing_index.md.DYq9R6wr.js
│ │ │ │ ├── contributing_index.md.DYq9R6wr.lean.js
│ │ │ │ ├── contributing_maintenance.md.k2bR0IaR.js
│ │ │ │ ├── contributing_maintenance.md.k2bR0IaR.lean.js
│ │ │ │ ├── deployment_cicd.md.Ci2T0UYC.js
│ │ │ │ ├── deployment_cicd.md.Ci2T0UYC.lean.js
│ │ │ │ ├── deployment_cloudflare-workers.md.D2WHsfep.js
│ │ │ │ ├── deployment_cloudflare-workers.md.D2WHsfep.lean.js
│ │ │ │ ├── deployment_docker.md.B8bQDQTo.js
│ │ │ │ ├── deployment_docker.md.B8bQDQTo.lean.js
│ │ │ │ ├── deployment_index.md.ClYeOkpy.js
│ │ │ │ ├── deployment_index.md.ClYeOkpy.lean.js
│ │ │ │ ├── deployment_koyeb.md.B_wJhvF7.js
│ │ │ │ ├── deployment_koyeb.md.B_wJhvF7.lean.js
│ │ │ │ ├── examples_advanced-routing.md.B3CqhLZ7.js
│ │ │ │ ├── examples_advanced-routing.md.B3CqhLZ7.lean.js
│ │ │ │ ├── examples_basic-node.md.CaDZzGlO.js
│ │ │ │ ├── examples_basic-node.md.CaDZzGlO.lean.js
│ │ │ │ ├── examples_cloudflare-worker.md.DwVSz-c7.js
│ │ │ │ ├── examples_cloudflare-worker.md.DwVSz-c7.lean.js
│ │ │ │ ├── examples_index.md.CBF_BLkl.js
│ │ │ │ ├── examples_index.md.CBF_BLkl.lean.js
│ │ │ │ ├── examples_oauth-delegation.md.1hZxoqDl.js
│ │ │ │ ├── examples_oauth-delegation.md.1hZxoqDl.lean.js
│ │ │ │ ├── examples_overview.md.CZN0JbZ7.js
│ │ │ │ ├── examples_overview.md.CZN0JbZ7.lean.js
│ │ │ │ ├── examples_testing.md.Dek4GpNs.js
│ │ │ │ ├── examples_testing.md.Dek4GpNs.lean.js
│ │ │ │ ├── getting-started_concepts.md.D7ON9iGB.js
│ │ │ │ ├── getting-started_concepts.md.D7ON9iGB.lean.js
│ │ │ │ ├── getting-started_installation.md.BKnVqAGg.js
│ │ │ │ ├── getting-started_installation.md.BKnVqAGg.lean.js
│ │ │ │ ├── getting-started_overview.md.DvJDFL2N.js
│ │ │ │ ├── getting-started_overview.md.DvJDFL2N.lean.js
│ │ │ │ ├── getting-started_quickstart-node.md.GOO4aGas.js
│ │ │ │ ├── getting-started_quickstart-node.md.GOO4aGas.lean.js
│ │ │ │ ├── getting-started_quickstart-workers.md.Cpofh8Mj.js
│ │ │ │ ├── getting-started_quickstart-workers.md.Cpofh8Mj.lean.js
│ │ │ │ ├── getting-started.md.DG9ndneo.js
│ │ │ │ ├── getting-started.md.DG9ndneo.lean.js
│ │ │ │ ├── guides_configuration-management.md.B-jwYMbA.js
│ │ │ │ ├── guides_configuration-management.md.B-jwYMbA.lean.js
│ │ │ │ ├── guides_configuration.md.Ci3zYDFA.js
│ │ │ │ ├── guides_configuration.md.Ci3zYDFA.lean.js
│ │ │ │ ├── guides_index.md.CIlq2fmx.js
│ │ │ │ ├── guides_index.md.CIlq2fmx.lean.js
│ │ │ │ ├── guides_module-loading.md.BkJvuRnQ.js
│ │ │ │ ├── guides_module-loading.md.BkJvuRnQ.lean.js
│ │ │ │ ├── guides_oauth-delegation.md.DEOZ-_G0.js
│ │ │ │ ├── guides_oauth-delegation.md.DEOZ-_G0.lean.js
│ │ │ │ ├── guides_request-routing.md.Bdzf0VLg.js
│ │ │ │ ├── guides_request-routing.md.Bdzf0VLg.lean.js
│ │ │ │ ├── guides_testing.md.kYfHqJLu.js
│ │ │ │ ├── guides_testing.md.kYfHqJLu.lean.js
│ │ │ │ ├── inter-italic-cyrillic-ext.r48I6akx.woff2
│ │ │ │ ├── inter-italic-cyrillic.By2_1cv3.woff2
│ │ │ │ ├── inter-italic-greek-ext.1u6EdAuj.woff2
│ │ │ │ ├── inter-italic-greek.DJ8dCoTZ.woff2
│ │ │ │ ├── inter-italic-latin-ext.CN1xVJS-.woff2
│ │ │ │ ├── inter-italic-latin.C2AdPX0b.woff2
│ │ │ │ ├── inter-italic-vietnamese.BSbpV94h.woff2
│ │ │ │ ├── inter-roman-cyrillic-ext.BBPuwvHQ.woff2
│ │ │ │ ├── inter-roman-cyrillic.C5lxZ8CY.woff2
│ │ │ │ ├── inter-roman-greek-ext.CqjqNYQ-.woff2
│ │ │ │ ├── inter-roman-greek.BBVDIX6e.woff2
│ │ │ │ ├── inter-roman-latin-ext.4ZJIpNVo.woff2
│ │ │ │ ├── inter-roman-latin.Di8DUHzh.woff2
│ │ │ │ ├── inter-roman-vietnamese.BjW4sHH5.woff2
│ │ │ │ ├── README.md.BO5r5M9u.js
│ │ │ │ ├── README.md.BO5r5M9u.lean.js
│ │ │ │ ├── style.BQrfSMzK.css
│ │ │ │ ├── troubleshooting_common-issues.md.CScvzWM1.js
│ │ │ │ ├── troubleshooting_common-issues.md.CScvzWM1.lean.js
│ │ │ │ ├── troubleshooting_deployment.md.DUhpqnLE.js
│ │ │ │ ├── troubleshooting_deployment.md.DUhpqnLE.lean.js
│ │ │ │ ├── troubleshooting_errors.md.BSCsEmGc.js
│ │ │ │ ├── troubleshooting_errors.md.BSCsEmGc.lean.js
│ │ │ │ ├── troubleshooting_oauth.md.Cw60Eka3.js
│ │ │ │ ├── troubleshooting_oauth.md.Cw60Eka3.lean.js
│ │ │ │ ├── troubleshooting_performance.md.DxY6LJcT.js
│ │ │ │ ├── troubleshooting_performance.md.DxY6LJcT.lean.js
│ │ │ │ ├── troubleshooting_routing.md.BHN-MDhs.js
│ │ │ │ ├── troubleshooting_routing.md.BHN-MDhs.lean.js
│ │ │ │ ├── troubleshooting_security-best-practices.md.Yiu8E-zt.js
│ │ │ │ ├── troubleshooting_security-best-practices.md.Yiu8E-zt.lean.js
│ │ │ │ ├── tutorials_beginner-getting-started.md.BXObgobW.js
│ │ │ │ ├── tutorials_beginner-getting-started.md.BXObgobW.lean.js
│ │ │ │ ├── tutorials_cloudflare-workers-tutorial.md.MPHsc0aT.js
│ │ │ │ ├── tutorials_cloudflare-workers-tutorial.md.MPHsc0aT.lean.js
│ │ │ │ ├── tutorials_load-balancing-and-resilience.md.Dv9r9jyW.js
│ │ │ │ ├── tutorials_load-balancing-and-resilience.md.Dv9r9jyW.lean.js
│ │ │ │ ├── tutorials_oauth-delegation-github.md.Nq4glqCe.js
│ │ │ │ └── tutorials_oauth-delegation-github.md.Nq4glqCe.lean.js
│ │ │ ├── configuration
│ │ │ │ ├── environment-variables.html
│ │ │ │ ├── environment.html
│ │ │ │ ├── examples.html
│ │ │ │ ├── overview.html
│ │ │ │ ├── performance.html
│ │ │ │ ├── reference.html
│ │ │ │ └── security.html
│ │ │ ├── contributing
│ │ │ │ ├── dev-setup.html
│ │ │ │ ├── guidelines.html
│ │ │ │ ├── index.html
│ │ │ │ └── maintenance.html
│ │ │ ├── deployment
│ │ │ │ ├── cicd.html
│ │ │ │ ├── cloudflare-workers.html
│ │ │ │ ├── docker.html
│ │ │ │ ├── index.html
│ │ │ │ └── koyeb.html
│ │ │ ├── diagrams
│ │ │ │ └── architecture.svg
│ │ │ ├── examples
│ │ │ │ ├── advanced-routing.html
│ │ │ │ ├── basic-node.html
│ │ │ │ ├── cloudflare-worker.html
│ │ │ │ ├── index.html
│ │ │ │ ├── oauth-delegation.html
│ │ │ │ ├── overview.html
│ │ │ │ └── testing.html
│ │ │ ├── getting-started
│ │ │ │ ├── concepts.html
│ │ │ │ ├── installation.html
│ │ │ │ ├── overview.html
│ │ │ │ ├── quick-start.html
│ │ │ │ ├── quickstart-node.html
│ │ │ │ └── quickstart-workers.html
│ │ │ ├── getting-started.html
│ │ │ ├── guides
│ │ │ │ ├── authentication.html
│ │ │ │ ├── client-integration.html
│ │ │ │ ├── configuration-management.html
│ │ │ │ ├── configuration.html
│ │ │ │ ├── index.html
│ │ │ │ ├── module-loading.html
│ │ │ │ ├── oauth-delegation.html
│ │ │ │ ├── request-routing.html
│ │ │ │ ├── server-management.html
│ │ │ │ ├── server-sharing.html
│ │ │ │ └── testing.html
│ │ │ ├── hashmap.json
│ │ │ ├── index.html
│ │ │ ├── logo.svg
│ │ │ ├── README.html
│ │ │ ├── reports
│ │ │ │ └── mcp-compliance-audit.html
│ │ │ ├── troubleshooting
│ │ │ │ ├── common-issues.html
│ │ │ │ ├── deployment.html
│ │ │ │ ├── errors.html
│ │ │ │ ├── index.html
│ │ │ │ ├── oauth.html
│ │ │ │ ├── performance.html
│ │ │ │ ├── routing.html
│ │ │ │ └── security-best-practices.html
│ │ │ ├── tutorials
│ │ │ │ ├── beginner-getting-started.html
│ │ │ │ ├── cloudflare-workers-tutorial.html
│ │ │ │ ├── load-balancing-and-resilience.html
│ │ │ │ └── oauth-delegation-github.html
│ │ │ └── vp-icons.css
│ │ └── theme
│ │ ├── components
│ │ │ ├── ApiPlayground.vue
│ │ │ ├── AuthFlowDemo.vue
│ │ │ ├── CodeTabs.vue
│ │ │ └── ConfigGenerator.vue
│ │ ├── index.ts
│ │ └── style.css
│ ├── advanced
│ │ ├── extensibility.md
│ │ ├── index.md
│ │ ├── monitoring.md
│ │ ├── performance.md
│ │ └── security.md
│ ├── api
│ │ ├── functions
│ │ │ └── createServer.md
│ │ ├── index.md
│ │ ├── interfaces
│ │ │ └── RunningServer.md
│ │ └── README.md
│ ├── architecture
│ │ └── images
│ │ └── mcp_master_architecture.svg
│ ├── configuration
│ │ ├── environment-variables.md
│ │ ├── environment.md
│ │ ├── examples.md
│ │ ├── overview.md
│ │ ├── performance.md
│ │ ├── reference.md
│ │ └── security.md
│ ├── contributing
│ │ ├── dev-setup.md
│ │ ├── guidelines.md
│ │ ├── index.md
│ │ └── maintenance.md
│ ├── deployment
│ │ ├── cicd.md
│ │ ├── cloudflare-workers.md
│ │ ├── docker.md
│ │ ├── docs-site.md
│ │ ├── index.md
│ │ └── koyeb.md
│ ├── examples
│ │ ├── advanced-routing.md
│ │ ├── basic-node.md
│ │ ├── cloudflare-worker.md
│ │ ├── index.md
│ │ ├── oauth-delegation.md
│ │ ├── overview.md
│ │ └── testing.md
│ ├── getting-started
│ │ ├── concepts.md
│ │ ├── installation.md
│ │ ├── overview.md
│ │ ├── quick-start.md
│ │ ├── quickstart-node.md
│ │ └── quickstart-workers.md
│ ├── getting-started.md
│ ├── guides
│ │ ├── authentication.md
│ │ ├── client-integration.md
│ │ ├── configuration-management.md
│ │ ├── configuration.md
│ │ ├── index.md
│ │ ├── module-loading.md
│ │ ├── oauth-delegation.md
│ │ ├── request-routing.md
│ │ ├── server-management.md
│ │ ├── server-sharing.md
│ │ └── testing.md
│ ├── index.html
│ ├── public
│ │ ├── diagrams
│ │ │ └── architecture.svg
│ │ ├── github-social.png
│ │ │ └── image.png
│ │ ├── logo.png
│ │ └── logo.svg
│ ├── README.md
│ ├── stdio-servers.md
│ ├── testing
│ │ └── phase-9-testing-architecture.md
│ ├── troubleshooting
│ │ ├── common-issues.md
│ │ ├── deployment.md
│ │ ├── errors.md
│ │ ├── index.md
│ │ ├── oauth.md
│ │ ├── performance.md
│ │ ├── routing.md
│ │ └── security-best-practices.md
│ └── tutorials
│ ├── beginner-getting-started.md
│ ├── cloudflare-workers-tutorial.md
│ ├── load-balancing-and-resilience.md
│ └── oauth-delegation-github.md
├── examples
│ ├── advanced-routing
│ │ ├── config.yaml
│ │ └── README.md
│ ├── basic-node
│ │ ├── config.yaml
│ │ ├── README.md
│ │ └── server.ts
│ ├── cloudflare-worker
│ │ ├── README.md
│ │ └── worker.ts
│ ├── custom-auth
│ │ ├── config.yaml
│ │ ├── index.ts
│ │ └── README.md
│ ├── multi-server
│ │ ├── config.yaml
│ │ └── README.md
│ ├── oauth-delegation
│ │ └── README.md
│ ├── oauth-node
│ │ ├── config.yaml
│ │ └── README.md
│ ├── performance
│ │ ├── config.yaml
│ │ └── README.md
│ ├── sample-configs
│ │ ├── basic.yaml
│ │ └── simple-setup.yaml
│ ├── security-hardening
│ │ └── README.md
│ ├── stdio-mcp-server.cjs
│ ├── test-mcp-server.js
│ └── test-stdio-server.js
├── LICENSE
├── master-mcp-definition.md
├── package-lock.json
├── package.json
├── README.md
├── reports
│ └── claude_report_20250815_222153.html
├── scripts
│ └── generate-config-docs.ts
├── src
│ ├── auth
│ │ ├── multi-auth-manager.ts
│ │ ├── oauth-providers.ts
│ │ └── token-manager.ts
│ ├── config
│ │ ├── config-loader.ts
│ │ ├── environment-manager.ts
│ │ ├── schema-validator.ts
│ │ └── secret-manager.ts
│ ├── index.ts
│ ├── mcp-server.ts
│ ├── modules
│ │ ├── capability-aggregator.ts
│ │ ├── module-loader.ts
│ │ ├── request-router.ts
│ │ ├── stdio-capability-discovery.ts
│ │ └── stdio-manager.ts
│ ├── oauth
│ │ ├── callback-handler.ts
│ │ ├── flow-controller.ts
│ │ ├── flow-validator.ts
│ │ ├── pkce-manager.ts
│ │ ├── state-manager.ts
│ │ └── web-interface.ts
│ ├── routing
│ │ ├── circuit-breaker.ts
│ │ ├── load-balancer.ts
│ │ ├── retry-handler.ts
│ │ └── route-registry.ts
│ ├── runtime
│ │ ├── node.ts
│ │ └── worker.ts
│ ├── server
│ │ ├── config-manager.ts
│ │ ├── dependency-container.ts
│ │ ├── master-server.ts
│ │ └── protocol-handler.ts
│ ├── types
│ │ ├── auth.ts
│ │ ├── config.ts
│ │ ├── jose-shim.d.ts
│ │ ├── mcp.ts
│ │ └── server.ts
│ └── utils
│ ├── cache.ts
│ ├── crypto.ts
│ ├── dev.ts
│ ├── errors.ts
│ ├── http.ts
│ ├── logger.ts
│ ├── monitoring.ts
│ ├── string.ts
│ ├── time.ts
│ ├── validation.ts
│ └── validators.ts
├── static
│ └── oauth
│ ├── consent.html
│ ├── error.html
│ ├── script.js
│ ├── style.css
│ └── success.html
├── tests
│ ├── _setup
│ │ ├── miniflare.setup.ts
│ │ └── vitest.setup.ts
│ ├── _utils
│ │ ├── log-capture.ts
│ │ ├── mock-fetch.ts
│ │ └── test-server.ts
│ ├── .gitkeep
│ ├── e2e
│ │ ├── flow-controller.express.test.ts
│ │ └── flow-controller.worker.test.ts
│ ├── factories
│ │ ├── configFactory.ts
│ │ ├── mcpFactory.ts
│ │ └── oauthFactory.ts
│ ├── fixtures
│ │ ├── capabilities.json
│ │ └── stdio-server.js
│ ├── integration
│ │ ├── modules.capability-aggregator.test.ts
│ │ ├── modules.module-loader-health.test.ts
│ │ ├── oauth.callback-handler.test.ts
│ │ └── request-router.test.ts
│ ├── mocks
│ │ ├── mcp
│ │ │ └── fake-backend.ts
│ │ └── oauth
│ │ └── mock-oidc-provider.ts
│ ├── perf
│ │ ├── artillery
│ │ │ └── auth-routing.yaml
│ │ └── perf.auth-and-routing.test.ts
│ ├── security
│ │ └── security.oauth-and-input.test.ts
│ ├── servers
│ │ ├── test-auth-simple.js
│ │ ├── test-debug.js
│ │ ├── test-master-mcp.js
│ │ ├── test-mcp-client.js
│ │ ├── test-streaming-both-complete.js
│ │ ├── test-streaming-both-full.js
│ │ ├── test-streaming-both-simple.js
│ │ ├── test-streaming-both.js
│ │ └── test-streaming.js
│ ├── setup
│ │ └── test-setup.ts
│ ├── unit
│ │ ├── auth.multi-auth-manager.test.ts
│ │ ├── auth.token-manager.test.ts
│ │ ├── config.environment-manager.test.ts
│ │ ├── config.schema-validator.test.ts
│ │ ├── config.secret-manager.test.ts
│ │ ├── modules
│ │ │ ├── stdio-capability-discovery.test.ts
│ │ │ └── stdio-manager.test.ts
│ │ ├── modules.route-registry.test.ts
│ │ ├── oauth.pkce-state.test.ts
│ │ ├── routing
│ │ │ └── circuit-breaker.test.ts
│ │ ├── routing.core.test.ts
│ │ ├── stdio-capability-discovery.test.ts
│ │ ├── utils.crypto.test.ts
│ │ ├── utils.logger.test.ts
│ │ └── utils.monitoring.test.ts
│ └── utils
│ ├── fake-express.ts
│ ├── mock-http.ts
│ ├── oauth-mocks.ts
│ └── token-storages.ts
├── tsconfig.base.json
├── tsconfig.json
├── tsconfig.node.json
├── tsconfig.worker.json
├── typedoc.json
├── vitest.config.ts
└── vitest.worker.config.ts
```
# Files
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
```javascript
1 |
```
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
```
1 |
```
--------------------------------------------------------------------------------
/deploy/cloudflare/.gitkeep:
--------------------------------------------------------------------------------
```
1 |
2 |
```
--------------------------------------------------------------------------------
/deploy/docker/.gitkeep:
--------------------------------------------------------------------------------
```
1 |
2 |
```
--------------------------------------------------------------------------------
/deploy/koyeb/.gitkeep:
--------------------------------------------------------------------------------
```
1 |
2 |
```
--------------------------------------------------------------------------------
/tests/.gitkeep:
--------------------------------------------------------------------------------
```
1 |
2 |
```
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
```
1 | dist
2 | node_modules
3 | coverage
4 |
5 |
```
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
```
1 | dist
2 | node_modules
3 | coverage
4 |
5 |
```
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "singleQuote": true,
3 | "semi": false,
4 | "trailingComma": "es5",
5 | "printWidth": 100
6 | }
7 |
8 |
```
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
```
1 | # Runtime
2 | NODE_ENV=development
3 | LOG_LEVEL=info
4 | LOG_FORMAT=json
5 |
6 | # Hosting
7 | # If running behind a PaaS that sets PORT, the config loader will detect it.
8 | PORT=3000
9 | MASTER_HOSTING_PLATFORM=node
10 | MASTER_HOSTING_PORT=3000
11 | MASTER_BASE_URL=http://localhost:3000
12 |
13 | # Security
14 | # REQUIRED in production; if missing in dev, an ephemeral key is generated
15 | TOKEN_ENC_KEY=replace-with-32+char-hex-or-random
16 |
17 | # OAuth defaults (example values; override per provider)
18 | MASTER_OAUTH_AUTHORIZATION_ENDPOINT=https://example.com/auth
19 | MASTER_OAUTH_TOKEN_ENDPOINT=https://example.com/token
20 | MASTER_OAUTH_CLIENT_ID=placeholder
21 | MASTER_OAUTH_REDIRECT_URI=http://localhost:3000/oauth/callback
22 | MASTER_OAUTH_SCOPES=openid
23 |
24 |
```
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
```
1 | /* eslint-env node */
2 | module.exports = {
3 | root: true,
4 | parser: '@typescript-eslint/parser',
5 | parserOptions: {
6 | project: ['./tsconfig.node.json'],
7 | tsconfigRootDir: __dirname,
8 | sourceType: 'module',
9 | ecmaVersion: 2022
10 | },
11 | env: {
12 | node: true,
13 | es2022: true
14 | },
15 | plugins: ['@typescript-eslint'],
16 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
17 | rules: {
18 | 'no-console': 'off',
19 | '@typescript-eslint/no-explicit-any': 'off',
20 | '@typescript-eslint/no-unused-vars': 'off'
21 | },
22 | overrides: [
23 | {
24 | files: ['**/*.ts', '**/*.tsx'],
25 | parser: '@typescript-eslint/parser',
26 | parserOptions: {
27 | project: ['./tsconfig.node.json'],
28 | tsconfigRootDir: __dirname,
29 | sourceType: 'module',
30 | ecmaVersion: 2022,
31 | },
32 | },
33 | {
34 | files: ['src/runtime/worker.ts'],
35 | parserOptions: {
36 | project: ['./tsconfig.worker.json'],
37 | },
38 | },
39 | ],
40 | ignorePatterns: ['dist/**', 'node_modules/**']
41 | }
42 |
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | # Master MCP Server - Comprehensive .gitignore
2 |
3 | # Node.js & npm
4 | node_modules/
5 | npm-debug.log*
6 | yarn-debug.log*
7 | yarn-error.log*
8 | lerna-debug.log*
9 | .pnpm-debug.log*
10 |
11 | # Package manager lock files (keep package-lock.json for reproducible builds)
12 | yarn.lock
13 | pnpm-lock.yaml
14 |
15 | # Runtime data
16 | pids/
17 | *.pid
18 | *.seed
19 | *.pid.lock
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage/
23 | *.lcov
24 | .nyc_output/
25 | .coverage/
26 |
27 | # Compiled output
28 | dist/
29 | build/
30 | lib/
31 | out/
32 | *.tsbuildinfo
33 |
34 | # TypeScript cache
35 | *.tsbuildinfo
36 |
37 | # ESLint cache
38 | .eslintcache
39 |
40 | # Prettier cache
41 | .prettiercache
42 |
43 | # Optional npm cache directory
44 | .npm
45 |
46 | # Optional eslint cache
47 | .eslintcache
48 |
49 | # Microbundle cache
50 | .rpt2_cache/
51 | .rts2_cache_cjs/
52 | .rts2_cache_es/
53 | .rts2_cache_umd/
54 |
55 | # Optional REPL history
56 | .node_repl_history
57 |
58 | # Output of 'npm pack'
59 | *.tgz
60 |
61 | # Yarn Integrity file
62 | .yarn-integrity
63 |
64 | # Environment variables
65 | .env
66 | .env.local
67 | .env.development.local
68 | .env.test.local
69 | .env.production.local
70 | .env.*.local
71 | .env.production
72 |
73 | # Secrets and sensitive configuration
74 | config/secrets.json
75 | config/production-secrets.json
76 | .secrets/
77 | secrets/
78 |
79 | # IDE and Editor files
80 | .vscode/
81 | .idea/
82 | *.swp
83 | *.swo
84 | *~
85 | .DS_Store
86 | Thumbs.db
87 |
88 | # OS generated files
89 | .DS_Store
90 | .DS_Store?
91 | ._*
92 | .Spotlight-V100
93 | .Trashes
94 | ehthumbs.db
95 | Thumbs.db
96 |
97 | # Logs
98 | logs/
99 | *.log
100 | npm-debug.log*
101 | yarn-debug.log*
102 | yarn-error.log*
103 | lerna-debug.log*
104 |
105 | # Runtime and process files
106 | .pm2/
107 | .pid
108 |
109 | # Database
110 | *.db
111 | *.sqlite
112 | *.sqlite3
113 |
114 | # Cache directories
115 | .cache/
116 | .parcel-cache/
117 | .next/
118 | .nuxt/
119 | .vuepress/dist
120 |
121 | # Temporary directories
122 | tmp/
123 | temp/
124 | .tmp/
125 | .temp/
126 |
127 | # Test artifacts
128 | test-results/
129 | junit.xml
130 | coverage-final.json
131 |
132 | # Documentation build artifacts
133 | docs/.vitepress/dist/
134 | docs/.vitepress/cache/
135 | .typedoc/
136 |
137 | # Deployment artifacts
138 | deploy/secrets/
139 | deploy/keys/
140 | deploy/*.key
141 | deploy/*.pem
142 |
143 | # Docker
144 | .dockerignore
145 | docker-compose.override.yml
146 |
147 | # Cloudflare Workers
148 | .dev.vars
149 | .wrangler/
150 | worker-configuration.d.ts
151 |
152 | # Koyeb
153 | .koyeb/
154 |
155 | # SSL certificates and keys
156 | *.pem
157 | *.key
158 | *.crt
159 | *.ca-bundle
160 | *.p12
161 | *.pfx
162 |
163 | # API keys and tokens
164 | .api-keys
165 | *.token
166 | auth.json
167 |
168 | # Ouroboros and Swarm Agent Files
169 | ouroboros-results/
170 | ouroboros-logs/
171 | ouroboros-cache/
172 | ouroboros-data/
173 | swarm-cache/
174 | swarm-logs/
175 | agent-logs/
176 | agent-results/
177 | agent-cache/
178 | .ouroboros/
179 | .swarm/
180 |
181 | # Session and memory files
182 | SESSION-MEMORY.md
183 | session-*.md
184 | .session/
185 | memory/
186 |
187 | # MCP specific
188 | mcp-logs/
189 | mcp-cache/
190 | server-data/
191 | server-logs/
192 | backend-servers/
193 | loaded-servers/
194 | .mcp/
195 |
196 | # OAuth and authentication
197 | oauth-tokens/
198 | oauth-cache/
199 | .oauth/
200 | tokens/
201 | session-store/
202 |
203 | # Performance and monitoring
204 | metrics/
205 | traces/
206 | .monitoring/
207 | perf-data/
208 |
209 | # Local development
210 | .dev/
211 | dev-data/
212 | local-config/
213 | local-secrets/
214 |
215 | # Husky hooks (if needed, can be uncommented)
216 | .husky/
217 |
218 | # Backup files
219 | *.backup
220 | *.bak
221 | *~
222 |
223 | # Temporary files
224 | *.tmp
225 | *.temp
226 |
227 | # Archive files
228 | *.zip
229 | *.tar.gz
230 | *.rar
231 | *.7z
232 |
233 | # Binary files (unless specifically needed)
234 | *.exe
235 | *.dll
236 | *.so
237 | *.dylib
238 |
239 | # Cloud provider specific
240 | .aws/
241 | .gcp/
242 | .azure/
243 |
244 | # Terraform
245 | *.tfstate
246 | *.tfstate.*
247 | .terraform/
248 | .terraform.lock.hcl
249 |
250 | # Kubernetes
251 | *.kubeconfig
252 |
253 | # Monitoring and observability
254 | jaeger-data/
255 | prometheus-data/
256 | grafana-data/
257 |
258 | # Load testing
259 | artillery-output/
260 | k6-results/
261 |
262 | # Keep important files (negative patterns)
263 | !.gitkeep
264 | !.env.example
265 | !.env.template
266 | !config/default.json
267 | !config/schema.json
268 | !examples/**/*.yaml
269 | !examples/**/*.json
270 | !docs/**/*
271 | !deploy/docker/Dockerfile
272 | !deploy/cloudflare/wrangler.toml
273 | !deploy/koyeb/koyeb.yaml
274 |
275 |
276 | # MCP / local tooling
277 | .crush/
278 | .kiro/
279 | .claude/
280 | .ouroboros/
281 | ouroboros-results/
282 |
283 | # Keep uv.lock tracked (do not ignore)
284 |
285 | # IDEs and Ouroboros
286 | AGENTS.md
287 | CLAUDE.md
288 | CRUSH.md
289 | GEMINI.md
290 | QWEN.md
291 | IMPLEMENTATION_PLAN.md
292 | SESSION-MEMORY.md
293 | task-definition.md
294 |
295 | # Documentation application
296 | .vitepress
297 |
298 | # Random
299 | .DS_Store
```
--------------------------------------------------------------------------------
/examples/advanced-routing/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Advanced Routing Example
2 |
3 | Demonstrates retry policy, circuit breaker, and load balancing configuration.
4 |
5 | Run
6 | - `CONFIG_PATH=examples/advanced-routing/config.yaml npm start`
7 |
8 |
```
--------------------------------------------------------------------------------
/examples/performance/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Example: Performance Tuning
2 |
3 | Demonstrates routing configuration tuned for high-throughput and resilience.
4 |
5 | ## Run
6 |
7 | ```
8 | MASTER_CONFIG_PATH=examples/performance/config.yaml npm run dev
9 | ```
10 |
11 |
```
--------------------------------------------------------------------------------
/examples/multi-server/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Example: Multi-Server with Resilience
2 |
3 | Demonstrates multiple instances of the same service id with load balancing, retries, and circuit breaker.
4 |
5 | ## Run
6 |
7 | ```
8 | MASTER_CONFIG_PATH=examples/multi-server/config.yaml npm run dev
9 | ```
10 |
11 |
```
--------------------------------------------------------------------------------
/examples/custom-auth/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Example: Custom Authentication Strategy
2 |
3 | Shows how to attach a custom `MultiAuthManager` that tweaks headers per backend.
4 |
5 | ## Run
6 |
7 | ```
8 | MASTER_CONFIG_PATH=examples/custom-auth/config.yaml \
9 | node --loader ts-node/esm examples/custom-auth/index.ts
10 | ```
11 |
12 |
```
--------------------------------------------------------------------------------
/examples/cloudflare-worker/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Cloudflare Worker Example
2 |
3 | Runs the Master MCP Worker handler with Wrangler.
4 |
5 | Steps
6 | 1. Build worker bundle: `npm run build:worker`
7 | 2. Dev: `npx wrangler dev examples/cloudflare-worker/worker.ts`
8 | 3. Deploy: configure and run from `/deploy/cloudflare`.
9 |
10 |
```
--------------------------------------------------------------------------------
/docs/api/README.md:
--------------------------------------------------------------------------------
```markdown
1 | **master-mcp-server**
2 |
3 | ***
4 |
5 | # master-mcp-server
6 |
7 | ## Interfaces
8 |
9 | - [RunningServer](interfaces/RunningServer.md)
10 |
11 | ## Functions
12 |
13 | - [createServer](functions/createServer.md)
14 |
15 | ## References
16 |
17 | ### default
18 |
19 | Renames and re-exports [createServer](functions/createServer.md)
20 |
```
--------------------------------------------------------------------------------
/examples/basic-node/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Example: Basic Master MCP Server (Node)
2 |
3 | This example runs the Node runtime with a single local backend.
4 |
5 | ## Run
6 |
7 | ```
8 | MASTER_CONFIG_PATH=examples/basic-node/config.yaml npm run dev
9 | ```
10 |
11 | Endpoints:
12 |
13 | - http://localhost:3000/health
14 | - http://localhost:3000/mcp/tools/list
15 |
16 |
```
--------------------------------------------------------------------------------
/examples/oauth-delegation/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # OAuth Delegation Example
2 |
3 | Enable delegated OAuth for backends and complete flows via the built-in controller.
4 |
5 | Run
6 | 1. Configure `oauth_delegation.enabled: true` in your config, and set provider entries if desired.
7 | 2. Start the server and visit `/oauth` to initiate flows.
8 |
9 |
```
--------------------------------------------------------------------------------
/examples/oauth-node/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Example: OAuth Delegation (GitHub)
2 |
3 | Demonstrates `delegate_oauth` for a GitHub-protected backend.
4 |
5 | ## Run
6 |
7 | Set secrets:
8 |
9 | ```
10 | export GITHUB_CLIENT_SECRET=... # from GitHub OAuth app
11 | ```
12 |
13 | Start:
14 |
15 | ```
16 | MASTER_CONFIG_PATH=examples/oauth-node/config.yaml npm run dev
17 | ```
18 |
19 | Then open:
20 |
21 | ```
22 | http://localhost:3000/oauth/authorize?server_id=github-tools
23 | ```
24 |
25 |
```
--------------------------------------------------------------------------------
/examples/security-hardening/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Example: Security Hardening
2 |
3 | This example outlines recommended environment and config for production.
4 |
5 | ## Environment
6 |
7 | - `NODE_ENV=production`
8 | - `LOG_FORMAT=json`
9 | - `LOG_LEVEL=info`
10 | - `TOKEN_ENC_KEY` set via secret store
11 | - `MASTER_BASE_URL=https://your.domain`
12 |
13 | ## Config Snippet
14 |
15 | ```yaml
16 | security:
17 | audit: true
18 | logging:
19 | level: info
20 | hosting:
21 | platform: node
22 | port: 3000
23 | ```
24 |
25 | Deploy with Docker/Koyeb using read-only config mounts and non-root users (see Dockerfiles).
26 |
27 |
```
--------------------------------------------------------------------------------
/deploy/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Deployment Overview
2 |
3 | Artifacts and configs to deploy the Master MCP Server:
4 |
5 | - Docker
6 | - `deploy/docker/Dockerfile`: multi-stage build for Node runtime.
7 | - `deploy/docker/docker-compose.yml`: local development runner.
8 | - Cloudflare Workers
9 | - `deploy/cloudflare/wrangler.toml`: Workers config (staging/production envs).
10 | - `deploy/cloudflare/README.md`: usage notes.
11 | - Koyeb
12 | - `deploy/koyeb/koyeb.yaml`: Koyeb service and autoscaling settings.
13 |
14 | CI/CD pipelines live in `.github/workflows`. See `docs/architecture/phase10-deployment-architecture.md` for full details.
15 |
16 |
```
--------------------------------------------------------------------------------
/deploy/cloudflare/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Cloudflare Workers Deployment
2 |
3 | ## Prerequisites
4 | - Cloudflare account and `CLOUDFLARE_API_TOKEN` with Workers permissions.
5 | - `wrangler` installed locally or use GitHub Action for CI deploys.
6 |
7 | ## Local Dev
8 | ```
9 | wrangler dev
10 | ```
11 |
12 | ## Configure Secrets
13 | ```
14 | wrangler secret put TOKEN_ENC_KEY
15 | # Add any OAuth client secrets as needed
16 | ```
17 |
18 | ## Deploy
19 | ```
20 | wrangler deploy --env staging
21 | wrangler deploy --env production
22 | ```
23 |
24 | 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).
25 |
26 |
```
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Master MCP Server Documentation
2 |
3 | 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.
4 |
5 | ## Contents
6 |
7 | - Getting Started: `../docs/getting-started.md`
8 | - Guides:
9 | - Authentication: `./guides/authentication.md`
10 | - Configuration Mgmt: `./guides/configuration-management.md`
11 | - Module Loading & Aggregation: `./guides/module-loading.md`
12 | - Request Routing & Resilience: `./guides/request-routing.md`
13 | - Server Management: `./guides/server-management.md`
14 | - Tutorials:
15 | - Beginner Setup: `./tutorials/beginner-getting-started.md`
16 | - OAuth Delegation with GitHub: `./tutorials/oauth-delegation-github.md`
17 | - Cloudflare Workers: `./tutorials/cloudflare-workers-tutorial.md`
18 | - Load Balancing & Resilience: `./tutorials/load-balancing-and-resilience.md`
19 | - API Reference: `./api/README.md` (generated via TypeDoc)
20 | - Configuration:
21 | - Schema & Reference: `./configuration/reference.md`
22 | - Environment Variables: `./configuration/environment-variables.md`
23 | - Security: `./configuration/security.md`
24 | - Performance: `./configuration/performance.md`
25 | - Deployment:
26 | - Docker: `./deployment/docker.md`
27 | - Cloudflare Workers: `./deployment/cloudflare-workers.md`
28 | - Koyeb: `./deployment/koyeb.md`
29 | - CI/CD: `./deployment/cicd.md`
30 | - Troubleshooting:
31 | - Common Issues: `./troubleshooting/common-issues.md`
32 | - Error Reference: `./troubleshooting/errors.md`
33 | - Performance Troubleshooting: `./troubleshooting/performance.md`
34 | - Security Best Practices: `./troubleshooting/security-best-practices.md`
35 |
36 | If you are looking for design notes per phase, see `./architecture/*` and testing strategy in `./testing/phase-9-testing-architecture.md`.
37 |
38 |
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Master MCP Server
2 |
3 | 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.
4 |
5 | ## Highlights
6 |
7 | - Aggregates multiple MCP servers with tool/resource discovery and namespacing
8 | - OAuth support: master token pass-through, delegated provider flows, proxy refresh
9 | - Config-driven setup with JSON/YAML, schema validation, and secret resolution
10 | - Resilient routing: load-balancing, retries with backoff/jitter, circuit-breakers
11 | - Cross-platform: Node.js server and Cloudflare Workers runtime
12 | - Production-ready deployment: Docker, Cloudflare Workers, Koyeb
13 | - Testing strategy and CI-ready structure
14 |
15 | ## Quick Start (Node.js)
16 |
17 | 1) Install dependencies (requires network):
18 |
19 | ```plaintext
20 | npm ci
21 | ```
22 |
23 | 2) Configure environment (copy and edit):
24 |
25 | ```plaintext
26 | cp .env.example .env
27 | ```
28 |
29 | 3) Run in dev mode:
30 |
31 | ```plaintext
32 | npm run dev
33 | ```
34 |
35 | 4) Health and Metrics:
36 |
37 | - `GET /health` → `{ ok: true }`
38 | - `GET /metrics` → basic system metrics
39 |
40 | 5) MCP endpoints (HTTP gateway):
41 |
42 | - `POST /mcp/tools/list`
43 | - `POST /mcp/tools/call` with `{ name, arguments }`
44 | - `POST /mcp/resources/list`
45 | - `POST /mcp/resources/read` with `{ uri }`
46 |
47 | See `docs/` for full guides and end-to-end examples.
48 |
49 | ## Documentation
50 |
51 | - Docs index: `docs/index.md`
52 | - Getting started: `docs/getting-started/overview.md`
53 | - Guides: `docs/guides/*`
54 | - API reference: generated into `docs/api/reference/` (see below)
55 | - Configuration reference: `docs/configuration/*`
56 | - Deployment: `docs/deployment/*`
57 | - Troubleshooting: `docs/troubleshooting/*`
58 | - Contributing: `docs/contributing/*`
59 |
60 | ## Generate API Docs
61 |
62 | We use TypeDoc (Markdown) to generate API docs from TypeScript.
63 |
64 | 1) Install (requires network):
65 |
66 | ```plaintext
67 | npm i -D typedoc typedoc-plugin-markdown
68 | ```
69 |
70 | 2) Generate docs:
71 |
72 | ```plaintext
73 | npm run docs:api
74 | ```
75 |
76 | Outputs to `docs/api/`.
77 |
78 | ## Examples
79 |
80 | Working examples live in `examples/`:
81 |
82 | - Basic Node: `examples/basic-node`
83 | - Cloudflare Worker: `examples/cloudflare-worker`
84 | - Advanced Routing: `examples/advanced-routing`
85 | - OAuth Delegation: `examples/oauth-delegation`
86 | - Testing Patterns: see `/tests` and `docs/examples/testing.md`
87 |
88 | Each example has a README with run instructions.
89 |
90 | ## Deployment
91 |
92 | - Docker: `deploy/docker/*` and top-level `Dockerfile` / `docker-compose.yml`
93 | - Cloudflare Workers: `deploy/cloudflare/*` with `wrangler.toml`
94 | - Koyeb: `deploy/koyeb/koyeb.yaml`
95 | - CI/CD examples: see `docs/deployment/cicd.md`
96 |
97 | ## Architecture
98 |
99 | 
100 |
101 | ## Contributing & Support
102 |
103 | - See `docs/contributing/*` for development workflow and guidelines
104 | - See `docs/troubleshooting/index.md` for solutions
105 | - Open an issue or discussion for help and ideas
106 |
107 | ## License
108 |
109 | See `LICENSE`. This repository currently uses UNLICENSED for private/internal use.
110 |
```
--------------------------------------------------------------------------------
/docs/advanced/security.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Security Hardening
3 | ---
4 |
5 | # Security Hardening
6 |
7 | - Rotate secrets regularly (`security.rotation_days`).
8 | - Use `security.audit` for config change logging.
9 | - Lock down OAuth redirect URIs and audiences.
10 | - Enforce strict TLS and CSP in hosting platform.
11 | - Validate inputs and sanitize logs (see `utils/security` patterns).
12 |
13 |
```
--------------------------------------------------------------------------------
/docs/configuration/security.md:
--------------------------------------------------------------------------------
```markdown
1 | # Security Configuration & Practices
2 |
3 | ## Secrets in Config
4 |
5 | Use either of the following in your config:
6 |
7 | - `env:VARNAME` → value is read from `process.env.VARNAME`
8 | - `enc:gcm:<base64>` → value is decrypted using `MASTER_CONFIG_KEY` (or `MASTER_SECRET_KEY`)
9 |
10 | `SecretManager` handles both resolving and redacting sensitive values for logs.
11 |
12 | ## Token Encryption
13 |
14 | `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.
15 |
16 | ## OAuth Best Practices
17 |
18 | - Always set `hosting.base_url` correctly for accurate redirect URIs behind proxies.
19 | - Use PKCE (enabled by default) and short-lived state tokens.
20 | - Limit scopes in `servers[].auth_config.scopes` to the minimum required.
21 |
22 | ## Hardening Tips
23 |
24 | - Drop container capabilities and run as non-root (see Dockerfiles)
25 | - Use `LOG_FORMAT=json` in production for structured logs
26 | - Ensure secrets are injected via platform secret stores (KMS, Workers Secrets, Koyeb Secrets)
27 | - Enable `security.audit` to log config changes (redacted)
28 |
29 |
```
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
```markdown
1 | # Contributing Guide
2 |
3 | Thanks for your interest in contributing to the Master MCP Server. This guide outlines the development workflow and standards.
4 |
5 | ## Development Setup
6 |
7 | 1) Install Node >= 18.17
8 | 2) Install dependencies:
9 |
10 | ```
11 | npm ci
12 | ```
13 |
14 | 3) Useful scripts:
15 |
16 | ```
17 | npm run typecheck
18 | npm run build
19 | npm run dev
20 | npm run test
21 | npm run lint && npm run format
22 | ```
23 |
24 | ## Docs & API Reference
25 |
26 | - Author guides and tutorials in `docs/`
27 | - Generate API docs (requires network): `npm run docs:api`
28 | - Keep examples in `examples/` runnable and minimal
29 |
30 | ## Coding Standards
31 |
32 | - TypeScript strict mode; no `any` without justification
33 | - Prefer small, composable modules and clear interfaces
34 | - Avoid introducing runtime-only dependencies in shared modules (support both Node and Workers)
35 |
36 | ## Testing
37 |
38 | - Unit tests under `tests/unit` and integration under `tests/integration`
39 | - Add targeted tests for new modules and critical paths
40 |
41 | ## Commit Style
42 |
43 | - Use clear, imperative messages (e.g., "Add OAuth state validation")
44 | - Reference issues where applicable
45 |
46 | ## Security
47 |
48 | - Never commit secrets
49 | - Use `SecretManager` patterns for config secrets
50 |
51 |
```
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "type": "module"
3 | }
4 |
```
--------------------------------------------------------------------------------
/config/production.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "logging": { "level": "info" }
3 | }
4 |
5 |
```
--------------------------------------------------------------------------------
/config/development.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "logging": { "level": "debug" }
3 | }
4 |
5 |
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "extends": "./tsconfig.node.json"
3 | }
4 |
5 |
```
--------------------------------------------------------------------------------
/examples/cloudflare-worker/worker.ts:
--------------------------------------------------------------------------------
```typescript
1 | export { default as default } from '../../src/runtime/worker.js'
2 |
3 |
```
--------------------------------------------------------------------------------
/src/utils/validators.ts:
--------------------------------------------------------------------------------
```typescript
1 | export { isNonEmptyString, isRecord } from './validation.js'
2 | export * as Validation from './validation.js'
3 |
```
--------------------------------------------------------------------------------
/docs/advanced/index.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Advanced Topics
3 | ---
4 |
5 | # Advanced Topics
6 |
7 | Deep dives into security, performance, monitoring, and extensibility.
8 |
9 |
```
--------------------------------------------------------------------------------
/tests/fixtures/stdio-server.js:
--------------------------------------------------------------------------------
```javascript
1 | process.stdout.write(JSON.stringify({ type: 'notification', message: 'server ready' }) + '\n');
2 |
3 | setInterval(() => {
4 | // Keep the process alive
5 | }, 1000)
```
--------------------------------------------------------------------------------
/static/oauth/script.js:
--------------------------------------------------------------------------------
```javascript
1 | // Placeholder for future enhancements like telemetry, animations, etc.
2 | // Intentionally minimal to reduce attack surface.
3 | document.documentElement.classList.add('js')
4 |
5 |
```
--------------------------------------------------------------------------------
/tests/fixtures/capabilities.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "tools": [
3 | { "name": "echo", "description": "Echo input" }
4 | ],
5 | "resources": [
6 | { "uri": "docs.readme", "name": "Readme", "mimeType": "text/plain" }
7 | ]
8 | }
9 |
10 |
```
--------------------------------------------------------------------------------
/examples/basic-node/server.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { createServer } from '../../src/index.js'
2 |
3 | async function main() {
4 | await createServer(true)
5 | }
6 |
7 | // eslint-disable-next-line @typescript-eslint/no-floating-promises
8 | main()
9 |
10 |
```
--------------------------------------------------------------------------------
/docs/advanced/extensibility.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Extensibility & Plugins
3 | ---
4 |
5 | # Extensibility & Plugins
6 |
7 | - Extend `ModuleLoader` to support new sources.
8 | - Add routing strategies by extending the load balancer.
9 | - Use typed interfaces in `src/types/*` for compatibility.
10 |
11 |
```
--------------------------------------------------------------------------------
/docs/examples/testing.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Testing Patterns
3 | ---
4 |
5 | # Example: Testing Patterns
6 |
7 | Use `/tests` as the primary reference. This example demonstrates configuring Node vs Workers test environments and using utilities in `_utils`.
8 |
9 | Run
10 | - `npm run test` or target specific suites.
11 |
12 |
```
--------------------------------------------------------------------------------
/docs/contributing/index.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Contributing
3 | ---
4 |
5 | # Contributing
6 |
7 | We welcome contributions across code, documentation, and examples.
8 |
9 | - Read Guidelines for coding and docs conventions
10 | - Use Dev Setup to get your environment ready
11 | - Open PRs with focused changes and include tests/docs
12 |
13 |
```
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "extends": "./tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "dist/node",
5 | "module": "NodeNext",
6 | "moduleResolution": "NodeNext",
7 | "types": ["node"],
8 | "lib": ["ES2022"],
9 | "jsx": "react-jsx"
10 | },
11 | "exclude": ["src/runtime/worker.ts"]
12 | }
13 |
```
--------------------------------------------------------------------------------
/docs/advanced/performance.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Performance & Scalability
3 | ---
4 |
5 | # Performance & Scalability
6 |
7 | - Tune retry/backoff to avoid thundering herd.
8 | - Right-size circuit breaker thresholds per backend latency.
9 | - Use caching (`utils/cache`) where safe.
10 | - Monitor p95/p99 latencies and error rates.
11 |
12 |
```
--------------------------------------------------------------------------------
/docs/advanced/monitoring.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Monitoring & Logging
3 | ---
4 |
5 | # Monitoring & Logging
6 |
7 | - Use `/metrics` endpoint and `collectSystemMetrics()` for basic telemetry.
8 | - Integrate logs with your platform (stdout in Docker/Koyeb, Workers logs).
9 | - Centralize error handling in `utils/errors` and `utils/logger`.
10 |
11 |
```
--------------------------------------------------------------------------------
/tests/_setup/miniflare.setup.ts:
--------------------------------------------------------------------------------
```typescript
1 | // Miniflare/Vitest setup for Workers-targeted suites
2 | import { Logger } from '../../src/utils/logger.js'
3 |
4 | Logger.configure({ level: 'error', json: true })
5 |
6 | // Miniflare provides global fetch/Request/Response.
7 | // If we need to start auxiliary Node HTTP stubs, do it within tests.
8 |
9 |
```
--------------------------------------------------------------------------------
/docs/troubleshooting/deployment.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Deployment
3 | ---
4 |
5 | # Troubleshooting: Deployment
6 |
7 | - Docker: ensure env files loaded and ports exposed.
8 | - Workers: align bundling target and avoid Node APIs.
9 | - Koyeb: ensure service listens on the configured port.
10 | - Secrets: verify platform secret injection and `config_key_env`.
11 |
12 |
```
--------------------------------------------------------------------------------
/docs/examples/oauth-delegation.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: OAuth Delegation
3 | ---
4 |
5 | # Example: OAuth Delegation
6 |
7 | Folder: `/examples/oauth-delegation`
8 |
9 | What it shows
10 | - Enabling `oauth_delegation` and providers
11 | - Using `OAuthFlowController` to complete flows
12 |
13 | Run
14 | 1. Configure providers and redirect URIs
15 | 2. Start Node server and visit `/oauth` UI
16 |
17 |
```
--------------------------------------------------------------------------------
/docs/examples/cloudflare-worker.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Cloudflare Worker
3 | ---
4 |
5 | # Example: Cloudflare Worker
6 |
7 | Folder: `/examples/cloudflare-worker`
8 |
9 | What it shows
10 | - Worker `fetch` handler delegating to runtime adapter
11 | - Using `wrangler.toml` from `/deploy/cloudflare`
12 |
13 | Run
14 | 1. `npm run build:worker`
15 | 2. `npx wrangler dev examples/cloudflare-worker/worker.ts`
16 |
17 |
```
--------------------------------------------------------------------------------
/src/types/jose-shim.d.ts:
--------------------------------------------------------------------------------
```typescript
1 | declare module 'jose' {
2 | export function createRemoteJWKSet(url: URL): any
3 | export function jwtVerify(
4 | jwt: string,
5 | key: any,
6 | options?: { issuer?: string | string[]; audience?: string | string[] }
7 | ): Promise<{ payload: any; protectedHeader: any }>
8 | export function decodeJwt(jwt: string): any
9 | }
10 |
11 |
```
--------------------------------------------------------------------------------
/tsconfig.worker.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "extends": "./tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "dist/worker",
5 | "module": "ESNext",
6 | "moduleResolution": "Bundler",
7 | "types": [],
8 | "lib": ["ES2022", "WebWorker"]
9 | },
10 | "exclude": [
11 | "src/runtime/node.ts",
12 | "src/utils/crypto.ts",
13 | "src/config/**",
14 | "src/auth/token-manager.ts"
15 | ]
16 | }
17 |
```
--------------------------------------------------------------------------------
/docs/deployment/index.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Deployment Overview
3 | ---
4 |
5 | # Deployment Overview
6 |
7 | Supported targets
8 | - Docker (Node runtime)
9 | - Cloudflare Workers
10 | - Koyeb (Node runtime)
11 |
12 | Deployment assets live in `/deploy/*`. Platform-specific guides reference these files and environment variable requirements.
13 |
14 | See architecture: `docs/architecture/phase10-deployment-architecture.md`.
15 |
16 |
```
--------------------------------------------------------------------------------
/deploy/docker/entrypoint.sh:
--------------------------------------------------------------------------------
```bash
1 | #!/bin/sh
2 | set -e
3 |
4 | # Map PaaS-provided PORT to MASTER_HOSTING_PORT if not explicitly set.
5 | if [ -n "${PORT}" ] && [ -z "${MASTER_HOSTING_PORT}" ]; then
6 | export MASTER_HOSTING_PORT="${PORT}"
7 | fi
8 |
9 | # Default to json logs in production if not set
10 | if [ "${NODE_ENV}" = "production" ] && [ -z "${LOG_FORMAT}" ]; then
11 | export LOG_FORMAT=json
12 | fi
13 |
14 | exec "$@"
15 |
16 |
```
--------------------------------------------------------------------------------
/docs/guides/index.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Guides
3 | ---
4 |
5 | # User Guides
6 |
7 | Structured, task-based guides for common scenarios across auth, routing, configuration, testing, and operations.
8 |
9 | Recommended path by persona
10 | - Developers: Authentication → Module Loading → Request Routing
11 | - DevOps: Configuration → Deployment → Monitoring
12 | - Integrators: OAuth Delegation → Routing policies → Security
13 |
14 |
```
--------------------------------------------------------------------------------
/docs/examples/advanced-routing.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Advanced Routing
3 | ---
4 |
5 | # Example: Advanced Routing
6 |
7 | Folder: `/examples/advanced-routing`
8 |
9 | What it shows
10 | - Configure retry policy with backoff/jitter
11 | - Circuit breaker thresholds and recovery
12 | - Load balancer strategies
13 |
14 | Run
15 | 1. Use `config.yaml` from the example
16 | 2. Start the server with `CONFIG_PATH=examples/advanced-routing/config.yaml npm start`
17 |
18 |
```
--------------------------------------------------------------------------------
/docs/configuration/environment.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Environment Variables
3 | ---
4 |
5 | # Environment Variables
6 |
7 | Common variables
8 | - `NODE_ENV`: `development` | `production`
9 | - `PORT`: overrides `hosting.port`
10 | - OAuth secrets: `MASTER_OAUTH_CLIENT_SECRET`, provider secrets
11 | - Encryption: key name from `security.config_key_env`
12 |
13 | Use `.env` for local development and set secrets via platform secrets in production.
14 |
15 |
```
--------------------------------------------------------------------------------
/examples/basic-node/config.yaml:
--------------------------------------------------------------------------------
```yaml
1 | hosting:
2 | platform: node
3 | port: 3000
4 |
5 | master_oauth:
6 | authorization_endpoint: https://example.com/oauth/authorize
7 | token_endpoint: https://example.com/oauth/token
8 | client_id: master-mcp
9 | redirect_uri: http://localhost:3000/oauth/callback
10 | scopes: [openid]
11 |
12 | servers:
13 | - id: tools
14 | type: local
15 | auth_strategy: bypass_auth
16 | config:
17 | port: 3333
18 |
19 |
```
--------------------------------------------------------------------------------
/tests/factories/mcpFactory.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { ToolDefinition, ResourceDefinition } from '../../src/types/mcp.js'
2 |
3 | export function makeTools(...names: string[]): ToolDefinition[] {
4 | return names.map((n) => ({ name: n, description: `${n} tool` }))
5 | }
6 |
7 | export function makeResources(...uris: string[]): ResourceDefinition[] {
8 | return uris.map((u) => ({ uri: u, name: u, mimeType: 'text/plain' }))
9 | }
10 |
11 |
```
--------------------------------------------------------------------------------
/static/oauth/success.html:
--------------------------------------------------------------------------------
```html
1 | <!doctype html>
2 | <html lang="en">
3 | <head>
4 | <meta charset="utf-8" />
5 | <meta name="viewport" content="width=device-width, initial-scale=1" />
6 | <title>OAuth Success</title>
7 | <link rel="stylesheet" href="/static/oauth/style.css" />
8 | </head>
9 | <body>
10 | <main class="container">
11 | <h1>Success</h1>
12 | <p>Authorization completed successfully.</p>
13 | </main>
14 | </body>
15 | </html>
16 |
17 |
```
--------------------------------------------------------------------------------
/typedoc.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "$schema": "https://typedoc.org/schema.json",
3 | "entryPoints": [
4 | "src"
5 | ],
6 | "tsconfig": "tsconfig.node.json",
7 | "plugin": ["typedoc-plugin-markdown"],
8 | "out": "docs/api",
9 | "cleanOutputDir": false,
10 | "categorizeByGroup": false,
11 | "hideBreadcrumbs": true,
12 | "excludePrivate": true,
13 | "excludeProtected": false,
14 | "excludeInternal": true,
15 | "readme": "none"
16 | }
17 |
18 |
```
--------------------------------------------------------------------------------
/docs/troubleshooting/routing.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Routing & Modules
3 | ---
4 |
5 | # Troubleshooting: Routing & Modules
6 |
7 | - Missing capabilities: call `discoverAllCapabilities()` after loading servers.
8 | - Unhealthy backend: check health checks and restart via `restartServer(id)`.
9 | - Retry storms: reduce `maxRetries` or increase `baseDelayMs`.
10 | - Circuit breaker open: lower `failureThreshold` or increase recovery timeout.
11 |
12 |
```
--------------------------------------------------------------------------------
/docs/contributing/dev-setup.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Development Setup
3 | ---
4 |
5 | # Development Setup
6 |
7 | Prerequisites
8 | - Node 18.17+
9 | - npm or pnpm
10 |
11 | Common commands
12 | - `npm run build` — build Node and Worker bundles
13 | - `npm run dev` — start local server
14 | - `npm run test` — run test suites
15 | - `npm run lint` / `npm run format` — code quality
16 |
17 | Docs
18 | - `npm run docs:api` — generate API reference
19 | - `npm run docs:dev` — serve docs locally
20 |
21 |
```
--------------------------------------------------------------------------------
/examples/sample-configs/basic.yaml:
--------------------------------------------------------------------------------
```yaml
1 | hosting:
2 | platform: node
3 | port: 3000
4 |
5 | master_oauth:
6 | authorization_endpoint: https://example.com/auth
7 | token_endpoint: https://example.com/token
8 | client_id: demo-client
9 | redirect_uri: http://localhost:3000/callback
10 | scopes:
11 | - openid
12 |
13 | servers:
14 | - id: example
15 | type: local
16 | auth_strategy: master_oauth
17 | config:
18 | environment: {}
19 | args: []
20 | port: 3333
21 |
22 |
```
--------------------------------------------------------------------------------
/static/oauth/error.html:
--------------------------------------------------------------------------------
```html
1 | <!doctype html>
2 | <html lang="en">
3 | <head>
4 | <meta charset="utf-8" />
5 | <meta name="viewport" content="width=device-width, initial-scale=1" />
6 | <title>OAuth Error</title>
7 | <link rel="stylesheet" href="/static/oauth/style.css" />
8 | </head>
9 | <body>
10 | <main class="container error">
11 | <h1>Authorization Error</h1>
12 | <p>Something went wrong during the OAuth flow.</p>
13 | </main>
14 | </body>
15 | </html>
16 |
17 |
```
--------------------------------------------------------------------------------
/docs/examples/basic-node.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Basic Node Aggregator
3 | ---
4 |
5 | # Example: Basic Node Aggregator
6 |
7 | Folder: `/examples/basic-node`
8 |
9 | What it shows
10 | - Load two MCP servers from config
11 | - Expose health, metrics, and MCP endpoints
12 | - Validate configuration with schema
13 |
14 | Run
15 | 1. `npm run build`
16 | 2. `node examples/basic-node/server.ts` (ts-node recommended for dev)
17 |
18 | Config
19 | - Copy and adapt `examples/sample-configs/basic.yaml`.
20 |
21 |
```
--------------------------------------------------------------------------------
/docs/getting-started/quickstart-workers.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Quickstart (Workers)
3 | ---
4 |
5 | # Quickstart (Cloudflare Workers)
6 |
7 | Run Master MCP in Cloudflare Workers runtime using `src/runtime/worker.ts`.
8 |
9 | Steps
10 | 1. Copy `deploy/cloudflare/wrangler.toml` and adjust env vars.
11 | 2. Build worker bundle: `npm run build:worker`.
12 | 3. Publish with Wrangler: `npx wrangler deploy`.
13 |
14 | See also: Deployment → Cloudflare Workers and Examples → Cloudflare Worker.
15 |
16 |
```
--------------------------------------------------------------------------------
/docs/getting-started/installation.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Installation
3 | ---
4 |
5 | # Installation
6 |
7 | Prerequisites
8 | - Node.js >= 18.17
9 | - npm or pnpm
10 |
11 | Install
12 | 1. Clone the repository and install deps: `npm install`
13 | 2. Copy `.env.example` to `.env` and adjust values.
14 | 3. Build Node and Worker bundles: `npm run build`
15 |
16 | Run (Node)
17 | - Dev: `npm run dev` or `npm run dev:watch`
18 | - Prod: `npm run start:prod`
19 |
20 | Next: Quickstart (Node) or Quickstart (Workers)
21 |
22 |
```
--------------------------------------------------------------------------------
/examples/sample-configs/simple-setup.yaml:
--------------------------------------------------------------------------------
```yaml
1 | hosting:
2 | platform: node
3 | port: 3000
4 |
5 | master_oauth:
6 | authorization_endpoint: https://auth.example.com/authorize
7 | token_endpoint: https://auth.example.com/token
8 | client_id: master-mcp
9 | redirect_uri: http://localhost:3000/oauth/callback
10 | scopes: [openid, profile]
11 |
12 | servers:
13 | - id: local-simple
14 | type: local
15 | auth_strategy: bypass_auth
16 | config:
17 | port: 4001
18 | environment: {}
19 |
20 |
```
--------------------------------------------------------------------------------
/tests/factories/oauthFactory.ts:
--------------------------------------------------------------------------------
```typescript
1 | export function makeToken(overrides?: Partial<{ access_token: string; expires_in: number; scope: string | string[] }>) {
2 | const scope = overrides?.scope ?? ['openid']
3 | return {
4 | access_token: overrides?.access_token ?? `at_${Math.random().toString(36).slice(2)}`,
5 | token_type: 'bearer',
6 | expires_in: overrides?.expires_in ?? 3600,
7 | scope: Array.isArray(scope) ? scope.join(' ') : scope,
8 | }
9 | }
10 |
11 |
```
--------------------------------------------------------------------------------
/src/runtime/node.ts:
--------------------------------------------------------------------------------
```typescript
1 | import express from 'express'
2 | import { createServer } from '../index.js'
3 |
4 | export async function startNode() {
5 | const app = express()
6 | await createServer()
7 | const port = process.env.PORT ? Number(process.env.PORT) : 3000
8 | app.get('/health', (_req, res) => res.json({ ok: true }))
9 | app.listen(port, () => {
10 | // eslint-disable-next-line no-console
11 | console.log(`Master MCP (Node) on ${port}`)
12 | })
13 | }
14 |
15 |
```
--------------------------------------------------------------------------------
/docs/contributing/guidelines.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Coding & Docs Guidelines
3 | ---
4 |
5 | # Coding & Docs Guidelines
6 |
7 | Code
8 | - Follow TypeScript strictness and ESLint rules.
9 | - Prefer TSDoc comments for public APIs with `@example` where useful.
10 | - Keep modules cohesive and avoid unrelated changes.
11 |
12 | Docs
13 | - Write in Markdown under `/docs`.
14 | - Add pages to VitePress sidebar via `.vitepress/config.ts`.
15 | - For API docs, rely on TypeDoc output under `/docs/api/reference`.
16 |
17 |
```
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
```markdown
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | ## 0.1.0 – Phase 11 (Final)
6 |
7 | - Complete documentation set (guides, tutorials, configuration, deployment)
8 | - API reference pipeline via TypeDoc
9 | - Working examples for Node, OAuth delegation, multi-server, custom auth, performance, security
10 | - Deployment guides for Docker, Cloudflare Workers, and Koyeb
11 | - Troubleshooting and best practices
12 |
13 |
```
--------------------------------------------------------------------------------
/docs/contributing/maintenance.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Maintenance Procedures
3 | ---
4 |
5 | # Maintenance & Updates
6 |
7 | - After adding/changing public APIs: update TSDoc and run `npm run docs:api`.
8 | - After changing config schema or examples: run `npm run docs:config`.
9 | - Before releases: run `npm run docs:all` and preview with `npm run docs:preview`.
10 | - Keep `.vitepress/config.ts` nav/sidebars in sync with new pages.
11 | - Update examples under `/examples/*` and link from `/docs/examples`.
12 |
13 |
```
--------------------------------------------------------------------------------
/docs/api/functions/createServer.md:
--------------------------------------------------------------------------------
```markdown
1 | [**master-mcp-server**](../README.md)
2 |
3 | ***
4 |
5 | # Function: createServer()
6 |
7 | > **createServer**(`startHttp`): `Promise`\<[`RunningServer`](../interfaces/RunningServer.md)\>
8 |
9 | Defined in: [index.ts:18](https://github.com/solita-internal/master-mcp-server/blob/cd13e0009f7a1b7f244de882dc738bbf1f90f2c2/src/index.ts#L18)
10 |
11 | ## Parameters
12 |
13 | ### startHttp
14 |
15 | `boolean` = `true`
16 |
17 | ## Returns
18 |
19 | `Promise`\<[`RunningServer`](../interfaces/RunningServer.md)\>
20 |
```
--------------------------------------------------------------------------------
/docs/troubleshooting/oauth.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: OAuth & Tokens
3 | ---
4 |
5 | # Troubleshooting: OAuth & Tokens
6 |
7 | - Symptom: `invalid_grant` — Check code exchange timing and redirect URI match.
8 | - Symptom: `invalid_client` — Verify client_id/secret; rotate if leaked.
9 | - Symptom: missing `Authorization` — Ensure client token is passed to router/handler.
10 | - Symptom: provider callback 404 — Mount `OAuthFlowController` endpoints.
11 |
12 | Logs
13 | - Increase log level to `debug` to see flow details.
14 |
15 |
```
--------------------------------------------------------------------------------
/docs/troubleshooting/security-best-practices.md:
--------------------------------------------------------------------------------
```markdown
1 | # Security Best Practices
2 |
3 | - Store secrets in environment or platform secret stores; avoid plaintext in config
4 | - Set `TOKEN_ENC_KEY` in production and rotate periodically
5 | - Use minimal OAuth scopes and avoid long-lived tokens when possible
6 | - Prefer `LOG_FORMAT=json` and sanitize logs; `SecretManager.redact` prevents secret leakage in config logs
7 | - Enforce `https` at the edge and set `MASTER_BASE_URL=https://...` to ensure secure redirects
8 |
9 |
```
--------------------------------------------------------------------------------
/tests/_setup/vitest.setup.ts:
--------------------------------------------------------------------------------
```typescript
1 | // Global test setup for Node-targeted suites
2 | import { Logger } from '../../src/utils/logger.js'
3 |
4 | // Reduce log noise; allow tests to opt into capture
5 | Logger.configure({ level: 'error', json: true })
6 |
7 | // Node 18 has global fetch; ensure it exists for all tests
8 | if (typeof fetch !== 'function') {
9 | // eslint-disable-next-line @typescript-eslint/no-var-requires
10 | const nodeFetch = require('node-fetch')
11 | // @ts-ignore
12 | globalThis.fetch = nodeFetch
13 | }
14 |
15 |
```
--------------------------------------------------------------------------------
/docs/examples/index.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Examples
3 | ---
4 |
5 | # Examples
6 |
7 | Real-world usage patterns for Node and Workers with routing, OAuth, and configuration.
8 |
9 | - Basic Node Aggregator — minimal end-to-end setup
10 | - Cloudflare Worker — Workers runtime entry
11 | - Advanced Routing — retries, circuit-breaking, load balancing
12 | - OAuth Delegation — per-backend OAuth flows
13 | - Testing Patterns — integrate with the test utilities
14 |
15 | Each example has a runnable skeleton under `/examples/*` with a README.
16 |
17 |
```
--------------------------------------------------------------------------------
/tests/perf/artillery/auth-routing.yaml:
--------------------------------------------------------------------------------
```yaml
1 | config:
2 | target: "http://127.0.0.1:3000"
3 | phases:
4 | - duration: 30
5 | arrivalRate: 10
6 | - duration: 60
7 | arrivalRate: 25
8 | scenarios:
9 | - name: "Call tool echo"
10 | flow:
11 | - post:
12 | url: "/mcp/tools/call"
13 | json:
14 | name: "backend.echo"
15 | arguments: { msg: "hello" }
16 | - think: 1
17 | - name: "Read resource"
18 | flow:
19 | - post:
20 | url: "/mcp/resources/read"
21 | json:
22 | uri: "backend.docs.readme"
23 |
24 |
```
--------------------------------------------------------------------------------
/docs/guides/oauth-delegation.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: OAuth Delegation
3 | ---
4 |
5 | # OAuth Delegation Guide
6 |
7 | Enable per-server OAuth by delegating authorization flows.
8 |
9 | - Configure `oauth_delegation.enabled: true` and optional `providers` map.
10 | - Implement callback base URL and provider-specific overrides.
11 | - Use `FlowController` with Express or Workers runtime to complete flows.
12 |
13 | Security
14 | - Use state and PKCE to prevent CSRF and code interception.
15 | - Restrict allowed redirect URIs.
16 |
17 | See: `src/oauth/*` and `examples/oauth-delegation/`.
18 |
19 |
```
--------------------------------------------------------------------------------
/static/oauth/consent.html:
--------------------------------------------------------------------------------
```html
1 | <!doctype html>
2 | <html lang="en">
3 | <head>
4 | <meta charset="utf-8" />
5 | <meta name="viewport" content="width=device-width, initial-scale=1" />
6 | <title>Connect Account</title>
7 | <link rel="stylesheet" href="/static/oauth/style.css" />
8 | <script src="/static/oauth/script.js" defer></script>
9 | </head>
10 | <body>
11 | <main class="container">
12 | <h1>Connect Your Account</h1>
13 | <p>This page is a placeholder template. The server may use dynamic pages for provider redirect.</p>
14 | </main>
15 | </body>
16 | </html>
17 |
18 |
```
--------------------------------------------------------------------------------
/docs/guides/testing.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Testing Strategy
3 | ---
4 |
5 | # Testing Strategy
6 |
7 | Phase 9 established a comprehensive strategy with unit, integration, e2e, perf, and security tests.
8 |
9 | - Structure: see `/tests` directories by test type.
10 | - Runtimes: run both Node and Workers (Miniflare) where applicable.
11 | - Utilities: `_utils` helpers for HTTP, logging, and test servers.
12 |
13 | Commands
14 | - `npm run test` — run all tests
15 | - `npm run test:unit|integration|e2e|perf|security`
16 |
17 | See also: `docs/testing/phase-9-testing-architecture.md`.
18 |
19 |
```
--------------------------------------------------------------------------------
/docs/deployment/koyeb.md:
--------------------------------------------------------------------------------
```markdown
1 | # Deploy on Koyeb
2 |
3 | Use the CI-built image and the service manifest in `deploy/koyeb/koyeb.yaml`.
4 |
5 | ## Steps
6 |
7 | 1) Build and push an image to GHCR or Docker Hub
8 | 2) In Koyeb, create a new service from container image
9 | 3) Set environment variables and secrets:
10 | - `NODE_ENV=production`
11 | - `TOKEN_ENC_KEY` (secret)
12 | - `MASTER_BASE_URL` set to your Koyeb app URL
13 | 4) Configure autoscaling (see example manifest)
14 |
15 | The platform-provided `PORT` is mapped automatically to `MASTER_HOSTING_PORT` by `deploy/docker/entrypoint.sh`.
16 |
17 |
```
--------------------------------------------------------------------------------
/docs/getting-started/concepts.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Core Concepts
3 | ---
4 |
5 | # Core Concepts
6 |
7 | - Master OAuth: centralized OAuth for aggregated servers.
8 | - Delegated OAuth: per-server OAuth via delegation.
9 | - Module Loader: fetches and manages external MCP servers.
10 | - Request Router: registers routes and dispatches requests, with retries and circuit breaking.
11 | - Runtime Abstraction: Node vs Workers environment differences.
12 | - Config System: schema validation, secrets, and environment.
13 |
14 | Architecture references are available under `docs/architecture/phase*-architecture.md`.
15 |
16 |
```
--------------------------------------------------------------------------------
/docs/getting-started/quickstart-node.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Quickstart (Node)
3 | ---
4 |
5 | # Quickstart (Node)
6 |
7 | This minimal example runs Master MCP Server in Node, aggregating two servers.
8 |
9 | Steps
10 | 1. Create a config file (e.g., `config/master.yaml`) based on `examples/sample-configs/basic.yaml`.
11 | 2. Start the server: `npm run start`.
12 | 3. Connect your MCP client to the exposed endpoint.
13 |
14 | Highlights
15 | - Uses `src/runtime/node.ts` to bootstrap.
16 | - Validates config with `SchemaValidator`.
17 | - Routes requests via `RouteRegistry` and `RequestRouter`.
18 |
19 | See also: Examples → Basic Node Aggregator.
20 |
21 |
```
--------------------------------------------------------------------------------
/docs/configuration/overview.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Configuration Overview
3 | ---
4 |
5 | # Configuration Overview
6 |
7 | The Master MCP configuration is validated against a JSON Schema (`SchemaValidator`).
8 |
9 | Key Sections
10 | - `master_oauth`: top-level OAuth issuer/client settings
11 | - `hosting`: platform and runtime options
12 | - `logging`, `security`: operational controls
13 | - `routing`: load balancing, retries, circuit breakers
14 | - `servers[]`: aggregated MCP servers with per-entry config
15 |
16 | Start with examples in `/examples/sample-configs/*.yaml`.
17 | Generate the full reference with `npm run docs:config`.
18 |
19 |
```
--------------------------------------------------------------------------------
/docs/guides/configuration.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Configuration
3 | ---
4 |
5 | # Configuration Guide
6 |
7 | Centralized configuration with schema validation and secrets.
8 |
9 | - Types: `MasterConfig`, `HostingConfig`, `RoutingConfig`, `ServerConfig`, `SecurityConfig`.
10 | - Validation: `SchemaValidator` with built-in default schema.
11 | - Secrets: `SecretManager` decrypts protected values; keys from `SecurityConfig.config_key_env`.
12 | - Environments: `EnvironmentManager` to load/merge env vars.
13 |
14 | Commands
15 | - Generate reference from schema: `npm run docs:config`
16 |
17 | See: Configuration → Reference for full schema.
18 |
19 |
```
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "ESNext",
5 | "rootDir": "src",
6 | "strict": true,
7 | "noImplicitOverride": true,
8 | "noUnusedLocals": true,
9 | "noUnusedParameters": true,
10 | "noFallthroughCasesInSwitch": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "skipLibCheck": false,
13 | "sourceMap": true,
14 | "declaration": true,
15 | "declarationMap": true,
16 | "resolveJsonModule": true,
17 | "esModuleInterop": true,
18 | "moduleResolution": "NodeNext",
19 | "types": []
20 | },
21 | "include": ["src/**/*"],
22 | "exclude": ["node_modules", "dist", "tests"]
23 | }
24 |
25 |
```
--------------------------------------------------------------------------------
/docs/.vitepress/theme/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import DefaultTheme from 'vitepress/theme'
2 | import type { Theme } from 'vitepress'
3 | import CodeTabs from './components/CodeTabs.vue'
4 | import ConfigGenerator from './components/ConfigGenerator.vue'
5 | import AuthFlowDemo from './components/AuthFlowDemo.vue'
6 | import ApiPlayground from './components/ApiPlayground.vue'
7 | import './style.css'
8 |
9 | const theme: Theme = {
10 | ...DefaultTheme,
11 | enhanceApp({ app }) {
12 | app.component('CodeTabs', CodeTabs)
13 | app.component('ConfigGenerator', ConfigGenerator)
14 | app.component('AuthFlowDemo', AuthFlowDemo)
15 | app.component('ApiPlayground', ApiPlayground)
16 | },
17 | }
18 |
19 | export default theme
20 |
21 |
```
--------------------------------------------------------------------------------
/docs/api/index.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: API Overview
3 | ---
4 |
5 | # API Overview
6 |
7 | The API reference is generated from TypeScript sources using TypeDoc and the `typedoc-plugin-markdown` plugin.
8 |
9 | - Generated docs output: `/api/reference/`
10 | - Generation config: `typedoc.json`
11 | - Command: `npm run docs:api`
12 |
13 | Key entry points
14 | - `MasterServer` — main orchestration server
15 | - `ModuleLoader`, `RequestRouter`, `CapabilityAggregator` — module and routing
16 | - `MultiAuthManager`, `TokenManager` — authentication core
17 | - `FlowController` and related OAuth utilities — OAuth flows
18 | - `SchemaValidator`, `ConfigLoader` — configuration
19 |
20 | See `/api/reference/` for the full API.
21 |
22 |
```
--------------------------------------------------------------------------------
/docs/deployment/cloudflare-workers.md:
--------------------------------------------------------------------------------
```markdown
1 | # Deploy on Cloudflare Workers
2 |
3 | Configuration lives in `deploy/cloudflare/wrangler.toml`.
4 |
5 | ## Setup
6 |
7 | ```
8 | wrangler whoami
9 | wrangler login
10 | ```
11 |
12 | Set secrets:
13 |
14 | ```
15 | wrangler secret put TOKEN_ENC_KEY
16 | # Optional provider secrets as needed
17 | ```
18 |
19 | Bind KV for token persistence (optional but recommended):
20 |
21 | ```
22 | wrangler kv:namespace create TOKENS
23 | # Add binding to wrangler.toml and your environments
24 | ```
25 |
26 | ## Dev and Deploy
27 |
28 | ```
29 | wrangler dev
30 | wrangler deploy --env staging
31 | wrangler deploy --env production
32 | ```
33 |
34 | Ensure your config uses `hosting.platform=cloudflare-workers` (auto-detected) and `hosting.base_url` is set for OAuth redirects.
35 |
36 |
```
--------------------------------------------------------------------------------
/docs/examples/overview.md:
--------------------------------------------------------------------------------
```markdown
1 | # Examples Overview
2 |
3 | This directory groups runnable examples demonstrating common scenarios:
4 |
5 | - `examples/basic-node` — Minimal Node runtime with a local backend
6 | - `examples/oauth-node` — OAuth delegation using GitHub
7 | - `examples/multi-server` — Multiple instances, load balancing, retries, circuit breaker
8 | - `examples/custom-auth` — Custom `MultiAuthManager` that tweaks backend headers
9 | - `examples/performance` — Tuning routing for throughput and resilience
10 | - `examples/security-hardening` — Production environment configuration tips
11 |
12 | Run each by pointing `MASTER_CONFIG_PATH` and using `npm run dev`, unless a custom launcher is provided.
13 |
14 |
```
--------------------------------------------------------------------------------
/docs/troubleshooting/performance.md:
--------------------------------------------------------------------------------
```markdown
1 | # Performance Troubleshooting
2 |
3 | ## Symptoms
4 |
5 | - Elevated latency on tool calls
6 | - Increased error rates or timeouts
7 |
8 | ## Checks
9 |
10 | - Inspect `/metrics` (Node) and platform dashboards (Workers, Koyeb)
11 | - Verify backends’ `/health` and logs
12 | - Confirm load-balancing strategy is appropriate for your topology
13 |
14 | ## Tuning
15 |
16 | - Increase `retry.maxRetries` and `baseDelayMs` judiciously
17 | - Switch to `health` strategy and feed health scores when available
18 | - Tighten circuit breaker thresholds to fail fast on unhealthy instances
19 |
20 | ## Environment
21 |
22 | - Run Node with adequate CPU/memory; consider horizontal scaling
23 | - Use KV-backed tokens in Workers to reduce token cache misses
24 |
25 |
```
--------------------------------------------------------------------------------
/examples/oauth-node/config.yaml:
--------------------------------------------------------------------------------
```yaml
1 | hosting:
2 | platform: node
3 | port: 3000
4 |
5 | master_oauth:
6 | authorization_endpoint: https://example.com/oauth/authorize
7 | token_endpoint: https://example.com/oauth/token
8 | client_id: master-mcp
9 | redirect_uri: http://localhost:3000/oauth/callback
10 | scopes: [openid]
11 |
12 | servers:
13 | - id: github-tools
14 | type: local
15 | auth_strategy: delegate_oauth
16 | auth_config:
17 | provider: github
18 | authorization_endpoint: https://github.com/login/oauth/authorize
19 | token_endpoint: https://github.com/login/oauth/access_token
20 | client_id: ${GITHUB_CLIENT_ID}
21 | client_secret: env:GITHUB_CLIENT_SECRET
22 | scopes: [repo, read:user]
23 | config:
24 | port: 4100
25 |
26 |
```
--------------------------------------------------------------------------------
/deploy/cloudflare/wrangler.toml:
--------------------------------------------------------------------------------
```toml
1 | name = "master-mcp-server"
2 | main = "src/runtime/worker.ts"
3 | compatibility_date = "2024-07-01"
4 | workers_dev = true
5 |
6 | # Optional: enable Node compat if needed (try to keep off)
7 | # node_compat = false
8 |
9 | [vars]
10 | # Non-sensitive defaults; override per-env below or via dashboard
11 | MASTER_HOSTING_PLATFORM = "workers"
12 | LOG_LEVEL = "info"
13 | LOG_FORMAT = "json"
14 |
15 | [env.staging]
16 | name = "master-mcp-server-staging"
17 |
18 | [env.staging.vars]
19 | LOG_LEVEL = "debug"
20 |
21 | [env.production]
22 | name = "master-mcp-server-prod"
23 |
24 | [env.production.vars]
25 | LOG_LEVEL = "info"
26 |
27 | # Secrets to provision via: wrangler secret put TOKEN_ENC_KEY
28 | # - TOKEN_ENC_KEY
29 | # - Any OAuth client secrets required by configured backends
30 |
31 |
```
--------------------------------------------------------------------------------
/examples/advanced-routing/config.yaml:
--------------------------------------------------------------------------------
```yaml
1 | master_oauth:
2 | authorization_endpoint: https://example.com/auth
3 | token_endpoint: https://example.com/token
4 | client_id: example
5 | redirect_uri: http://localhost:3000/oauth/callback
6 | scopes: [openid, profile]
7 |
8 | hosting:
9 | platform: node
10 | port: 3000
11 |
12 | routing:
13 | loadBalancer:
14 | strategy: round_robin
15 | retry:
16 | maxRetries: 3
17 | baseDelayMs: 250
18 | maxDelayMs: 4000
19 | backoffFactor: 2
20 | jitter: full
21 | circuitBreaker:
22 | failureThreshold: 5
23 | successThreshold: 2
24 | recoveryTimeoutMs: 30000
25 |
26 | servers:
27 | - id: local-a
28 | type: local
29 | auth_strategy: bypass_auth
30 | config: {}
31 | - id: local-b
32 | type: local
33 | auth_strategy: bypass_auth
34 | config: {}
35 |
36 |
```
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { defineConfig } from 'vitest/config'
2 |
3 | export default defineConfig({
4 | test: {
5 | globals: true,
6 | include: ['tests/**/*.{test,spec}.ts'],
7 | exclude: ['tests/workers/**/*', 'dist', 'node_modules'],
8 | setupFiles: ['tests/_setup/vitest.setup.ts'],
9 | environment: 'node',
10 | coverage: {
11 | reporter: ['text', 'lcov', 'cobertura'],
12 | provider: 'v8',
13 | reportsDirectory: './coverage/node',
14 | all: true,
15 | include: ['src/**/*.ts'],
16 | exclude: ['src/runtime/worker.ts', 'src/types/**', 'src/**/index.ts'],
17 | thresholds: {
18 | lines: 0.85,
19 | functions: 0.85,
20 | branches: 0.8,
21 | statements: 0.85,
22 | },
23 | },
24 | },
25 | })
26 |
27 |
```
--------------------------------------------------------------------------------
/docs/getting-started/overview.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Overview
3 | ---
4 |
5 | # Overview
6 |
7 | Master MCP Server aggregates multiple MCP servers and exposes a unified endpoint with:
8 |
9 | - Authentication: Master OAuth, delegated OAuth, token management.
10 | - Module loading: Git, NPM, PyPI, Docker, local.
11 | - Request routing: load balancing, retries, circuit breakers.
12 | - Configuration: schema validation, secrets, environment management.
13 | - Runtimes: Node and Cloudflare Workers.
14 | - Testing and Deployment: comprehensive strategy with CI.
15 |
16 | Who is this for?
17 | - Developers integrating MCP into applications.
18 | - DevOps engineers deploying/managing the server.
19 | - System integrators configuring auth and routing.
20 |
21 | Continue with Installation and Quickstarts.
22 |
23 |
```
--------------------------------------------------------------------------------
/docs/troubleshooting/errors.md:
--------------------------------------------------------------------------------
```markdown
1 | # Error Reference
2 |
3 | ## Configuration validation failed
4 |
5 | The config did not conform to the schema. The error lists the path and reason. Fix the offending property and reload.
6 |
7 | ## CircuitOpenError
8 |
9 | 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.
10 |
11 | ## OAuthError
12 |
13 | An upstream OAuth provider response was invalid. Check client credentials, scopes, and redirect URIs.
14 |
15 | ## Token decryption failed
16 |
17 | 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.
18 |
19 |
```
--------------------------------------------------------------------------------
/docs/tutorials/cloudflare-workers-tutorial.md:
--------------------------------------------------------------------------------
```markdown
1 | # Tutorial: Cloudflare Workers
2 |
3 | Run Master MCP Server on Cloudflare Workers with the provided runtime.
4 |
5 | ## 1) Prerequisites
6 |
7 | - Cloudflare account
8 | - `wrangler` CLI
9 |
10 | ## 2) Configure
11 |
12 | Update `deploy/cloudflare/wrangler.toml` as needed. Secrets:
13 |
14 | ```
15 | wrangler secret put TOKEN_ENC_KEY
16 | ```
17 |
18 | If you need persistent token storage, bind a KV namespace named `TOKENS` and pass it via environment bindings.
19 |
20 | ## 3) Dev
21 |
22 | ```
23 | wrangler dev
24 | ```
25 |
26 | ## 4) Deploy
27 |
28 | ```
29 | wrangler deploy --env staging
30 | wrangler deploy --env production
31 | ```
32 |
33 | 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.
34 |
35 |
```
--------------------------------------------------------------------------------
/tests/unit/config.secret-manager.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import '../setup/test-setup.js'
2 | import test from 'node:test'
3 | import assert from 'node:assert/strict'
4 | import { SecretManager } from '../../src/config/secret-manager.js'
5 |
6 | test('SecretManager encrypt/decrypt and resolve env placeholders', () => {
7 | const sm = new SecretManager({ key: 'k123' })
8 | const enc = sm.encrypt('s3cr3t')
9 | assert.ok(sm.isEncrypted(enc))
10 | assert.equal(sm.decrypt(enc), 's3cr3t')
11 |
12 | process.env.MY_TOKEN = 'abc'
13 | const cfg = { a: enc, b: 'env:MY_TOKEN', c: { password: 'x' } }
14 | const resolved = sm.resolveSecrets(cfg)
15 | assert.equal(resolved.a, 's3cr3t')
16 | assert.equal(resolved.b, 'abc')
17 | const redacted = sm.redact(resolved)
18 | assert.equal(redacted.c.password, '***')
19 | })
20 |
21 |
```
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "hosting": {
3 | "platform": "node",
4 | "port": 3005,
5 | "base_url": "http://localhost:3005"
6 | },
7 | "master_oauth": {
8 | "authorization_endpoint": "https://github.com/login/oauth/authorize",
9 | "token_endpoint": "https://github.com/login/oauth/access_token",
10 | "client_id": "Ov23li2S7w1LYoM4yLJP",
11 | "client_secret": "9f2cebe77974976e5fe7125eb50edefa81ee5219",
12 | "redirect_uri": "http://localhost:3005/oauth/callback",
13 | "scopes": ["read:user", "user:email"]
14 | },
15 | "servers": [
16 | {
17 | "id": "test-server",
18 | "type": "local",
19 | "url": "http://localhost:3006",
20 | "auth_strategy": "bypass_auth",
21 | "config": {
22 | "environment": {},
23 | "args": []
24 | }
25 | }
26 | ]
27 | }
```
--------------------------------------------------------------------------------
/tests/utils/token-storages.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { TokenStorage } from '../../src/auth/token-manager.js'
2 |
3 | export class MemoryKVStorage implements TokenStorage {
4 | private kv = new Map<string,string>()
5 | async set(key: string, value: string) { this.kv.set(key, value) }
6 | async get(key: string) { return this.kv.get(key) }
7 | async delete(key: string) { this.kv.delete(key) }
8 | async *entries() { for (const e of this.kv.entries()) yield e }
9 | }
10 |
11 | export class RedisLikeStorage implements TokenStorage {
12 | private map = new Map<string, string>()
13 | async set(key: string, value: string) { this.map.set(key, value) }
14 | async get(key: string) { return this.map.get(key) }
15 | async delete(key: string) { this.map.delete(key) }
16 | async *entries() { for (const e of this.map.entries()) yield e }
17 | }
18 |
19 |
```
--------------------------------------------------------------------------------
/docs/tutorials/beginner-getting-started.md:
--------------------------------------------------------------------------------
```markdown
1 | # Tutorial: Beginner Setup
2 |
3 | Goal: Run Master MCP Server with a single local backend and call a tool.
4 |
5 | ## 1) Install and Start
6 |
7 | ```
8 | npm ci
9 | cp .env.example .env
10 | npm run dev
11 | ```
12 |
13 | ## 2) Add a Backend
14 |
15 | Create `examples/basic-node/config.yaml` (already provided) and run with:
16 |
17 | ```
18 | MASTER_CONFIG_PATH=examples/basic-node/config.yaml npm run dev
19 | ```
20 |
21 | ## 3) Verify Health and Capabilities
22 |
23 | ```
24 | curl http://localhost:3000/health
25 | curl -X POST http://localhost:3000/mcp/tools/list -H 'content-type: application/json' -d '{}'
26 | ```
27 |
28 | ## 4) Call a Tool
29 |
30 | ```
31 | curl -X POST http://localhost:3000/mcp/tools/call \
32 | -H 'content-type: application/json' \
33 | -d '{"name":"tools.echo","arguments":{"text":"hello"}}'
34 | ```
35 |
36 | Replace `tools.echo` with a tool exposed by your backend.
37 |
38 |
```
--------------------------------------------------------------------------------
/examples/custom-auth/config.yaml:
--------------------------------------------------------------------------------
```yaml
1 | hosting:
2 | platform: node
3 | port: 3000
4 |
5 | master_oauth:
6 | authorization_endpoint: https://example.com/oauth/authorize
7 | token_endpoint: https://example.com/oauth/token
8 | client_id: master-mcp
9 | redirect_uri: http://localhost:3000/oauth/callback
10 | scopes: [openid]
11 |
12 | servers:
13 | - id: public-tools
14 | type: local
15 | auth_strategy: bypass_auth
16 | config:
17 | port: 4200
18 |
19 | - id: custom-proxy
20 | type: local
21 | auth_strategy: proxy_oauth
22 | auth_config:
23 | provider: google
24 | authorization_endpoint: https://accounts.google.com/o/oauth2/v2/auth
25 | token_endpoint: https://oauth2.googleapis.com/token
26 | client_id: ${GOOGLE_CLIENT_ID}
27 | client_secret: env:GOOGLE_CLIENT_SECRET
28 | scopes: [openid, profile, email]
29 | config:
30 | port: 4201
31 |
32 |
```
--------------------------------------------------------------------------------
/deploy/koyeb/koyeb.yaml:
--------------------------------------------------------------------------------
```yaml
1 | version: v1
2 |
3 | services:
4 | - name: master-mcp
5 | type: web
6 | ports:
7 | - port: 80
8 | protocol: http
9 | instance_types:
10 | - micro
11 | min_instances: 2
12 | max_instances: 10
13 | scaling:
14 | metric: cpu
15 | target: 70
16 | stabilization_window: 60
17 | health_check:
18 | path: /health
19 | interval: 10s
20 | timeout: 3s
21 | unhealthy_threshold: 3
22 | healthy_threshold: 1
23 | docker:
24 | image: ghcr.io/OWNER/REPO:latest # replaced by CI/CD
25 | env:
26 | - key: NODE_ENV
27 | value: production
28 | - key: LOG_FORMAT
29 | value: json
30 | - key: LOG_LEVEL
31 | value: info
32 | # Koyeb provides PORT; entrypoint maps it to hosting.port
33 | - key: TOKEN_ENC_KEY
34 | value: ${TOKEN_ENC_KEY} # set via Koyeb secrets
35 |
36 |
```
--------------------------------------------------------------------------------
/vitest.worker.config.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { defineConfig } from 'vitest/config'
2 |
3 | export default defineConfig({
4 | test: {
5 | globals: true,
6 | include: ['tests/workers/**/*.{test,spec}.ts'],
7 | setupFiles: ['tests/_setup/miniflare.setup.ts'],
8 | environment: 'miniflare',
9 | environmentOptions: {
10 | modules: true,
11 | script: undefined, // tests import modules directly
12 | bindings: {},
13 | kvNamespaces: [],
14 | durableObjects: {},
15 | },
16 | coverage: {
17 | reporter: ['text', 'lcov'],
18 | provider: 'v8',
19 | reportsDirectory: './coverage/workers',
20 | all: true,
21 | include: ['src/**/*.ts'],
22 | exclude: ['src/runtime/node.ts', 'src/types/**'],
23 | thresholds: {
24 | lines: 0.8,
25 | functions: 0.8,
26 | branches: 0.75,
27 | statements: 0.8,
28 | },
29 | },
30 | },
31 | })
32 |
33 |
```
--------------------------------------------------------------------------------
/tests/unit/modules/stdio-capability-discovery.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { test } from 'node:test'
2 | import assert from 'node:assert'
3 | import { StdioCapabilityDiscovery } from '../../../src/modules/stdio-capability-discovery.js'
4 | import { StdioManager } from '../../../src/modules/stdio-manager.js'
5 |
6 | test('StdioCapabilityDiscovery should instantiate correctly', async () => {
7 | const manager = new StdioManager()
8 | const discovery = new StdioCapabilityDiscovery(manager)
9 | assert.ok(discovery)
10 | })
11 |
12 | test('StdioCapabilityDiscovery should have required methods', async () => {
13 | const manager = new StdioManager()
14 | const discovery = new StdioCapabilityDiscovery(manager)
15 | assert.strictEqual(typeof discovery.discoverCapabilities, 'function')
16 | assert.strictEqual(typeof discovery.callTool, 'function')
17 | assert.strictEqual(typeof discovery.readResource, 'function')
18 | })
```
--------------------------------------------------------------------------------
/docs/configuration/performance.md:
--------------------------------------------------------------------------------
```markdown
1 | # Performance & Tuning
2 |
3 | Tune routing and runtime to handle your workload.
4 |
5 | ## Routing
6 |
7 | - Prefer `health` strategy to route to instances with better health scores (when provided)
8 | - Increase `retry.maxRetries` for flaky networks, but cap `maxDelayMs`
9 | - Use `jitter: full` to avoid thundering herds
10 | - Adjust `circuitBreaker` thresholds based on observed upstream reliability
11 |
12 | ## Node Runtime
13 |
14 | - Use `NODE_ENV=production`
15 | - Run behind a reverse proxy (nginx, Cloudflare) for TLS termination
16 | - Set `LOG_LEVEL=info` or `warn`
17 |
18 | ## Workers Runtime
19 |
20 | - Bind KV storage for token persistence to avoid in-memory losses across isolates
21 | - Avoid large responses; stream when possible
22 |
23 | ## Observability
24 |
25 | - Use `/metrics` to scrape basic system stats in Node
26 | - Add platform logs/metrics (Cloudflare, Koyeb dashboards)
27 |
28 |
```
--------------------------------------------------------------------------------
/examples/multi-server/config.yaml:
--------------------------------------------------------------------------------
```yaml
1 | hosting:
2 | platform: node
3 | port: 3000
4 |
5 | master_oauth:
6 | authorization_endpoint: https://example.com/oauth/authorize
7 | token_endpoint: https://example.com/oauth/token
8 | client_id: master-mcp
9 | redirect_uri: http://localhost:3000/oauth/callback
10 | scopes: [openid]
11 |
12 | routing:
13 | loadBalancer:
14 | strategy: health
15 | circuitBreaker:
16 | failureThreshold: 3
17 | successThreshold: 2
18 | recoveryTimeoutMs: 10000
19 | retry:
20 | maxRetries: 3
21 | baseDelayMs: 200
22 | maxDelayMs: 3000
23 | backoffFactor: 2
24 | jitter: full
25 | retryOn:
26 | networkErrors: true
27 | httpStatuses: [408, 429]
28 | httpStatusClasses: [5]
29 |
30 | servers:
31 | - id: compute
32 | type: local
33 | auth_strategy: bypass_auth
34 | config:
35 | port: 4101
36 | - id: compute
37 | type: local
38 | auth_strategy: bypass_auth
39 | config:
40 | port: 4102
41 |
42 |
```
--------------------------------------------------------------------------------
/deploy/docker/docker-compose.yml:
--------------------------------------------------------------------------------
```yaml
1 | version: '3.9'
2 |
3 | services:
4 | master-mcp:
5 | build:
6 | context: ../..
7 | dockerfile: deploy/docker/Dockerfile
8 | args:
9 | - NODE_VERSION=20
10 | image: master-mcp:dev
11 | environment:
12 | - NODE_ENV=development
13 | - LOG_LEVEL=debug
14 | - MASTER_HOSTING_PLATFORM=node
15 | - MASTER_BASE_URL=http://localhost:3000
16 | # TOKEN_ENC_KEY is recommended even in dev to persist tokens across restarts
17 | - TOKEN_ENC_KEY=dev-dev-dev-dev-dev-dev-dev-dev
18 | ports:
19 | - "3000:3000"
20 | volumes:
21 | - ../../config:/app/config:ro
22 | healthcheck:
23 | 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))"]
24 | interval: 5s
25 | timeout: 3s
26 | retries: 20
27 | start_period: 10s
28 |
29 |
```
--------------------------------------------------------------------------------
/docs/troubleshooting/common-issues.md:
--------------------------------------------------------------------------------
```markdown
1 | # Troubleshooting: Common Issues
2 |
3 | ## Config validation failed
4 |
5 | - Error shows `<path>: <reason>` from `SchemaValidator`
6 | - Verify your file is valid JSON/YAML
7 | - Check required fields under `master_oauth`, `hosting`, `servers`
8 |
9 | ## Missing TOKEN_ENC_KEY in production
10 |
11 | - Set `TOKEN_ENC_KEY` to a strong random string
12 | - In development, an ephemeral key is generated with a warning
13 |
14 | ## OAuth callback mismatch
15 |
16 | - Ensure `master_oauth.redirect_uri` matches your runtime base URL
17 | - If behind a proxy, set `MASTER_BASE_URL` to the external URL
18 |
19 | ## 401 Unauthorized to backends
20 |
21 | - Ensure client token is valid or delegated tokens are stored
22 | - For delegated flows, complete `/oauth/authorize` and `/oauth/callback` first
23 |
24 | ## Workers runtime odd redirects
25 |
26 | - Always set `hosting.base_url` in Workers to generate correct absolute URLs
27 |
28 |
```
--------------------------------------------------------------------------------
/examples/performance/config.yaml:
--------------------------------------------------------------------------------
```yaml
1 | hosting:
2 | platform: node
3 | port: 3000
4 |
5 | master_oauth:
6 | authorization_endpoint: https://example.com/oauth/authorize
7 | token_endpoint: https://example.com/oauth/token
8 | client_id: master-mcp
9 | redirect_uri: http://localhost:3000/oauth/callback
10 | scopes: [openid]
11 |
12 | logging:
13 | level: info
14 |
15 | routing:
16 | loadBalancer:
17 | strategy: weighted
18 | retry:
19 | maxRetries: 4
20 | baseDelayMs: 100
21 | maxDelayMs: 1500
22 | backoffFactor: 1.8
23 | jitter: full
24 | retryOn:
25 | networkErrors: true
26 | httpStatuses: [408, 429]
27 | httpStatusClasses: [5]
28 | circuitBreaker:
29 | failureThreshold: 4
30 | successThreshold: 2
31 | recoveryTimeoutMs: 8000
32 |
33 | servers:
34 | - id: compute
35 | type: local
36 | auth_strategy: bypass_auth
37 | config:
38 | port: 4301
39 | - id: compute
40 | type: local
41 | auth_strategy: bypass_auth
42 | config:
43 | port: 4302
44 |
45 |
```
--------------------------------------------------------------------------------
/tests/setup/test-setup.ts:
--------------------------------------------------------------------------------
```typescript
1 | // Global test setup for Node test runner with ts-node ESM
2 | // - Configures logger to reduce noise
3 | // - Provides minimal polyfills and deterministic behavior where helpful
4 |
5 | import { Logger } from '../../src/utils/logger.js'
6 |
7 | // Quiet logs during tests unless DEBUG is set
8 | Logger.configure({ level: (process.env.DEBUG ? 'debug' : 'error') as any, json: false })
9 |
10 | // Ensure process.env defaults for tests
11 | process.env.NODE_ENV = process.env.NODE_ENV || 'test'
12 |
13 | // Deterministic Math.random for some tests (can be overridden locally)
14 | const seed = 42
15 | let state = seed
16 | const origRandom = Math.random
17 | globalThis.Math.random = () => {
18 | if (process.env.TEST_NON_DETERMINISTIC === '1') return origRandom()
19 | state = (1103515245 * state + 12345) % 0x100000000
20 | return state / 0x100000000
21 | }
22 |
23 | // Export nothing; imported by tests as side-effect
24 | export {}
25 |
26 |
```
--------------------------------------------------------------------------------
/tests/_utils/log-capture.ts:
--------------------------------------------------------------------------------
```typescript
1 | export class LogCapture {
2 | private originalLog = console.log
3 | private originalError = console.error
4 | private logs: string[] = []
5 | private errors: string[] = []
6 |
7 | start(): void {
8 | console.log = (...args: any[]) => {
9 | try { this.logs.push(args.map(String).join(' ')) } catch {}
10 | this.originalLog.apply(console, args as any)
11 | }
12 | console.error = (...args: any[]) => {
13 | try { this.errors.push(args.map(String).join(' ')) } catch {}
14 | this.originalError.apply(console, args as any)
15 | }
16 | }
17 |
18 | stop(): void {
19 | console.log = this.originalLog
20 | console.error = this.originalError
21 | }
22 |
23 | find(substr: string): boolean {
24 | return this.logs.concat(this.errors).some((l) => l.includes(substr))
25 | }
26 |
27 | dump(): { logs: string[]; errors: string[] } {
28 | return { logs: [...this.logs], errors: [...this.errors] }
29 | }
30 | }
31 |
32 |
```
--------------------------------------------------------------------------------
/src/types/auth.ts:
--------------------------------------------------------------------------------
```typescript
1 | export type AuthHeaders = Record<string, string>
2 |
3 | export interface ClientInfo {
4 | client_id?: string
5 | redirect_uri?: string
6 | scopes?: string[]
7 | metadata?: Record<string, unknown>
8 | }
9 |
10 | export interface OAuthDelegation {
11 | type: 'oauth_delegation'
12 | auth_endpoint: string
13 | token_endpoint: string
14 | client_info: ClientInfo
15 | required_scopes: string[]
16 | redirect_after_auth: boolean
17 | }
18 |
19 | export interface OAuthToken {
20 | access_token: string
21 | refresh_token?: string
22 | expires_at: number // epoch millis
23 | scope: string[]
24 | user_info?: unknown
25 | }
26 |
27 | export interface AuthInfo {
28 | type: 'bearer'
29 | token: string
30 | }
31 |
32 | export interface TokenValidationResult {
33 | valid: boolean
34 | expiresAt?: number
35 | scopes?: string[]
36 | error?: string
37 | }
38 |
39 | export interface UserInfo {
40 | id: string
41 | name?: string
42 | email?: string
43 | avatarUrl?: string
44 | [key: string]: unknown
45 | }
46 |
47 |
```
--------------------------------------------------------------------------------
/static/oauth/style.css:
--------------------------------------------------------------------------------
```css
1 | /* Minimal, responsive styles for OAuth pages */
2 | :root { --bg: #0b0d10; --fg: #e8edf2; --muted: #a9b3bd; --accent: #52b788; }
3 | * { box-sizing: border-box; }
4 | html, body { height: 100%; }
5 | body {
6 | margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, Noto Sans, "Apple Color Emoji", "Segoe UI Emoji";
7 | background: var(--bg); color: var(--fg);
8 | display: grid; place-items: center;
9 | }
10 | .container { width: 100%; max-width: 640px; padding: 24px; }
11 | h1 { font-size: 1.5rem; margin: 0 0 0.5rem; }
12 | p { color: var(--muted); line-height: 1.5; }
13 | .button {
14 | display: inline-block; background: var(--accent); color: #02120c; font-weight: 600;
15 | padding: 10px 14px; border-radius: 8px; text-decoration: none; margin-top: 12px;
16 | }
17 | .error h1 { color: #ff8692; }
18 | @media (max-width: 480px) { .container { padding: 16px; } }
19 |
20 |
```
--------------------------------------------------------------------------------
/tests/servers/test-debug.js:
--------------------------------------------------------------------------------
```javascript
1 | import { MultiAuthManager } from '../../src/auth/multi-auth-manager.js'
2 | import { AuthStrategy } from '../../src/types/config.js'
3 |
4 | const masterCfg = {
5 | authorization_endpoint: 'http://localhost/auth',
6 | token_endpoint: 'http://localhost/token',
7 | client_id: 'master',
8 | redirect_uri: 'http://localhost/cb',
9 | scopes: ['openid'],
10 | }
11 |
12 | try {
13 | console.log('Creating MultiAuthManager...')
14 | const mam = new MultiAuthManager(masterCfg)
15 | console.log('MultiAuthManager created successfully')
16 |
17 | console.log('Registering server auth...')
18 | mam.registerServerAuth('srv1', AuthStrategy.MASTER_OAUTH)
19 | console.log('Server auth registered')
20 |
21 | console.log('Preparing auth for backend...')
22 | const h = await mam.prepareAuthForBackend('srv1', 'CLIENT')
23 | console.log('Result:', h)
24 |
25 | } catch (error) {
26 | console.error('Error:', error)
27 | console.error('Stack:', error.stack)
28 | }
```
--------------------------------------------------------------------------------
/tests/utils/oauth-mocks.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { createMockServer, MockServer } from './mock-http.js'
2 |
3 | export async function createGitHubMock(): Promise<MockServer> {
4 | return createMockServer([
5 | { method: 'GET', path: '/user', handler: () => ({ headers: { 'x-oauth-scopes': 'read:user, repo' }, body: { id: 1, login: 'octocat' } }) },
6 | ])
7 | }
8 |
9 | export async function createGoogleMock(): Promise<MockServer> {
10 | return createMockServer([
11 | { method: 'GET', path: '/userinfo', handler: () => ({ body: { sub: '123', name: 'Alice', email: '[email protected]', picture: 'http://x' } }) },
12 | ])
13 | }
14 |
15 | export async function createCustomOIDCMock(): Promise<MockServer> {
16 | return createMockServer([
17 | { method: 'POST', path: '/token', handler: () => ({ body: { access_token: 'AT', expires_in: 3600, scope: 'openid' } }) },
18 | { method: 'GET', path: '/userinfo', handler: () => ({ body: { sub: 'abc', name: 'Bob' } }) },
19 | ])
20 | }
21 |
22 |
```
--------------------------------------------------------------------------------
/tests/integration/modules.module-loader-health.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import '../setup/test-setup.js'
2 | import test from 'node:test'
3 | import assert from 'node:assert/strict'
4 | import { DefaultModuleLoader } from '../../src/modules/module-loader.js'
5 | import { createMockServer } from '../utils/mock-http.js'
6 |
7 | test('DefaultModuleLoader performHealthCheck against /health JSON', async () => {
8 | const srv = await createMockServer([
9 | { method: 'GET', path: '/health', handler: () => ({ body: { ok: true } }) },
10 | ])
11 | try {
12 | const loader = new DefaultModuleLoader()
13 | const ls: any = { id: 'a', type: 'node', endpoint: srv.url, config: {} as any, status: 'starting', lastHealthCheck: 0 }
14 | const ok = await loader.performHealthCheck(ls)
15 | assert.equal(ok, true)
16 | // performHealthCheck doesn't update status, that's done by the load method
17 | assert(ls.lastHealthCheck > 0, 'lastHealthCheck should be updated')
18 | } finally {
19 | await srv.close()
20 | }
21 | })
22 |
23 |
```
--------------------------------------------------------------------------------
/docs/deployment/docker.md:
--------------------------------------------------------------------------------
```markdown
1 | # Deploy with Docker
2 |
3 | Two Docker setups are provided:
4 |
5 | 1) Top-level `Dockerfile` (multi-stage) and `docker-compose.yml` for local dev
6 | 2) `deploy/docker/Dockerfile` optimized for CI-built images and Koyeb
7 |
8 | ## Local Development
9 |
10 | ```
11 | docker compose up --build
12 | ```
13 |
14 | This uses the dev target with hot reload (`nodemon`) and maps your working directory into the container.
15 |
16 | ## Production Image (CI)
17 |
18 | Build and push an image:
19 |
20 | ```
21 | docker build -f deploy/docker/Dockerfile -t ghcr.io/OWNER/REPO:latest .
22 | docker push ghcr.io/OWNER/REPO:latest
23 | ```
24 |
25 | Run:
26 |
27 | ```
28 | docker run -p 3000:3000 \
29 | -e NODE_ENV=production \
30 | -e TOKEN_ENC_KEY=... \
31 | -e MASTER_BASE_URL=https://your.domain \
32 | ghcr.io/OWNER/REPO:latest
33 | ```
34 |
35 | ## Environment
36 |
37 | - Set `TOKEN_ENC_KEY` in production
38 | - Set `MASTER_BASE_URL` if serving behind a proxy to ensure correct OAuth redirects
39 | - Inject `MASTER_OAUTH_CLIENT_SECRET` and other provider secrets via env
40 |
41 |
```
--------------------------------------------------------------------------------
/src/types/server.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { ToolDefinition, ResourceDefinition, PromptDefinition } from './mcp.js'
2 | import type { ServerConfig } from './config.js'
3 |
4 | export type ServerType = 'python' | 'node' | 'typescript' | 'stdio' | 'unknown'
5 |
6 | export interface ServerProcess {
7 | pid?: number
8 | port?: number
9 | url?: string
10 | stop: () => Promise<void>
11 | }
12 |
13 | export interface ServerCapabilities {
14 | tools: ToolDefinition[]
15 | resources: ResourceDefinition[]
16 | prompts?: PromptDefinition[]
17 | }
18 |
19 | export interface LoadedServer {
20 | id: string
21 | type: ServerType
22 | process?: ServerProcess
23 | endpoint: string
24 | config: ServerConfig
25 | capabilities?: ServerCapabilities
26 | status: 'starting' | 'running' | 'stopped' | 'error'
27 | lastHealthCheck: number
28 | // Optional: when a server has multiple deploys/instances
29 | instances?: ServerInstance[]
30 | }
31 |
32 | export interface ServerInstance {
33 | id: string
34 | url: string
35 | weight?: number
36 | healthScore?: number // 0..100, used by health-based LB
37 | }
38 |
```
--------------------------------------------------------------------------------
/docs/guides/module-loading.md:
--------------------------------------------------------------------------------
```markdown
1 | # Module Loading & Capability Aggregation
2 |
3 | Master MCP loads backend servers from multiple sources and aggregates their capabilities.
4 |
5 | ## Sources
6 |
7 | - `local`: A locally running server exposing HTTP endpoints
8 | - `git`, `npm`, `pypi`, `docker`: Stubs for different origins; endpoint resolution is config-driven (e.g., `config.port` or `url`).
9 |
10 | Example server block:
11 |
12 | ```yaml
13 | servers:
14 | - id: search
15 | type: local
16 | auth_strategy: master_oauth
17 | config:
18 | port: 4100
19 | ```
20 |
21 | ## Health Checks
22 |
23 | `DefaultModuleLoader` pings each server’s `/health` endpoint when loading to set an initial status (`running` or `error`).
24 |
25 | ## Capability Aggregation
26 |
27 | `CapabilityAggregator` discovers tools and resources via:
28 |
29 | - `GET /capabilities` (optional if provided by backend)
30 | - `POST /mcp/tools/list`
31 | - `POST /mcp/resources/list`
32 |
33 | Capabilities can be prefixed by server id (default) to avoid naming conflicts. Use the aggregated names in requests, e.g., `serverId.toolName`.
34 |
35 |
```
--------------------------------------------------------------------------------
/docs/guides/request-routing.md:
--------------------------------------------------------------------------------
```markdown
1 | # Request Routing & Resilience
2 |
3 | Requests are routed by `RequestRouter`, which uses a `RouteRegistry`, `LoadBalancer`, `RetryHandler`, and `CircuitBreaker` to provide resilient upstream calls.
4 |
5 | ## Endpoints
6 |
7 | - Tools: `POST /mcp/tools/call` with `{ name, arguments }`
8 | - Resources: `POST /mcp/resources/read` with `{ uri }`
9 |
10 | Names and URIs may be prefixed by server id when aggregated.
11 |
12 | ## Load Balancing
13 |
14 | Configure strategy under `routing.loadBalancer.strategy`:
15 |
16 | - `round_robin` (default)
17 | - `weighted`
18 | - `health`
19 |
20 | ## Retries
21 |
22 | `routing.retry` controls attempts with backoff and jitter:
23 |
24 | - `maxRetries`, `baseDelayMs`, `maxDelayMs`, `backoffFactor`, `jitter` (`full` or `none`)
25 | - `retryOn.httpStatuses`, `retryOn.httpStatusClasses`, `retryOn.networkErrors`
26 |
27 | ## Circuit Breaker
28 |
29 | `routing.circuitBreaker` manages failure thresholds and recovery:
30 |
31 | - `failureThreshold`, `successThreshold`, `recoveryTimeoutMs`
32 |
33 | When open, requests fail fast with a retry-after hint.
34 |
35 |
```
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
```yaml
1 | version: "3.9"
2 |
3 | services:
4 | master-mcp:
5 | build:
6 | context: .
7 | target: dev
8 | image: master-mcp-server:dev
9 | container_name: master-mcp-dev
10 | ports:
11 | - "3000:3000"
12 | environment:
13 | - NODE_ENV=development
14 | - MASTER_ENV=development
15 | - MASTER_HOSTING_PLATFORM=node
16 | - MASTER_HOSTING_PORT=3000
17 | # Uncomment and set for local secrets
18 | # - MASTER_OAUTH_CLIENT_SECRET=${MASTER_OAUTH_CLIENT_SECRET}
19 | # - TOKEN_ENC_KEY=${TOKEN_ENC_KEY}
20 | env_file:
21 | - .env
22 | volumes:
23 | - ./:/app
24 | - /app/node_modules
25 | healthcheck:
26 | test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/health"]
27 | interval: 10s
28 | timeout: 3s
29 | retries: 3
30 | start_period: 20s
31 | security_opt:
32 | - no-new-privileges:true
33 | cap_drop:
34 | - ALL
35 | deploy:
36 | resources:
37 | limits:
38 | cpus: "0.75"
39 | memory: 768M
40 | reservations:
41 | cpus: "0.25"
42 | memory: 256M
43 |
44 |
```
--------------------------------------------------------------------------------
/tests/_utils/mock-fetch.ts:
--------------------------------------------------------------------------------
```typescript
1 | // Lightweight Node fetch mocking via Undici MockAgent (Node 18)
2 | // Falls back to no-op if Undici not available.
3 |
4 | type RemoveFn = () => void
5 |
6 | export function withMockFetch(routes: Array<{ method: string; url: RegExp | string; reply: (body?: any) => any }>): RemoveFn {
7 | try {
8 | // eslint-disable-next-line @typescript-eslint/no-var-requires
9 | const undici = require('undici')
10 | const agent = new undici.MockAgent()
11 | agent.disableNetConnect()
12 | const pool = agent.get('http://localhost')
13 | for (const r of routes) {
14 | const method = r.method.toUpperCase()
15 | const matcher = typeof r.url === 'string' ? new RegExp(r.url.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) : r.url
16 | pool.intercept({ path: matcher, method }).reply(200, (_opts: any) => r.reply(_opts?.body))
17 | }
18 | undici.setGlobalDispatcher(agent)
19 | return () => undici.setGlobalDispatcher(new undici.Agent())
20 | } catch {
21 | // No undici; do nothing.
22 | return () => void 0
23 | }
24 | }
25 |
26 |
```
--------------------------------------------------------------------------------
/tests/unit/config.environment-manager.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import '../setup/test-setup.js'
2 | import test from 'node:test'
3 | import assert from 'node:assert/strict'
4 | import { EnvironmentManager } from '../../src/config/environment-manager.js'
5 |
6 | test('EnvironmentManager parseCliArgs dotted keys', () => {
7 | const orig = process.argv
8 | process.argv = ['node', 'script', '--hosting.port=4001', '--logging.level=debug', '--config-path=./x.json']
9 | try {
10 | const parsed = EnvironmentManager.parseCliArgs()
11 | assert.equal((parsed as any).hosting.port, 4001)
12 | assert.equal((parsed as any).logging.level, 'debug')
13 | assert.equal((parsed as any).configPath, './x.json')
14 | } finally {
15 | process.argv = orig
16 | }
17 | })
18 |
19 | test('EnvironmentManager loadEnvOverrides maps vars', () => {
20 | process.env.MASTER_HOSTING_PORT = '1234'
21 | process.env.MASTER_OAUTH_SCOPES = 'a,b'
22 | const ov = EnvironmentManager.loadEnvOverrides()
23 | // @ts-ignore
24 | assert.equal(ov.hosting?.port, 1234)
25 | // @ts-ignore
26 | assert.deepEqual(ov.master_oauth?.scopes, ['a','b'])
27 | })
28 |
29 |
```
--------------------------------------------------------------------------------
/tests/integration/modules.capability-aggregator.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import '../setup/test-setup.js'
2 | import test from 'node:test'
3 | import assert from 'node:assert/strict'
4 | import { CapabilityAggregator } from '../../src/modules/capability-aggregator.js'
5 | import { createMockServer } from '../utils/mock-http.js'
6 |
7 | test('CapabilityAggregator discovers via /capabilities', async () => {
8 | const caps = {
9 | tools: [{ name: 't1' }],
10 | resources: [{ uri: 'r1' }],
11 | }
12 | const srv = await createMockServer([
13 | { method: 'GET', path: '/capabilities', handler: () => ({ body: caps }) },
14 | ])
15 | try {
16 | const servers = new Map<string, any>([[
17 | 's1', { id: 's1', type: 'node', endpoint: srv.url, config: {} as any, status: 'running', lastHealthCheck: 0 }
18 | ]])
19 | const ag = new CapabilityAggregator()
20 | await ag.discoverCapabilities(servers as any)
21 | const tools = ag.getAllTools(servers as any)
22 | assert.equal(tools[0].name, 's1.t1')
23 | const map = ag.getMappingForTool('s1.t1')
24 | assert.equal(map?.originalName, 't1')
25 | } finally {
26 | await srv.close()
27 | }
28 | })
29 |
30 |
```
--------------------------------------------------------------------------------
/docs/api/interfaces/RunningServer.md:
--------------------------------------------------------------------------------
```markdown
1 | [**master-mcp-server**](../README.md)
2 |
3 | ***
4 |
5 | # Interface: RunningServer
6 |
7 | Defined in: [index.ts:7](https://github.com/solita-internal/master-mcp-server/blob/cd13e0009f7a1b7f244de882dc738bbf1f90f2c2/src/index.ts#L7)
8 |
9 | ## Properties
10 |
11 | ### container
12 |
13 | > **container**: `DependencyContainer`
14 |
15 | Defined in: [index.ts:10](https://github.com/solita-internal/master-mcp-server/blob/cd13e0009f7a1b7f244de882dc738bbf1f90f2c2/src/index.ts#L10)
16 |
17 | ***
18 |
19 | ### name
20 |
21 | > **name**: `string`
22 |
23 | Defined in: [index.ts:8](https://github.com/solita-internal/master-mcp-server/blob/cd13e0009f7a1b7f244de882dc738bbf1f90f2c2/src/index.ts#L8)
24 |
25 | ***
26 |
27 | ### stop()
28 |
29 | > **stop**: () => `Promise`\<`void`\>
30 |
31 | Defined in: [index.ts:11](https://github.com/solita-internal/master-mcp-server/blob/cd13e0009f7a1b7f244de882dc738bbf1f90f2c2/src/index.ts#L11)
32 |
33 | #### Returns
34 |
35 | `Promise`\<`void`\>
36 |
37 | ***
38 |
39 | ### version
40 |
41 | > **version**: `string`
42 |
43 | Defined in: [index.ts:9](https://github.com/solita-internal/master-mcp-server/blob/cd13e0009f7a1b7f244de882dc738bbf1f90f2c2/src/index.ts#L9)
44 |
```
--------------------------------------------------------------------------------
/tests/unit/utils.monitoring.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import '../setup/test-setup.js'
2 | import test from 'node:test'
3 | import assert from 'node:assert/strict'
4 | import { MetricRegistry, HealthCheckRegistry, monitorEventLoopLag } from '../../src/utils/monitoring.js'
5 |
6 | test('MetricRegistry counters/gauges/histograms', () => {
7 | const R = new MetricRegistry()
8 | R.counter('c').inc()
9 | R.gauge('g').set(5)
10 | R.histogram('h').observe(0.02)
11 | const snap = R.list()
12 | assert.equal(snap.counters.c, 1)
13 | assert.equal(snap.gauges.g, 5)
14 | assert.ok(Array.isArray(snap.histograms.h.counts))
15 | })
16 |
17 | test('HealthCheckRegistry aggregates ok/degraded', async () => {
18 | const H = new HealthCheckRegistry()
19 | H.register('ok', async () => ({ ok: true }))
20 | H.register('bad', async () => ({ ok: false, info: 'x' }))
21 | const res = await H.run()
22 | assert.equal(res.status, 'degraded')
23 | })
24 |
25 | test('monitorEventLoopLag provides callback and stopper', async () => {
26 | let called = 0
27 | const stop = monitorEventLoopLag(() => { called++ }, 5)
28 | await new Promise((r) => setTimeout(r, 20))
29 | stop()
30 | assert.ok(called >= 1)
31 | })
32 |
33 |
```
--------------------------------------------------------------------------------
/tests/unit/stdio-capability-discovery.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { test } from 'node:test'
2 | import assert from 'node:assert'
3 | import { StdioCapabilityDiscovery } from '../src/modules/stdio-capability-discovery.js'
4 |
5 | test('StdioCapabilityDiscovery should discover capabilities from a STDIO server', async () => {
6 | // This test would require a running STDIO server
7 | // For now, we'll just test that the class can be instantiated
8 | const discovery = new StdioCapabilityDiscovery()
9 | assert.ok(discovery)
10 | })
11 |
12 | test('StdioCapabilityDiscovery should have discoverCapabilities method', async () => {
13 | const discovery = new StdioCapabilityDiscovery()
14 | assert.strictEqual(typeof discovery.discoverCapabilities, 'function')
15 | })
16 |
17 | test('StdioCapabilityDiscovery should have callTool method', async () => {
18 | const discovery = new StdioCapabilityDiscovery()
19 | assert.strictEqual(typeof discovery.callTool, 'function')
20 | })
21 |
22 | test('StdioCapabilityDiscovery should have readResource method', async () => {
23 | const discovery = new StdioCapabilityDiscovery()
24 | assert.strictEqual(typeof discovery.readResource, 'function')
25 | })
```
--------------------------------------------------------------------------------
/tests/unit/modules/stdio-manager.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { test } from 'node:test'
2 | import assert from 'node:assert'
3 | import { StdioManager } from '../../../src/modules/stdio-manager.js'
4 | import path from 'node:path'
5 |
6 | test('StdioManager should handle notifications', async () => {
7 | const manager = new StdioManager()
8 | const serverId = 'test-server'
9 | const serverPath = path.resolve(process.cwd(), 'tests/fixtures/stdio-server.js')
10 |
11 | let notificationReceived = null
12 | const notificationPromise = new Promise(resolve => {
13 | manager.onNotification(serverId, (message) => {
14 | notificationReceived = message
15 | resolve(message)
16 | })
17 | })
18 |
19 | await manager.startServer(serverId, serverPath)
20 |
21 | // Give the server a moment to start and send a notification
22 | await new Promise(resolve => setTimeout(resolve, 500))
23 |
24 | // The test server should send a notification on start
25 | // Let's wait for it
26 | await notificationPromise
27 |
28 | assert.deepStrictEqual(notificationReceived, { type: 'notification', message: 'server ready' })
29 |
30 | const server = manager['processes'].get(serverId)
31 | server?.kill()
32 | })
```
--------------------------------------------------------------------------------
/tests/unit/modules.route-registry.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import '../setup/test-setup.js'
2 | import test from 'node:test'
3 | import assert from 'node:assert/strict'
4 | import { RouteRegistry } from '../../src/routing/route-registry.js'
5 | import { CircuitBreaker } from '../../src/routing/circuit-breaker.js'
6 | import { LoadBalancer } from '../../src/routing/load-balancer.js'
7 |
8 | test('RouteRegistry resolves and caches, bumps health', () => {
9 | const servers = new Map([
10 | ['s1', { id: 's1', type: 'node', endpoint: 'http://localhost:1234', config: {} as any, status: 'running', lastHealthCheck: Date.now(), instances: [
11 | { id: 'i1', url: 'http://localhost:1', healthScore: 50 },
12 | { id: 'i2', url: 'http://localhost:2', healthScore: 50 },
13 | ] }]
14 | ])
15 | const reg = new RouteRegistry(servers as any, new CircuitBreaker({ failureThreshold: 5, successThreshold: 1, recoveryTimeoutMs: 10 }), new LoadBalancer())
16 | const r1 = reg.resolve('s1')!
17 | assert.ok(r1.instance.id === 'i1' || r1.instance.id === 'i2')
18 | // mark success and failure adjust health without throwing
19 | reg.markSuccess('s1', r1.instance.id)
20 | reg.markFailure('s1', r1.instance.id)
21 | })
22 |
23 |
```
--------------------------------------------------------------------------------
/tests/unit/auth.token-manager.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import '../setup/test-setup.js'
2 | import test from 'node:test'
3 | import assert from 'node:assert/strict'
4 | import { TokenManager, InMemoryTokenStorage } from '../../src/auth/token-manager.js'
5 |
6 | test('TokenManager stores, retrieves and cleans up', async () => {
7 | const storage = new InMemoryTokenStorage()
8 | const tm = new TokenManager({ storage, secret: 'k' })
9 | const key = 'user::server'
10 | await tm.storeToken(key, { access_token: 't', expires_at: Date.now() + 50, scope: [] })
11 | const tok = await tm.getToken(key)
12 | assert.equal(tok?.access_token, 't')
13 | await new Promise((r) => setTimeout(r, 60))
14 | await tm.cleanupExpiredTokens()
15 | const tok2 = await tm.getToken(key)
16 | assert.equal(tok2, null)
17 | })
18 |
19 | test('TokenManager works with custom KV-like storage', async () => {
20 | const { MemoryKVStorage } = await import('../utils/token-storages.js')
21 | const storage = new MemoryKVStorage()
22 | const tm = new TokenManager({ storage, secret: 'k' })
23 | await tm.storeToken('k1', { access_token: 'Z', expires_at: Date.now() + 1000, scope: [] })
24 | const tok = await tm.getToken('k1')
25 | assert.equal(tok?.access_token, 'Z')
26 | })
27 |
```
--------------------------------------------------------------------------------
/docs/.vitepress/theme/components/CodeTabs.vue:
--------------------------------------------------------------------------------
```vue
1 | <template>
2 | <div class="mcp-tabs" role="tablist" aria-label="Code Tabs">
3 | <div class="mcp-tabs__nav">
4 | <button
5 | v-for="opt in options"
6 | :key="opt.value"
7 | class="mcp-tabs__btn"
8 | role="tab"
9 | :aria-selected="active === opt.value"
10 | @click="active = opt.value"
11 | >
12 | {{ opt.label }}
13 | </button>
14 | </div>
15 | <div class="mcp-tabs__panel" role="tabpanel">
16 | <slot :name="active" />
17 | </div>
18 | </div>
19 | <div v-if="note" class="mcp-callout" style="margin-top:8px">{{ note }}</div>
20 | <div v-if="footnote" style="margin-top:6px;color:var(--vp-c-text-2);font-size:.9rem">{{ footnote }}</div>
21 | </template>
22 |
23 | <script setup lang="ts">
24 | import { ref, watchEffect } from 'vue'
25 |
26 | interface Option { label: string; value: string }
27 | const props = defineProps<{
28 | options: Option[]
29 | modelValue?: string
30 | note?: string
31 | footnote?: string
32 | }>()
33 | const active = ref(props.modelValue || (props.options[0]?.value ?? ''))
34 | watchEffect(() => {
35 | if (!props.options.find(o => o.value === active.value)) {
36 | active.value = props.options[0]?.value ?? ''
37 | }
38 | })
39 | </script>
40 |
41 |
```
--------------------------------------------------------------------------------
/tests/unit/utils.logger.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import '../setup/test-setup.js'
2 | import test from 'node:test'
3 | import assert from 'node:assert/strict'
4 | import { Logger } from '../../src/utils/logger.js'
5 |
6 | test('Logger emits human log line', () => {
7 | const lines: string[] = []
8 | const orig = console.log
9 | console.log = (s: any) => { lines.push(String(s)) }
10 | try {
11 | Logger.configure({ json: false, level: 'debug' })
12 | Logger.info('hello', { a: 1 })
13 | assert.ok(lines.length >= 1)
14 | assert.match(lines[0], /\[INFO\].*hello/)
15 | } finally {
16 | console.log = orig
17 | Logger.configure({ json: false, level: 'error' })
18 | }
19 | })
20 |
21 | test('Logger child with base fields', () => {
22 | const lines: string[] = []
23 | const orig = console.log
24 | console.log = (s: any) => { lines.push(String(s)) }
25 | try {
26 | Logger.configure({ json: true, level: 'debug', base: { svc: 'x' } })
27 | const L = Logger.with({ reqId: 'r1' })
28 | L.debug('dbg', { extra: 2 })
29 | assert.ok(lines.length)
30 | const parsed = JSON.parse(lines[0])
31 | assert.equal(parsed.svc, 'x')
32 | assert.equal(parsed.reqId, 'r1')
33 | assert.equal(parsed.msg, 'dbg')
34 | } finally {
35 | console.log = orig
36 | }
37 | })
38 |
39 |
```
--------------------------------------------------------------------------------
/docs/getting-started/quick-start.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Quick Start
3 | ---
4 |
5 | # Quick Start
6 |
7 | Get from zero to a running Master MCP Server in under 10 minutes.
8 |
9 | <CodeTabs
10 | :options="[
11 | { label: 'Node.js', value: 'node' },
12 | { label: 'Docker', value: 'docker' },
13 | { label: 'Cloudflare Workers', value: 'workers' }
14 | ]"
15 | note="Follow one tab end-to-end."
16 | >
17 | <template #node>
18 |
19 | ```bash
20 | npm install
21 | cp .env.example .env
22 | npm run build && npm run start
23 | ```
24 |
25 | Minimal `config/master.yaml`:
26 |
27 | ```yaml
28 | hosting:
29 | port: 3000
30 | servers:
31 | - id: search
32 | type: local
33 | auth_strategy: master_oauth
34 | config:
35 | port: 4100
36 | ```
37 |
38 | Verify:
39 |
40 | ```bash
41 | curl -s http://localhost:3000/health
42 | curl -s http://localhost:3000/mcp/tools/list | jq
43 | ```
44 |
45 | </template>
46 | <template #docker>
47 |
48 | ```bash
49 | docker compose up --build
50 | ```
51 |
52 | Production image:
53 |
54 | ```bash
55 | docker run -p 3000:3000 \
56 | -e NODE_ENV=production \
57 | -e TOKEN_ENC_KEY=... \
58 | ghcr.io/OWNER/REPO:latest
59 | ```
60 |
61 | </template>
62 | <template #workers>
63 |
64 | ```bash
65 | npm run build:worker
66 | npx wrangler deploy deploy/cloudflare
67 | ```
68 |
69 | </template>
70 | </CodeTabs>
71 |
72 | ## Generate a Config
73 |
74 | <ConfigGenerator />
75 |
76 | ## Test Requests
77 |
78 | <ApiPlayground />
79 |
80 |
```
--------------------------------------------------------------------------------
/src/types/mcp.ts:
--------------------------------------------------------------------------------
```typescript
1 | // Minimal MCP-like types for Phase 1 compilation.
2 | // Replace with @modelcontextprotocol/sdk imports in later phases.
3 |
4 | export interface ToolDefinition {
5 | name: string
6 | description?: string
7 | inputSchema?: unknown
8 | }
9 |
10 | export interface ResourceDefinition {
11 | uri: string
12 | name?: string
13 | description?: string
14 | mimeType?: string
15 | }
16 |
17 | export interface PromptDefinition {
18 | name: string
19 | description?: string
20 | input?: unknown
21 | }
22 |
23 | export interface ListToolsRequest {
24 | type: 'list_tools'
25 | }
26 |
27 | export interface ListToolsResult {
28 | tools: ToolDefinition[]
29 | }
30 |
31 | export interface CallToolRequest {
32 | name: string
33 | arguments?: Record<string, unknown> | undefined
34 | }
35 |
36 | export interface CallToolResult {
37 | content: unknown
38 | isError?: boolean
39 | }
40 |
41 | export interface ListResourcesRequest {
42 | type: 'list_resources'
43 | }
44 |
45 | export interface ListResourcesResult {
46 | resources: ResourceDefinition[]
47 | }
48 |
49 | export interface ReadResourceRequest {
50 | uri: string
51 | }
52 |
53 | export interface ReadResourceResult {
54 | contents: string | Uint8Array
55 | mimeType?: string
56 | }
57 |
58 | export interface SubscribeRequest {
59 | target: string
60 | }
61 |
62 | export interface SubscribeResult {
63 | ok: boolean
64 | }
65 |
66 |
```
--------------------------------------------------------------------------------
/tests/factories/configFactory.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { MasterConfig, ServerConfig, AuthStrategy } from '../../src/types/config.js'
2 |
3 | export function makeServerConfig(id: string, endpoint: string, authStrategy: AuthStrategy = 0 as any): ServerConfig {
4 | return {
5 | id,
6 | type: 'local',
7 | url: endpoint,
8 | auth_strategy: authStrategy || 'bypass_auth',
9 | config: { port: new URL(endpoint).port ? Number(new URL(endpoint).port) : undefined },
10 | }
11 | }
12 |
13 | export function makeMasterConfig(params: {
14 | servers: Array<{ id: string; endpoint: string }>
15 | hosting?: Partial<MasterConfig['hosting']>
16 | routing?: MasterConfig['routing']
17 | master_oauth?: Partial<MasterConfig['master_oauth']>
18 | }): MasterConfig {
19 | const servers: ServerConfig[] = params.servers.map((s) => makeServerConfig(s.id, s.endpoint))
20 | return {
21 | master_oauth: {
22 | authorization_endpoint: 'http://localhost/authorize',
23 | token_endpoint: 'http://localhost/token',
24 | client_id: 'local',
25 | redirect_uri: 'http://localhost/oauth/callback',
26 | scopes: ['openid'],
27 | ...(params.master_oauth ?? {}),
28 | },
29 | servers,
30 | hosting: { platform: 'node', port: 0, ...(params.hosting ?? {}) },
31 | routing: params.routing,
32 | }
33 | }
34 |
35 |
```
--------------------------------------------------------------------------------
/tests/unit/config.schema-validator.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import '../setup/test-setup.js'
2 | import test from 'node:test'
3 | import assert from 'node:assert/strict'
4 | import { SchemaValidator } from '../../src/config/schema-validator.js'
5 |
6 | test('SchemaValidator accepts minimal valid config', async () => {
7 | const schema = await SchemaValidator.loadSchema('config/schema.json')
8 | const cfg = {
9 | master_oauth: {
10 | authorization_endpoint: 'https://auth.local/authorize',
11 | token_endpoint: 'https://auth.local/token',
12 | client_id: 'x',
13 | redirect_uri: 'http://localhost/cb',
14 | scopes: ['openid'],
15 | },
16 | hosting: { platform: 'node' },
17 | servers: [],
18 | }
19 | assert.doesNotThrow(() => SchemaValidator.assertValid(cfg, schema!))
20 | })
21 |
22 | test('SchemaValidator rejects invalid platform', async () => {
23 | const schema = await SchemaValidator.loadSchema('config/schema.json')
24 | const cfg: any = {
25 | master_oauth: {
26 | authorization_endpoint: 'https://auth.local/authorize',
27 | token_endpoint: 'https://auth.local/token',
28 | client_id: 'x',
29 | redirect_uri: 'http://localhost/cb',
30 | scopes: ['openid'],
31 | },
32 | hosting: { platform: 'nope' },
33 | servers: [],
34 | }
35 | assert.throws(() => SchemaValidator.assertValid(cfg, schema!))
36 | })
37 |
38 |
```
--------------------------------------------------------------------------------
/config/default.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "master_oauth": {
3 | "authorization_endpoint": "https://example.com/oauth/authorize",
4 | "token_endpoint": "https://example.com/oauth/token",
5 | "client_id": "master-mcp",
6 | "client_secret": "env:MASTER_OAUTH_CLIENT_SECRET",
7 | "redirect_uri": "http://localhost:3000/callback",
8 | "scopes": ["openid", "profile"],
9 | "audience": "master-mcp"
10 | },
11 | "hosting": {
12 | "platform": "node",
13 | "port": 3000
14 | },
15 | "logging": {
16 | "level": "info"
17 | },
18 | "routing": {
19 | "loadBalancer": { "strategy": "round_robin" },
20 | "circuitBreaker": { "failureThreshold": 5, "successThreshold": 2, "recoveryTimeoutMs": 30000 },
21 | "retry": { "maxRetries": 2, "baseDelayMs": 250, "maxDelayMs": 4000, "backoffFactor": 2, "jitter": "full" }
22 | },
23 | "servers": [
24 | {
25 | "id": "test-server",
26 | "type": "local",
27 | "url": "http://localhost:3006",
28 | "auth_strategy": "bypass_auth",
29 | "config": {
30 | "environment": {},
31 | "args": []
32 | }
33 | },
34 | {
35 | "id": "stdio-server",
36 | "type": "local",
37 | "url": "file://./examples/stdio-mcp-server.cjs",
38 | "auth_strategy": "bypass_auth",
39 | "config": {
40 | "environment": {},
41 | "args": []
42 | }
43 | }
44 | ]
45 | }
46 |
47 |
```
--------------------------------------------------------------------------------
/docs/deployment/docs-site.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Deploy the Docs Site
3 | ---
4 |
5 | # Deploy the Docs Site
6 |
7 | The documentation is built with VitePress and lives under `docs/`.
8 |
9 | ## Build Locally
10 |
11 | ```bash
12 | npm run docs:build
13 | ```
14 |
15 | The static site is emitted to `docs/.vitepress/dist`.
16 |
17 | ## Preview Locally
18 |
19 | ```bash
20 | npm run docs:preview
21 | ```
22 |
23 | ## GitHub Pages
24 |
25 | Setup workflow (example):
26 |
27 | ```yaml
28 | name: Deploy Docs
29 | on:
30 | push:
31 | branches: [ main ]
32 | permissions:
33 | contents: read
34 | pages: write
35 | id-token: write
36 | jobs:
37 | build:
38 | runs-on: ubuntu-latest
39 | steps:
40 | - uses: actions/checkout@v4
41 | - uses: actions/setup-node@v4
42 | with: { node-version: '20' }
43 | - run: npm ci
44 | - run: npm run docs:build
45 | - uses: actions/upload-pages-artifact@v3
46 | with: { path: docs/.vitepress/dist }
47 | deploy:
48 | needs: build
49 | runs-on: ubuntu-latest
50 | environment: { name: github-pages, url: ${{ steps.deployment.outputs.page_url }} }
51 | steps:
52 | - id: deployment
53 | uses: actions/deploy-pages@v4
54 | ```
55 |
56 | ## Cloudflare Pages
57 |
58 | - Framework preset: None
59 | - Build command: `npm run docs:build`
60 | - Build output directory: `docs/.vitepress/dist`
61 |
62 | ## Netlify
63 |
64 | - Build command: `npm run docs:build`
65 | - Publish directory: `docs/.vitepress/dist`
66 |
67 |
```
--------------------------------------------------------------------------------
/docs/tutorials/load-balancing-and-resilience.md:
--------------------------------------------------------------------------------
```markdown
1 | # Tutorial: Load Balancing & Resilience
2 |
3 | Demonstrates multiple backends with load balancing, retries, and circuit breaker tuning.
4 |
5 | ## Configuration
6 |
7 | `examples/multi-server/config.yaml` (provided):
8 |
9 | ```yaml
10 | hosting:
11 | platform: node
12 | port: 3000
13 |
14 | master_oauth:
15 | authorization_endpoint: https://example.com/oauth/authorize
16 | token_endpoint: https://example.com/oauth/token
17 | client_id: master-mcp
18 | redirect_uri: http://localhost:3000/oauth/callback
19 | scopes: [openid]
20 |
21 | routing:
22 | loadBalancer:
23 | strategy: health
24 | circuitBreaker:
25 | failureThreshold: 3
26 | successThreshold: 2
27 | recoveryTimeoutMs: 10000
28 | retry:
29 | maxRetries: 3
30 | baseDelayMs: 200
31 | maxDelayMs: 3000
32 | backoffFactor: 2
33 | jitter: full
34 | retryOn:
35 | networkErrors: true
36 | httpStatuses: [408, 429]
37 | httpStatusClasses: [5]
38 |
39 | servers:
40 | - id: compute
41 | type: local
42 | auth_strategy: bypass_auth
43 | config:
44 | port: 4101
45 | - id: compute
46 | type: local
47 | auth_strategy: bypass_auth
48 | config:
49 | port: 4102
50 | ```
51 |
52 | Run master with:
53 |
54 | ```
55 | MASTER_CONFIG_PATH=examples/multi-server/config.yaml npm run dev
56 | ```
57 |
58 | The router will choose an instance per call, retry on transient errors, and open the circuit if failures breach the threshold.
59 |
60 |
```
--------------------------------------------------------------------------------
/docs/deployment/cicd.md:
--------------------------------------------------------------------------------
```markdown
1 | # CI/CD Pipelines
2 |
3 | Below are example steps you can adapt for your CI provider.
4 |
5 | ## Build & Publish Docker Image (GitHub Actions)
6 |
7 | ```yaml
8 | name: build-and-push
9 | on:
10 | push:
11 | branches: [ main ]
12 |
13 | jobs:
14 | docker:
15 | runs-on: ubuntu-latest
16 | permissions:
17 | contents: read
18 | packages: write
19 | steps:
20 | - uses: actions/checkout@v4
21 | - uses: docker/setup-buildx-action@v3
22 | - uses: docker/login-action@v3
23 | with:
24 | registry: ghcr.io
25 | username: ${{ github.actor }}
26 | password: ${{ secrets.GITHUB_TOKEN }}
27 | - name: Build
28 | run: |
29 | docker build -f deploy/docker/Dockerfile -t ghcr.io/${{ github.repository }}:latest .
30 | - name: Push
31 | run: docker push ghcr.io/${{ github.repository }}:latest
32 | ```
33 |
34 | ## Deploy to Cloudflare Workers (GitHub Actions)
35 |
36 | ```yaml
37 | name: deploy-workers
38 | on:
39 | push:
40 | branches: [ main ]
41 |
42 | jobs:
43 | deploy:
44 | runs-on: ubuntu-latest
45 | steps:
46 | - uses: actions/checkout@v4
47 | - uses: cloudflare/wrangler-action@v3
48 | with:
49 | apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
50 | command: deploy --env production
51 | ```
52 |
53 | ## Deploy to Koyeb (CLI from CI)
54 |
55 | Use Koyeb’s GitHub Action or the CLI with an API token to update the service image tag after push to GHCR.
56 |
57 |
```
--------------------------------------------------------------------------------
/src/runtime/worker.ts:
--------------------------------------------------------------------------------
```typescript
1 | // Worker runtime with minimal surface to avoid Node-specific modules
2 | import { ConfigLoader } from '../config/config-loader.js'
3 | import { OAuthFlowController } from '../oauth/flow-controller.js'
4 | import { collectSystemMetrics } from '../utils/monitoring.js'
5 |
6 | export default {
7 | async fetch(_req: Request, env?: Record<string, unknown>): Promise<Response> {
8 | ;(globalThis as any).__WORKER_ENV = env || (globalThis as any).__WORKER_ENV || {}
9 | try {
10 | const url = new URL(_req.url)
11 | if (url.pathname === '/health') {
12 | return new Response(JSON.stringify({ ok: true }), { headers: { 'content-type': 'application/json' } })
13 | }
14 | if (url.pathname === '/metrics') {
15 | return new Response(
16 | JSON.stringify({ ok: true, system: collectSystemMetrics() }),
17 | { headers: { 'content-type': 'application/json' } }
18 | )
19 | }
20 | if (url.pathname.startsWith('/oauth')) {
21 | const cfg = await ConfigLoader.loadFromEnv()
22 | const ctrl = new OAuthFlowController({ getConfig: () => cfg })
23 | return await ctrl.handleRequest(_req)
24 | }
25 | return new Response(JSON.stringify({ ok: true }), {
26 | headers: { 'content-type': 'application/json' },
27 | })
28 | } finally {
29 | // keep server warm for now; no-op
30 | }
31 | },
32 | }
33 |
```
--------------------------------------------------------------------------------
/docs/public/diagrams/architecture.svg:
--------------------------------------------------------------------------------
```
1 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 280">
2 | <defs>
3 | <style>
4 | .box{fill:#fff;stroke:#0ea5e9;stroke-width:2;rx:10;}
5 | .t{font:16px sans-serif;dominant-baseline:middle;text-anchor:middle;}
6 | </style>
7 | <marker id="arrow" markerWidth="10" markerHeight="10" refX="6" refY="3" orient="auto">
8 | <path d="M0,0 L0,6 L6,3 z" fill="#0ea5e9" />
9 | </marker>
10 | </defs>
11 | <rect x="40" y="40" width="160" height="60" class="box"/>
12 | <text x="120" y="70" class="t">MCP Client(s)</text>
13 |
14 | <rect x="320" y="40" width="180" height="60" class="box"/>
15 | <text x="410" y="60" class="t">Master MCP Server</text>
16 | <text x="410" y="80" class="t">Auth • Routing • Config</text>
17 |
18 | <rect x="620" y="20" width="140" height="40" class="box"/>
19 | <text x="690" y="40" class="t">Server A</text>
20 | <rect x="620" y="80" width="140" height="40" class="box"/>
21 | <text x="690" y="100" class="t">Server B</text>
22 | <rect x="620" y="140" width="140" height="40" class="box"/>
23 | <text x="690" y="160" class="t">Server C</text>
24 |
25 | <path d="M200,70 L320,70" stroke="#0ea5e9" stroke-width="2" marker-end="url(#arrow)"/>
26 | <path d="M500,60 L620,40" stroke="#0ea5e9" stroke-width="2" marker-end="url(#arrow)"/>
27 | <path d="M500,70 L620,100" stroke="#0ea5e9" stroke-width="2" marker-end="url(#arrow)"/>
28 | <path d="M500,80 L620,160" stroke="#0ea5e9" stroke-width="2" marker-end="url(#arrow)"/>
29 | </svg>
30 |
31 |
```
--------------------------------------------------------------------------------
/docs/configuration/environment-variables.md:
--------------------------------------------------------------------------------
```markdown
1 | # Environment Variables
2 |
3 | Environment variables can override configuration at load time. Key variables:
4 |
5 | ## Hosting
6 |
7 | - `MASTER_HOSTING_PLATFORM` → `hosting.platform`
8 | - `MASTER_HOSTING_PORT` → `hosting.port`
9 | - `MASTER_BASE_URL` → `hosting.base_url`
10 |
11 | ## Logging
12 |
13 | - `MASTER_LOG_LEVEL` → `logging.level`
14 |
15 | ## Master OAuth
16 |
17 | - `MASTER_OAUTH_ISSUER` → `master_oauth.issuer`
18 | - `MASTER_OAUTH_AUTHORIZATION_ENDPOINT` → `master_oauth.authorization_endpoint`
19 | - `MASTER_OAUTH_TOKEN_ENDPOINT` → `master_oauth.token_endpoint`
20 | - `MASTER_OAUTH_JWKS_URI` → `master_oauth.jwks_uri`
21 | - `MASTER_OAUTH_CLIENT_ID` → `master_oauth.client_id`
22 | - `MASTER_OAUTH_CLIENT_SECRET` → `master_oauth.client_secret` (stored as `env:MASTER_OAUTH_CLIENT_SECRET`)
23 | - `MASTER_OAUTH_REDIRECT_URI` → `master_oauth.redirect_uri`
24 | - `MASTER_OAUTH_SCOPES` → comma-separated list → `master_oauth.scopes[]`
25 | - `MASTER_OAUTH_AUDIENCE` → `master_oauth.audience`
26 |
27 | ## Servers (bulk)
28 |
29 | - `MASTER_SERVERS` → JSON array of servers
30 | - `MASTER_SERVERS_YAML` → YAML array of servers
31 |
32 | ## Config discovery and env
33 |
34 | - `MASTER_CONFIG_PATH` → explicit path to YAML/JSON config file
35 | - `MASTER_ENV` / `NODE_ENV` → selects env-specific overrides and affects runtime behavior
36 |
37 | ## Secrets & Tokens
38 |
39 | - `MASTER_CONFIG_KEY` (or `MASTER_SECRET_KEY`) → decrypts `enc:gcm:` config values
40 | - `TOKEN_ENC_KEY` → encrypts stored delegated/proxy tokens (REQUIRED in production)
41 |
42 | See `.env.example` for a template.
43 |
44 |
```
--------------------------------------------------------------------------------
/docs/guides/server-sharing.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Server Sharing
3 | ---
4 |
5 | # Server Sharing
6 |
7 | Expose multiple MCP backends through the master server and share a single, consistent endpoint with your team or applications.
8 |
9 | ## Add Backends
10 |
11 | Backends are defined in your master config under `servers`.
12 |
13 | ```yaml
14 | servers:
15 | - id: search
16 | type: local
17 | auth_strategy: master_oauth
18 | config: { port: 4100 }
19 | - id: github-tools
20 | type: local
21 | auth_strategy: delegate_oauth
22 | auth_config:
23 | provider: github
24 | authorization_endpoint: https://github.com/login/oauth/authorize
25 | token_endpoint: https://github.com/login/oauth/access_token
26 | client_id: ${GITHUB_CLIENT_ID}
27 | client_secret: env:GITHUB_CLIENT_SECRET
28 | scopes: [repo, read:user]
29 | config: { port: 4010 }
30 | ```
31 |
32 | Name collisions are avoided by prefixing capabilities with the server id (e.g., `search.query`, `github-tools.repo.read`).
33 |
34 | ## Auth Strategies per Server
35 |
36 | Choose one per server: `master_oauth`, `delegate_oauth`, `proxy_oauth`, `bypass_auth`.
37 |
38 | <AuthFlowDemo />
39 |
40 | ## Share the Endpoint
41 |
42 | - Local: `http://localhost:<port>`
43 | - Docker: container port mapped to host
44 | - Workers: public URL from your deployment
45 |
46 | Distribute the base URL along with any client token requirements.
47 |
48 | ## Health Monitoring & Logs
49 |
50 | - `GET /health` and `GET /metrics`
51 | - Container logs (Docker/Koyeb) or platform logs (Workers)
52 | - Use `performHealthChecks()` from code if embedding the master as a library
53 |
54 |
```
--------------------------------------------------------------------------------
/docs/tutorials/oauth-delegation-github.md:
--------------------------------------------------------------------------------
```markdown
1 | # Tutorial: OAuth Delegation (GitHub)
2 |
3 | Goal: Use delegated OAuth for a backend requiring GitHub OAuth.
4 |
5 | ## 1) Create a GitHub OAuth App
6 |
7 | - Homepage URL: `http://localhost:3000`
8 | - Authorization callback URL: `http://localhost:3000/oauth/callback`
9 |
10 | Record `Client ID` and `Client Secret`.
11 |
12 | ## 2) Configuration
13 |
14 | `examples/oauth-node/config.yaml` (provided):
15 |
16 | ```yaml
17 | hosting:
18 | platform: node
19 | port: 3000
20 |
21 | master_oauth:
22 | authorization_endpoint: https://example.com/oauth/authorize
23 | token_endpoint: https://example.com/oauth/token
24 | client_id: master-mcp
25 | redirect_uri: http://localhost:3000/oauth/callback
26 | scopes: [openid]
27 |
28 | servers:
29 | - id: github-tools
30 | type: local
31 | auth_strategy: delegate_oauth
32 | auth_config:
33 | provider: github
34 | authorization_endpoint: https://github.com/login/oauth/authorize
35 | token_endpoint: https://github.com/login/oauth/access_token
36 | client_id: ${GITHUB_CLIENT_ID}
37 | client_secret: env:GITHUB_CLIENT_SECRET
38 | scopes: [repo, read:user]
39 | config:
40 | port: 4100
41 | ```
42 |
43 | Set environment variable:
44 |
45 | ```
46 | export GITHUB_CLIENT_SECRET=... # from GitHub app
47 | ```
48 |
49 | Run with:
50 |
51 | ```
52 | MASTER_CONFIG_PATH=examples/oauth-node/config.yaml npm run dev
53 | ```
54 |
55 | ## 3) Start the Flow
56 |
57 | Navigate to:
58 |
59 | ```
60 | http://localhost:3000/oauth/authorize?server_id=github-tools
61 | ```
62 |
63 | Complete the GitHub consent, then you should see a success page. Calls to tools under `github-tools.*` will now include the delegated token.
64 |
65 |
```
--------------------------------------------------------------------------------
/tests/unit/utils.crypto.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import '../setup/test-setup.js'
2 | import test from 'node:test'
3 | import assert from 'node:assert/strict'
4 | import { CryptoUtils } from '../../src/utils/crypto.js'
5 |
6 | test('CryptoUtils encrypt/decrypt roundtrip', () => {
7 | const key = 'super-secret-key'
8 | const text = 'hello world ' + Date.now()
9 | const enc = CryptoUtils.encrypt(text, key)
10 | assert.ok(typeof enc === 'string' && enc.length > 16)
11 | const dec = CryptoUtils.decrypt(enc, key)
12 | assert.equal(dec, text)
13 | })
14 |
15 | test('CryptoUtils hash/verify', () => {
16 | const h = CryptoUtils.hash('abc')
17 | assert.ok(h.length === 64)
18 | assert.ok(CryptoUtils.verify('abc', h))
19 | assert.equal(CryptoUtils.verify('abcd', h), false)
20 | })
21 |
22 | test('CryptoUtils pbkdf2 and scrypt hashing', () => {
23 | const p = 'password'
24 | const pb = CryptoUtils.pbkdf2Hash(p, 10_000, 8)
25 | assert.ok(pb.startsWith('pbkdf2$sha256$'))
26 | assert.ok(CryptoUtils.pbkdf2Verify(p, pb))
27 | assert.equal(CryptoUtils.pbkdf2Verify('nope', pb), false)
28 |
29 | const sc = CryptoUtils.scryptHash(p, { N: 1024, r: 8, p: 1, saltLen: 8, keyLen: 16 })
30 | assert.ok(sc.startsWith('scrypt$'))
31 | assert.ok(CryptoUtils.scryptVerify(p, sc))
32 | assert.equal(CryptoUtils.scryptVerify('nope', sc), false)
33 | })
34 |
35 | test('CryptoUtils bcryptHash falls back but verifies', async () => {
36 | const p = 'topsecret'
37 | const h = await CryptoUtils.bcryptHash(p)
38 | assert.ok(typeof h === 'string')
39 | assert.ok(await CryptoUtils.bcryptVerify(p, h))
40 | assert.equal(await CryptoUtils.bcryptVerify('nope', h), false)
41 | })
42 |
43 |
```
--------------------------------------------------------------------------------
/tests/security/security.oauth-and-input.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import '../setup/test-setup.js'
2 | import test from 'node:test'
3 | import assert from 'node:assert/strict'
4 | import { CallbackHandler } from '../../src/oauth/callback-handler.js'
5 | import { PKCEManager } from '../../src/oauth/pkce-manager.js'
6 | import { StateManager } from '../../src/oauth/state-manager.js'
7 | import { TokenManager, InMemoryTokenStorage } from '../../src/auth/token-manager.js'
8 |
9 | test('CallbackHandler rejects missing/invalid state', async () => {
10 | const cb = new CallbackHandler({
11 | 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,
12 | stateManager: new StateManager(),
13 | pkceManager: new PKCEManager(),
14 | baseUrl: 'http://localhost',
15 | })
16 | const res = await cb.handleCallback(new URLSearchParams({ state: 'nope', code: 'x' }), { provider: 'custom', authorization_endpoint: 'http://a', token_endpoint: 'http://t', client_id: 'x' })
17 | assert.ok(res.error)
18 | })
19 |
20 | test('TokenManager decryption failure is handled and entry removed', async () => {
21 | const storage = new InMemoryTokenStorage()
22 | const tm1 = new TokenManager({ storage, secret: 'a' })
23 | const tm2 = new TokenManager({ storage, secret: 'b' })
24 | const key = 'k'
25 | await tm1.storeToken(key, { access_token: 'X', expires_at: Date.now() + 1000, scope: [] })
26 | const before = await tm2.getToken(key)
27 | assert.equal(before, null) // decryption failed => deleted
28 | })
29 |
30 |
```
--------------------------------------------------------------------------------
/docs/troubleshooting/index.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Common Issues
3 | ---
4 |
5 | # Troubleshooting: Common Issues
6 |
7 | - Invalid configuration: run `npm run docs:config` and compare with schema.
8 | - OAuth callback fails: verify redirect URIs, state/PKCE, and client secrets.
9 | - Workers runtime errors: avoid Node-only APIs; use Web Crypto and Fetch.
10 | - Routing loop or failure: check circuit breaker status and retry limits.
11 | - CORS/Networking: ensure your hosting platform permits required egress.
12 |
13 | ## FAQ
14 |
15 | <details>
16 | <summary>How do I connect a GUI client like Claude Desktop?</summary>
17 |
18 | 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.
19 |
20 | </details>
21 |
22 | <details>
23 | <summary>Why do I get 401/403 responses?</summary>
24 |
25 | 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>`.
26 |
27 | </details>
28 |
29 | <details>
30 | <summary>Tools or resources are missing in the client.</summary>
31 |
32 | Confirm each backend is healthy and exposes capabilities. Check `/capabilities` and `/mcp/tools/list`. Prefix names with the server id (e.g., `serverId.toolName`).
33 |
34 | </details>
35 |
36 | <details>
37 | <summary>Requests time out under load.</summary>
38 |
39 | Tune retries and circuit breaker thresholds in `routing`, and monitor p95/p99 latencies. See Advanced → Performance & Scalability.
40 |
41 | </details>
42 |
43 |
```
--------------------------------------------------------------------------------
/tests/mocks/mcp/fake-backend.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type http from 'node:http'
2 | import { createTestServer } from '../../_utils/test-server.js'
3 |
4 | export interface FakeBackendOptions {
5 | id: string
6 | tools?: Array<{ name: string; description?: string }>
7 | resources?: Array<{ uri: string; description?: string; mimeType?: string }>
8 | }
9 |
10 | export async function startFakeMcpBackend(opts: FakeBackendOptions): Promise<{ url: string; stop: () => Promise<void> }> {
11 | const srv = await createTestServer()
12 | const tools = opts.tools ?? [{ name: 'echo', description: 'Echo input' }]
13 | const resources = opts.resources ?? []
14 |
15 | srv.register('GET', '/health', () => ({ body: { ok: true } }))
16 | srv.register('GET', '/capabilities', () => ({ body: { tools, resources } }))
17 | srv.register('POST', '/mcp/tools/list', () => ({ body: { tools } }))
18 | srv.register('POST', '/mcp/resources/list', () => ({ body: { resources } }))
19 | srv.register('POST', '/mcp/tools/call', (_req: http.IncomingMessage, raw) => {
20 | const body = safeParse(raw)
21 | if (body?.name === 'echo') return { body: { content: body?.arguments ?? {}, isError: false } }
22 | return { body: { content: { error: 'unknown tool' }, isError: true } }
23 | })
24 | srv.register('POST', '/mcp/resources/read', (_req, raw) => {
25 | const body = safeParse(raw)
26 | return { body: { contents: `content:${body?.uri ?? ''}`, mimeType: 'text/plain' } }
27 | })
28 |
29 | return { url: srv.url, stop: srv.close }
30 | }
31 |
32 | function safeParse(raw?: string): any {
33 | try { return raw ? JSON.parse(raw) : undefined } catch { return undefined }
34 | }
35 |
36 |
```
--------------------------------------------------------------------------------
/docs/getting-started.md:
--------------------------------------------------------------------------------
```markdown
1 | # Getting Started
2 |
3 | This guide walks you through running Master MCP Server locally, configuring backends, and discovering capabilities.
4 |
5 | ## Prerequisites
6 |
7 | - Node.js >= 18.17
8 | - npm
9 | - Network access to install dependencies and reach your OAuth providers/backends
10 |
11 | ## Install
12 |
13 | ```
14 | npm ci
15 | ```
16 |
17 | ## Configure
18 |
19 | 1) Copy env template and edit as needed:
20 |
21 | ```
22 | cp .env.example .env
23 | ```
24 |
25 | 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.
26 |
27 | Example minimal YAML (single local server):
28 |
29 | ```yaml
30 | hosting:
31 | platform: node
32 | port: 3000
33 |
34 | master_oauth:
35 | authorization_endpoint: https://example.com/oauth/authorize
36 | token_endpoint: https://example.com/oauth/token
37 | client_id: master-mcp
38 | redirect_uri: http://localhost:3000/oauth/callback
39 | scopes: [openid]
40 |
41 | servers:
42 | - id: tools
43 | type: local
44 | auth_strategy: bypass_auth
45 | config:
46 | port: 3333
47 | ```
48 |
49 | ## Run Dev Server
50 |
51 | ```
52 | npm run dev
53 | ```
54 |
55 | If your config is outside `config/`, set:
56 |
57 | ```
58 | MASTER_CONFIG_PATH=examples/sample-configs/basic.yaml npm run dev
59 | ```
60 |
61 | ## Verify
62 |
63 | - `GET http://localhost:3000/health` → `{ ok: true }`
64 | - `POST http://localhost:3000/mcp/tools/list`
65 | - `POST http://localhost:3000/mcp/resources/list`
66 |
67 | When calling tools/resources on protected backends, include `Authorization: Bearer <token>`.
68 |
69 | ## Next Steps
70 |
71 | - Read `docs/guides/authentication.md` for OAuth flows
72 | - See `examples/*` to run end-to-end scenarios
73 | - Deploy using `docs/deployment/*`
74 |
75 |
```
--------------------------------------------------------------------------------
/tests/unit/auth.multi-auth-manager.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import '../setup/test-setup.js'
2 | import test from 'node:test'
3 | import assert from 'node:assert/strict'
4 | import { MultiAuthManager } from '../../src/auth/multi-auth-manager.js'
5 | import { AuthStrategy } from '../../src/types/config.js'
6 |
7 | const masterCfg = {
8 | authorization_endpoint: 'http://localhost/auth',
9 | token_endpoint: 'http://localhost/token',
10 | client_id: 'master',
11 | redirect_uri: 'http://localhost/cb',
12 | scopes: ['openid'],
13 | }
14 |
15 | test('MultiAuthManager pass-through and delegation', async (t) => {
16 | try {
17 | const mam = new MultiAuthManager(masterCfg as any)
18 | mam.registerServerAuth('srv1', AuthStrategy.MASTER_OAUTH)
19 | const h = await mam.prepareAuthForBackend('srv1', 'CLIENT')
20 | assert.equal(h.Authorization, 'Bearer CLIENT')
21 |
22 | mam.registerServerAuth('srv2', AuthStrategy.DELEGATE_OAUTH, {
23 | provider: 'custom', authorization_endpoint: 'http://p/auth', token_endpoint: 'http://p/token', client_id: 'c'
24 | })
25 | const d = await mam.prepareAuthForBackend('srv2', 'CLIENT') as any
26 | assert.equal(d.type, 'oauth_delegation')
27 | } catch (error) {
28 | console.error('Test failed:', error)
29 | throw error
30 | }
31 | })
32 |
33 | test('MultiAuthManager stores delegated server token', async (t) => {
34 | try {
35 | const mam = new MultiAuthManager(masterCfg as any)
36 | await mam.storeDelegatedToken('CLIENT', 'srv', { access_token: 'S', expires_at: Date.now() + 1000, scope: [] })
37 | const tok = await mam.getStoredServerToken('srv', 'CLIENT')
38 | assert.equal(tok, 'S')
39 | } catch (error) {
40 | console.error('Test failed:', error)
41 | throw error
42 | }
43 | })
44 |
45 |
```
--------------------------------------------------------------------------------
/debug-stdio.js:
--------------------------------------------------------------------------------
```javascript
1 | import { StdioManager } from './src/modules/stdio-manager.js'
2 | import { Logger } from './src/utils/logger.js'
3 |
4 | // Set logger to debug level
5 | Logger.configure({ level: 'debug' })
6 |
7 | async function testStdio() {
8 | const stdioManager = new StdioManager()
9 | const serverId = 'test-stdio-server'
10 | const filePath = './examples/stdio-mcp-server.cjs'
11 |
12 | try {
13 | console.log('Starting STDIO server...')
14 | const serverProcess = await stdioManager.startServer(serverId, filePath)
15 | console.log('STDIO server started successfully:', serverProcess)
16 |
17 | // Send a simple request to see if we can communicate
18 | console.log('Sending initialize request...')
19 | const initializeRequestId = Date.now()
20 | const initializeRequest = {
21 | jsonrpc: "2.0",
22 | id: initializeRequestId,
23 | method: "initialize",
24 | params: {
25 | protocolVersion: "2025-06-18",
26 | capabilities: {},
27 | clientInfo: {
28 | name: "debug-script",
29 | version: "1.0.0"
30 | }
31 | }
32 | }
33 |
34 | await stdioManager.sendMessage(serverId, initializeRequest)
35 | console.log('Initialize request sent')
36 |
37 | // Wait for response
38 | console.log('Waiting for response...')
39 | const response = await stdioManager.waitForResponse(serverId, initializeRequestId, 5000) // 5 second timeout
40 | console.log('Received response:', response)
41 |
42 | // Stop the server
43 | console.log('Stopping server...')
44 | await serverProcess.stop()
45 | console.log('Server stopped')
46 | } catch (error) {
47 | console.error('Error in STDIO test:', error)
48 | }
49 | }
50 |
51 | testStdio()
```
--------------------------------------------------------------------------------
/debug-stdio.cjs:
--------------------------------------------------------------------------------
```
1 | const { StdioManager } = require('./dist/node/modules/stdio-manager.js')
2 | const { Logger } = require('./dist/node/utils/logger.js')
3 |
4 | // Set logger to debug level
5 | Logger.configure({ level: 'debug' })
6 |
7 | async function testStdio() {
8 | const stdioManager = new StdioManager()
9 | const serverId = 'test-stdio-server'
10 | const filePath = './examples/stdio-mcp-server.cjs'
11 |
12 | try {
13 | console.log('Starting STDIO server...')
14 | const serverProcess = await stdioManager.startServer(serverId, filePath)
15 | console.log('STDIO server started successfully:', serverProcess)
16 |
17 | // Send a simple request to see if we can communicate
18 | console.log('Sending initialize request...')
19 | const initializeRequestId = Date.now()
20 | const initializeRequest = {
21 | jsonrpc: "2.0",
22 | id: initializeRequestId,
23 | method: "initialize",
24 | params: {
25 | protocolVersion: "2025-06-18",
26 | capabilities: {},
27 | clientInfo: {
28 | name: "debug-script",
29 | version: "1.0.0"
30 | }
31 | }
32 | }
33 |
34 | await stdioManager.sendMessage(serverId, initializeRequest)
35 | console.log('Initialize request sent')
36 |
37 | // Wait for response
38 | console.log('Waiting for response...')
39 | const response = await stdioManager.waitForResponse(serverId, initializeRequestId, 5000) // 5 second timeout
40 | console.log('Received response:', response)
41 |
42 | // Stop the server
43 | console.log('Stopping server...')
44 | await serverProcess.stop()
45 | console.log('Server stopped')
46 | } catch (error) {
47 | console.error('Error in STDIO test:', error)
48 | }
49 | }
50 |
51 | testStdio()
```
--------------------------------------------------------------------------------
/tests/utils/fake-express.ts:
--------------------------------------------------------------------------------
```typescript
1 | type Handler = (req: any, res: any) => void | Promise<void>
2 |
3 | export class FakeExpressApp {
4 | routes: Record<string, { method: 'GET'|'POST'; handler: Handler }> = {}
5 | use(_arg: any): void { /* ignore middleware */ }
6 | get(path: string, handler: Handler): void { this.routes[`GET ${path}`] = { method: 'GET', handler } }
7 | post(path: string, handler: Handler): void { this.routes[`POST ${path}`] = { method: 'POST', handler } }
8 | async invoke(method: 'GET'|'POST', path: string, options?: { query?: Record<string,string>, headers?: Record<string,string>, body?: any }) {
9 | const key = `${method} ${path}`
10 | const route = this.routes[key]
11 | if (!route) throw new Error(`Route not found: ${key}`)
12 | const req = {
13 | method,
14 | query: options?.query ?? {},
15 | headers: options?.headers ?? {},
16 | body: options?.body ?? undefined,
17 | protocol: 'http',
18 | get: (h: string) => (options?.headers?.[h.toLowerCase()] ?? options?.headers?.[h] ?? undefined),
19 | }
20 | let statusCode = 200
21 | let sentHeaders: Record<string,string> = {}
22 | let payload: any
23 | const res = {
24 | set: (k: string, v: string) => { sentHeaders[k.toLowerCase()] = v },
25 | status: (c: number) => { statusCode = c; return res },
26 | send: (b: any) => { payload = b },
27 | json: (b: any) => { sentHeaders['content-type'] = 'application/json'; payload = JSON.stringify(b) },
28 | redirect: (loc: string) => { statusCode = 302; sentHeaders['location'] = loc; payload = '' },
29 | }
30 | await route.handler(req, res)
31 | return { status: statusCode, headers: sentHeaders, body: payload }
32 | }
33 | }
34 |
35 |
```
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/_metadata.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "hash": "3c026b6f",
3 | "configHash": "1d5e3756",
4 | "lockfileHash": "b26962c5",
5 | "browserHash": "5431331e",
6 | "optimized": {
7 | "vue": {
8 | "src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
9 | "file": "vue.js",
10 | "fileHash": "7cfbfc66",
11 | "needsInterop": false
12 | },
13 | "vitepress > @vue/devtools-api": {
14 | "src": "../../../../node_modules/@vue/devtools-api/dist/index.js",
15 | "file": "vitepress___@vue_devtools-api.js",
16 | "fileHash": "89ef8781",
17 | "needsInterop": false
18 | },
19 | "vitepress > @vueuse/core": {
20 | "src": "../../../../node_modules/@vueuse/core/index.mjs",
21 | "file": "vitepress___@vueuse_core.js",
22 | "fileHash": "12a4fb0b",
23 | "needsInterop": false
24 | },
25 | "vitepress > @vueuse/integrations/useFocusTrap": {
26 | "src": "../../../../node_modules/@vueuse/integrations/useFocusTrap.mjs",
27 | "file": "vitepress___@vueuse_integrations_useFocusTrap.js",
28 | "fileHash": "b5a95a87",
29 | "needsInterop": false
30 | },
31 | "vitepress > mark.js/src/vanilla.js": {
32 | "src": "../../../../node_modules/mark.js/src/vanilla.js",
33 | "file": "vitepress___mark__js_src_vanilla__js.js",
34 | "fileHash": "38188df1",
35 | "needsInterop": false
36 | },
37 | "vitepress > minisearch": {
38 | "src": "../../../../node_modules/minisearch/dist/es/index.js",
39 | "file": "vitepress___minisearch.js",
40 | "fileHash": "996cffe0",
41 | "needsInterop": false
42 | }
43 | },
44 | "chunks": {
45 | "chunk-P2XGSYO7": {
46 | "file": "chunk-P2XGSYO7.js"
47 | },
48 | "chunk-HVR2FF6M": {
49 | "file": "chunk-HVR2FF6M.js"
50 | }
51 | }
52 | }
```
--------------------------------------------------------------------------------
/tests/e2e/flow-controller.worker.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import '../setup/test-setup.js'
2 | import test from 'node:test'
3 | import assert from 'node:assert/strict'
4 | import { OAuthFlowController } from '../../src/oauth/flow-controller.js'
5 | import { createMockServer } from '../utils/mock-http.js'
6 |
7 | test('OAuthFlowController Worker-style authorize and callback', async () => {
8 | const tokenSrv = await createMockServer([
9 | { method: 'POST', path: '/token', handler: () => ({ body: { access_token: 'AT', expires_in: 60, scope: 'openid' } }) },
10 | ])
11 | try {
12 | const cfg = {
13 | master_oauth: {
14 | authorization_endpoint: tokenSrv.url + '/authorize',
15 | token_endpoint: tokenSrv.url + '/token',
16 | client_id: 'cid',
17 | redirect_uri: 'http://localhost/oauth/callback',
18 | scopes: ['openid'],
19 | },
20 | hosting: { platform: 'cloudflare-workers', base_url: 'http://localhost' },
21 | servers: [],
22 | }
23 | const ctrl = new OAuthFlowController({ getConfig: () => cfg as any })
24 | const base = 'http://localhost'
25 | const authRes = await ctrl.handleRequest(new Request(base + '/oauth/authorize?provider=master', { method: 'GET' }))
26 | assert.equal(authRes.status, 200)
27 | const html = await authRes.text()
28 | const m = html.match(/url=([^"\s]+)/)
29 | assert.ok(m && m[1])
30 | const urlStr = m[1].replace(/&/g, '&') // Decode HTML entities
31 | const state = new URL(urlStr).searchParams.get('state')!
32 | const cbRes = await ctrl.handleRequest(new Request(base + `/oauth/callback?state=${encodeURIComponent(state)}&code=good&provider=master`, { method: 'GET' }))
33 | assert.equal(cbRes.status, 200)
34 | } finally {
35 | await tokenSrv.close()
36 | }
37 | })
38 |
39 |
```
--------------------------------------------------------------------------------
/tests/unit/oauth.pkce-state.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import '../setup/test-setup.js'
2 | import test from 'node:test'
3 | import assert from 'node:assert/strict'
4 | import { PKCEManager } from '../../src/oauth/pkce-manager.js'
5 | import { StateManager } from '../../src/oauth/state-manager.js'
6 | import { FlowValidator } from '../../src/oauth/flow-validator.js'
7 |
8 | test('PKCEManager generates and verifies', async () => {
9 | const pkce = new PKCEManager({ ttlMs: 1000 })
10 | const state = 'abc'
11 | const { challenge, method, verifier } = await pkce.generate(state)
12 | assert.ok(challenge.length > 16)
13 | assert.equal(method, 'S256')
14 | const v = pkce.getVerifier(state)
15 | assert.equal(v, verifier)
16 | // consumed; second time should be undefined
17 | assert.equal(pkce.getVerifier(state), undefined)
18 | })
19 |
20 | test('StateManager create/consume with TTL', async () => {
21 | const sm = new StateManager({ ttlMs: 10 })
22 | const s = sm.create({ provider: 'p', issuedAt: 0 } as any)
23 | const peek = sm.peek(s)
24 | assert.ok(peek && peek.provider === 'p')
25 | const used = sm.consume(s)
26 | assert.ok(used)
27 | assert.equal(sm.consume(s), null)
28 | })
29 |
30 | test('FlowValidator validateReturnTo prevents open redirects', () => {
31 | const fv = new FlowValidator(() => ({
32 | master_oauth: {
33 | authorization_endpoint: 'https://a', token_endpoint: 'https://t', client_id: 'x', redirect_uri: 'http://l', scopes: ['openid']
34 | }, hosting: { platform: 'node' }, servers: []
35 | } as any))
36 | assert.equal(fv.validateReturnTo('http://evil.com', 'http://localhost:3000'), undefined)
37 | assert.equal(fv.validateReturnTo('http://localhost:3000/path', 'http://localhost:3000'), '/path')
38 | assert.equal(fv.validateReturnTo('/ok', 'http://x'), '/ok')
39 | })
40 |
41 |
```