mcpmake 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +691 -0
- package/bin/mcpmake.mjs +2 -0
- package/dist/analyzer/auth-detector.d.ts +12 -0
- package/dist/analyzer/auth-detector.js +142 -0
- package/dist/analyzer/dom-parser.d.ts +10 -0
- package/dist/analyzer/dom-parser.js +259 -0
- package/dist/analyzer/goal-crawler.d.ts +25 -0
- package/dist/analyzer/goal-crawler.js +177 -0
- package/dist/analyzer/hybrid-detector.d.ts +28 -0
- package/dist/analyzer/hybrid-detector.js +96 -0
- package/dist/analyzer/index.d.ts +12 -0
- package/dist/analyzer/index.js +8 -0
- package/dist/analyzer/screenshot-capture.d.ts +29 -0
- package/dist/analyzer/screenshot-capture.js +42 -0
- package/dist/analyzer/selector-builder.d.ts +19 -0
- package/dist/analyzer/selector-builder.js +199 -0
- package/dist/analyzer/semantic-analyzer.d.ts +13 -0
- package/dist/analyzer/semantic-analyzer.js +145 -0
- package/dist/analyzer/site-crawler.d.ts +38 -0
- package/dist/analyzer/site-crawler.js +235 -0
- package/dist/cloud/billing/billing-engine.d.ts +44 -0
- package/dist/cloud/billing/billing-engine.js +81 -0
- package/dist/cloud/billing/credit-store.d.ts +64 -0
- package/dist/cloud/billing/credit-store.js +168 -0
- package/dist/cloud/billing/index.d.ts +4 -0
- package/dist/cloud/billing/index.js +2 -0
- package/dist/cloud/billing/usage-store.d.ts +42 -0
- package/dist/cloud/billing/usage-store.js +85 -0
- package/dist/cloud/billing/usage-tracker.d.ts +38 -0
- package/dist/cloud/billing/usage-tracker.js +95 -0
- package/dist/cloud/build-pipeline.d.ts +39 -0
- package/dist/cloud/build-pipeline.js +310 -0
- package/dist/cloud/build-queue.d.ts +30 -0
- package/dist/cloud/build-queue.js +70 -0
- package/dist/cloud/caddy-manager.d.ts +18 -0
- package/dist/cloud/caddy-manager.js +97 -0
- package/dist/cloud/container-backend.d.ts +62 -0
- package/dist/cloud/container-backend.js +59 -0
- package/dist/cloud/container-manager.d.ts +64 -0
- package/dist/cloud/container-manager.js +301 -0
- package/dist/cloud/crypto.d.ts +27 -0
- package/dist/cloud/crypto.js +63 -0
- package/dist/cloud/db/index.d.ts +27 -0
- package/dist/cloud/db/index.js +53 -0
- package/dist/cloud/db/migrations.d.ts +12 -0
- package/dist/cloud/db/migrations.js +329 -0
- package/dist/cloud/db/pg-store.d.ts +45 -0
- package/dist/cloud/db/pg-store.js +336 -0
- package/dist/cloud/failure-tracker.d.ts +51 -0
- package/dist/cloud/failure-tracker.js +102 -0
- package/dist/cloud/idle-monitor.d.ts +30 -0
- package/dist/cloud/idle-monitor.js +70 -0
- package/dist/cloud/mailer.d.ts +21 -0
- package/dist/cloud/mailer.js +193 -0
- package/dist/cloud/mcp-proxy.d.ts +58 -0
- package/dist/cloud/mcp-proxy.js +203 -0
- package/dist/cloud/metric-samples.d.ts +43 -0
- package/dist/cloud/metric-samples.js +85 -0
- package/dist/cloud/metrics.d.ts +26 -0
- package/dist/cloud/metrics.js +59 -0
- package/dist/cloud/multipart.d.ts +26 -0
- package/dist/cloud/multipart.js +132 -0
- package/dist/cloud/observability.d.ts +27 -0
- package/dist/cloud/observability.js +98 -0
- package/dist/cloud/rate-limiter.d.ts +31 -0
- package/dist/cloud/rate-limiter.js +58 -0
- package/dist/cloud/request-security.d.ts +5 -0
- package/dist/cloud/request-security.js +74 -0
- package/dist/cloud/resource-monitor.d.ts +69 -0
- package/dist/cloud/resource-monitor.js +130 -0
- package/dist/cloud/secret-store.d.ts +38 -0
- package/dist/cloud/secret-store.js +103 -0
- package/dist/cloud/security.d.ts +26 -0
- package/dist/cloud/security.js +142 -0
- package/dist/cloud/server.d.ts +21 -0
- package/dist/cloud/server.js +1079 -0
- package/dist/cloud/shared-state.d.ts +72 -0
- package/dist/cloud/shared-state.js +159 -0
- package/dist/cloud/ssrf.d.ts +43 -0
- package/dist/cloud/ssrf.js +150 -0
- package/dist/cloud/store.d.ts +41 -0
- package/dist/cloud/store.js +75 -0
- package/dist/cloud/stripe.d.ts +78 -0
- package/dist/cloud/stripe.js +317 -0
- package/dist/cloud/telemetry-store.d.ts +53 -0
- package/dist/cloud/telemetry-store.js +108 -0
- package/dist/cloud/web/auth.d.ts +225 -0
- package/dist/cloud/web/auth.js +555 -0
- package/dist/cloud/web/charts.d.ts +70 -0
- package/dist/cloud/web/charts.js +178 -0
- package/dist/cloud/web/csrf.d.ts +14 -0
- package/dist/cloud/web/csrf.js +22 -0
- package/dist/cloud/web/docs.d.ts +40 -0
- package/dist/cloud/web/docs.js +174 -0
- package/dist/cloud/web/router.d.ts +25 -0
- package/dist/cloud/web/router.js +1921 -0
- package/dist/cloud/web/static/alpine.min.js +5 -0
- package/dist/cloud/web/static/favicon.svg +4 -0
- package/dist/cloud/web/static/htmx-sse.js +290 -0
- package/dist/cloud/web/static/htmx.min.js +1 -0
- package/dist/cloud/web/static/style.css +2683 -0
- package/dist/cloud/web/static-server.d.ts +13 -0
- package/dist/cloud/web/static-server.js +73 -0
- package/dist/cloud/web/template-engine.d.ts +27 -0
- package/dist/cloud/web/template-engine.js +146 -0
- package/dist/cloud/web/templates/layouts/admin.hbs +57 -0
- package/dist/cloud/web/templates/layouts/auth.hbs +138 -0
- package/dist/cloud/web/templates/layouts/base.hbs +16 -0
- package/dist/cloud/web/templates/layouts/dashboard.hbs +39 -0
- package/dist/cloud/web/templates/layouts/landing.hbs +82 -0
- package/dist/cloud/web/templates/pages/admin/overview.hbs +123 -0
- package/dist/cloud/web/templates/pages/admin/servers.hbs +129 -0
- package/dist/cloud/web/templates/pages/admin/telemetry.hbs +39 -0
- package/dist/cloud/web/templates/pages/admin/user-edit.hbs +91 -0
- package/dist/cloud/web/templates/pages/admin/users.hbs +179 -0
- package/dist/cloud/web/templates/pages/auth/forgot-password.hbs +25 -0
- package/dist/cloud/web/templates/pages/auth/login.hbs +33 -0
- package/dist/cloud/web/templates/pages/auth/register.hbs +32 -0
- package/dist/cloud/web/templates/pages/auth/reset-password.hbs +34 -0
- package/dist/cloud/web/templates/pages/dashboard/billing.hbs +140 -0
- package/dist/cloud/web/templates/pages/dashboard/create.hbs +173 -0
- package/dist/cloud/web/templates/pages/dashboard/index.hbs +8 -0
- package/dist/cloud/web/templates/pages/dashboard/server-detail.hbs +280 -0
- package/dist/cloud/web/templates/pages/dashboard/server-logs.hbs +35 -0
- package/dist/cloud/web/templates/pages/dashboard/server-metrics.hbs +63 -0
- package/dist/cloud/web/templates/pages/dashboard/servers-partial.hbs +21 -0
- package/dist/cloud/web/templates/pages/dashboard/servers.hbs +44 -0
- package/dist/cloud/web/templates/pages/docs/show.hbs +16 -0
- package/dist/cloud/web/templates/pages/errors/404.hbs +9 -0
- package/dist/cloud/web/templates/pages/errors/500.hbs +8 -0
- package/dist/cloud/web/templates/pages/landing/index.hbs +223 -0
- package/dist/cloud/web/templates/pages/legal/privacy.hbs +71 -0
- package/dist/cloud/web/templates/pages/legal/terms.hbs +73 -0
- package/dist/cloud/web/templates/partials/admin-stats.hbs +52 -0
- package/dist/cloud/web/templates/partials/flash-message.hbs +6 -0
- package/dist/cloud/web/templates/partials/pricing-table.hbs +103 -0
- package/dist/cloud/web/templates/partials/server-card.hbs +19 -0
- package/dist/cloud/web/templates/partials/status-badge.hbs +1 -0
- package/dist/commands/bundle.d.ts +18 -0
- package/dist/commands/bundle.js +82 -0
- package/dist/commands/ci.d.ts +25 -0
- package/dist/commands/ci.js +149 -0
- package/dist/commands/deploy.d.ts +24 -0
- package/dist/commands/deploy.js +145 -0
- package/dist/commands/diff.d.ts +18 -0
- package/dist/commands/diff.js +185 -0
- package/dist/commands/from/describe.d.ts +65 -0
- package/dist/commands/from/describe.js +173 -0
- package/dist/commands/from/har.d.ts +81 -0
- package/dist/commands/from/har.js +255 -0
- package/dist/commands/from/openapi.d.ts +105 -0
- package/dist/commands/from/openapi.js +302 -0
- package/dist/commands/from/postman.d.ts +51 -0
- package/dist/commands/from/postman.js +146 -0
- package/dist/commands/from/target-support.d.ts +11 -0
- package/dist/commands/from/target-support.js +33 -0
- package/dist/commands/from/url.d.ts +75 -0
- package/dist/commands/from/url.js +244 -0
- package/dist/commands/from/website.d.ts +75 -0
- package/dist/commands/from/website.js +284 -0
- package/dist/commands/lint.d.ts +24 -0
- package/dist/commands/lint.js +184 -0
- package/dist/commands/merge.d.ts +18 -0
- package/dist/commands/merge.js +161 -0
- package/dist/commands/publish.d.ts +27 -0
- package/dist/commands/publish.js +334 -0
- package/dist/commands/rescan.d.ts +40 -0
- package/dist/commands/rescan.js +255 -0
- package/dist/commands/update.d.ts +14 -0
- package/dist/commands/update.js +87 -0
- package/dist/commands/verify.d.ts +14 -0
- package/dist/commands/verify.js +71 -0
- package/dist/config/configurable-command.d.ts +13 -0
- package/dist/config/configurable-command.js +70 -0
- package/dist/config/mcpmake-config.d.ts +68 -0
- package/dist/config/mcpmake-config.js +207 -0
- package/dist/docs/cli.md +400 -0
- package/dist/docs/mcp-2026-07-28-migration.md +78 -0
- package/dist/docs/migrate-from-stainless.md +94 -0
- package/dist/docs/quickstart.md +166 -0
- package/dist/docs/show-hn.md +26 -0
- package/dist/docs/website-servers.md +169 -0
- package/dist/emitter/code-writer.d.ts +8 -0
- package/dist/emitter/code-writer.js +25 -0
- package/dist/emitter/index.d.ts +32 -0
- package/dist/emitter/index.js +280 -0
- package/dist/emitter/mcpb-bundler.d.ts +31 -0
- package/dist/emitter/mcpb-bundler.js +172 -0
- package/dist/emitter/project-scaffolder.d.ts +4 -0
- package/dist/emitter/project-scaffolder.js +89 -0
- package/dist/emitter/python-template-loader.d.ts +4 -0
- package/dist/emitter/python-template-loader.js +30 -0
- package/dist/emitter/python-templates/dockerfile.hbs +14 -0
- package/dist/emitter/python-templates/env.example.hbs +6 -0
- package/dist/emitter/python-templates/requirements.txt.hbs +4 -0
- package/dist/emitter/python-templates/server.py.hbs +77 -0
- package/dist/emitter/site-scaffolder.d.ts +13 -0
- package/dist/emitter/site-scaffolder.js +70 -0
- package/dist/emitter/site-template-loader.d.ts +5 -0
- package/dist/emitter/site-template-loader.js +47 -0
- package/dist/emitter/site-templates/browser-manager.ts.hbs +233 -0
- package/dist/emitter/site-templates/config.ts.hbs +28 -0
- package/dist/emitter/site-templates/dockerfile.hbs +31 -0
- package/dist/emitter/site-templates/env.example.hbs +19 -0
- package/dist/emitter/site-templates/package.json.hbs +26 -0
- package/dist/emitter/site-templates/server-main-http.ts.hbs +108 -0
- package/dist/emitter/site-templates/server-main.ts.hbs +23 -0
- package/dist/emitter/site-templates/tool-handler-action.ts.hbs +86 -0
- package/dist/emitter/site-templates/tool-handler-form.ts.hbs +116 -0
- package/dist/emitter/site-templates/tool-handler-lifecycle.ts.hbs +146 -0
- package/dist/emitter/site-templates/tool-index.ts.hbs +11 -0
- package/dist/emitter/template-loader.d.ts +1 -0
- package/dist/emitter/template-loader.js +27 -0
- package/dist/emitter/templates/auth-provider.ts.hbs +57 -0
- package/dist/emitter/templates/config.ts.hbs +63 -0
- package/dist/emitter/templates/discovery.ts.hbs +301 -0
- package/dist/emitter/templates/dockerfile.hbs +34 -0
- package/dist/emitter/templates/env.example.hbs +28 -0
- package/dist/emitter/templates/gitignore.hbs +5 -0
- package/dist/emitter/templates/http-executor.ts.hbs +117 -0
- package/dist/emitter/templates/oauth.ts.hbs +188 -0
- package/dist/emitter/templates/package.json.hbs +25 -0
- package/dist/emitter/templates/prompts.ts.hbs +22 -0
- package/dist/emitter/templates/readme.md.hbs +123 -0
- package/dist/emitter/templates/resources.ts.hbs +63 -0
- package/dist/emitter/templates/server-main-http.ts.hbs +407 -0
- package/dist/emitter/templates/server-main.ts.hbs +40 -0
- package/dist/emitter/templates/task-handlers.ts.hbs +189 -0
- package/dist/emitter/templates/task-manager.ts.hbs +139 -0
- package/dist/emitter/templates/task-sse.ts.hbs +105 -0
- package/dist/emitter/templates/tool-handler.ts.hbs +124 -0
- package/dist/emitter/templates/tool-index.ts.hbs +11 -0
- package/dist/emitter/templates/tool-test.ts.hbs +57 -0
- package/dist/emitter/templates/trace.ts.hbs +79 -0
- package/dist/emitter/templates/tsconfig.json.hbs +16 -0
- package/dist/emitter/templates/types.ts.hbs +5 -0
- package/dist/emitter/worker-template-loader.d.ts +5 -0
- package/dist/emitter/worker-template-loader.js +33 -0
- package/dist/emitter/worker-templates/config.ts.hbs +54 -0
- package/dist/emitter/worker-templates/dev-vars.example.hbs +10 -0
- package/dist/emitter/worker-templates/gitignore.hbs +6 -0
- package/dist/emitter/worker-templates/package.json.hbs +24 -0
- package/dist/emitter/worker-templates/readme.md.hbs +53 -0
- package/dist/emitter/worker-templates/server.test.ts.hbs +20 -0
- package/dist/emitter/worker-templates/tool-handler.ts.hbs +85 -0
- package/dist/emitter/worker-templates/tool-index.ts.hbs +28 -0
- package/dist/emitter/worker-templates/tsconfig.json.hbs +17 -0
- package/dist/emitter/worker-templates/worker.ts.hbs +242 -0
- package/dist/emitter/worker-templates/wrangler.toml.hbs +19 -0
- package/dist/generator/spec-generator.d.ts +6 -0
- package/dist/generator/spec-generator.js +50 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +64 -0
- package/dist/parser/har-filter.d.ts +8 -0
- package/dist/parser/har-filter.js +71 -0
- package/dist/parser/har-loader.d.ts +2 -0
- package/dist/parser/har-loader.js +14 -0
- package/dist/parser/har-normalizer.d.ts +20 -0
- package/dist/parser/har-normalizer.js +78 -0
- package/dist/parser/index.d.ts +10 -0
- package/dist/parser/index.js +6 -0
- package/dist/parser/openapi-loader.d.ts +6 -0
- package/dist/parser/openapi-loader.js +308 -0
- package/dist/parser/operation-extractor.d.ts +13 -0
- package/dist/parser/operation-extractor.js +155 -0
- package/dist/parser/overlay-loader.d.ts +10 -0
- package/dist/parser/overlay-loader.js +184 -0
- package/dist/parser/postman-loader.d.ts +9 -0
- package/dist/parser/postman-loader.js +106 -0
- package/dist/parser/schema-converter.d.ts +12 -0
- package/dist/parser/schema-converter.js +117 -0
- package/dist/plugins/adapter.d.ts +40 -0
- package/dist/plugins/adapter.js +15 -0
- package/dist/plugins/loader.d.ts +25 -0
- package/dist/plugins/loader.js +58 -0
- package/dist/pricing.d.ts +55 -0
- package/dist/pricing.js +133 -0
- package/dist/providers/index.d.ts +15 -0
- package/dist/providers/index.js +56 -0
- package/dist/recorder/browser-recorder.d.ts +22 -0
- package/dist/recorder/browser-recorder.js +205 -0
- package/dist/registry/official-registry.d.ts +90 -0
- package/dist/registry/official-registry.js +129 -0
- package/dist/rescan/diff-engine.d.ts +5 -0
- package/dist/rescan/diff-engine.js +312 -0
- package/dist/rescan/index.d.ts +3 -0
- package/dist/rescan/index.js +2 -0
- package/dist/rescan/rescan-runner.d.ts +42 -0
- package/dist/rescan/rescan-runner.js +69 -0
- package/dist/rescan/rescan-scheduler.d.ts +41 -0
- package/dist/rescan/rescan-scheduler.js +179 -0
- package/dist/site-transformer/browser-tools.d.ts +10 -0
- package/dist/site-transformer/browser-tools.js +59 -0
- package/dist/site-transformer/index.d.ts +2 -0
- package/dist/site-transformer/index.js +2 -0
- package/dist/site-transformer/selector-healer.d.ts +8 -0
- package/dist/site-transformer/selector-healer.js +106 -0
- package/dist/site-transformer/tool-generator.d.ts +13 -0
- package/dist/site-transformer/tool-generator.js +245 -0
- package/dist/transformer/auth-detector.d.ts +13 -0
- package/dist/transformer/auth-detector.js +90 -0
- package/dist/transformer/catalog-builder.d.ts +18 -0
- package/dist/transformer/catalog-builder.js +56 -0
- package/dist/transformer/client-compat.d.ts +6 -0
- package/dist/transformer/client-compat.js +44 -0
- package/dist/transformer/har-clusterer.d.ts +9 -0
- package/dist/transformer/har-clusterer.js +27 -0
- package/dist/transformer/har-dedup.d.ts +10 -0
- package/dist/transformer/har-dedup.js +81 -0
- package/dist/transformer/har-schema-inferrer.d.ts +15 -0
- package/dist/transformer/har-schema-inferrer.js +90 -0
- package/dist/transformer/har-to-operations.d.ts +13 -0
- package/dist/transformer/har-to-operations.js +192 -0
- package/dist/transformer/index.d.ts +8 -0
- package/dist/transformer/index.js +6 -0
- package/dist/transformer/llm-namer.d.ts +6 -0
- package/dist/transformer/llm-namer.js +59 -0
- package/dist/transformer/naming.d.ts +4 -0
- package/dist/transformer/naming.js +30 -0
- package/dist/transformer/operation-filter.d.ts +13 -0
- package/dist/transformer/operation-filter.js +52 -0
- package/dist/transformer/resource-builder.d.ts +12 -0
- package/dist/transformer/resource-builder.js +80 -0
- package/dist/transformer/schema-merger.d.ts +14 -0
- package/dist/transformer/schema-merger.js +65 -0
- package/dist/transformer/tool-builder.d.ts +3 -0
- package/dist/transformer/tool-builder.js +114 -0
- package/dist/types/index.d.ts +131 -0
- package/dist/types/index.js +1 -0
- package/dist/types/site.d.ts +284 -0
- package/dist/types/site.js +8 -0
- package/dist/utils/fail.d.ts +48 -0
- package/dist/utils/fail.js +204 -0
- package/dist/utils/fs.d.ts +5 -0
- package/dist/utils/fs.js +28 -0
- package/dist/utils/interactive.d.ts +6 -0
- package/dist/utils/interactive.js +30 -0
- package/dist/utils/logger.d.ts +1 -0
- package/dist/utils/logger.js +2 -0
- package/dist/utils/sanitize.d.ts +28 -0
- package/dist/utils/sanitize.js +44 -0
- package/dist/utils/watcher.d.ts +11 -0
- package/dist/utils/watcher.js +36 -0
- package/package.json +65 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"moduleResolution": "Node16",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"rootDir": "src",
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*.ts"],
|
|
15
|
+
"exclude": ["node_modules", "dist"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template loader for Cloudflare Workers MCP server templates.
|
|
3
|
+
* Parallel to template-loader.ts but reads from worker-templates/.
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { resolve, dirname } from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import Handlebars from 'handlebars';
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const WORKER_TEMPLATE_DIR = resolve(__dirname, 'worker-templates');
|
|
11
|
+
const workerTemplateCache = new Map();
|
|
12
|
+
// Register helpers (same as template-loader.ts — Handlebars helpers are global).
|
|
13
|
+
// These may already be registered; Handlebars silently overwrites, which is fine.
|
|
14
|
+
Handlebars.registerHelper('eq', function (a, b) {
|
|
15
|
+
return a === b;
|
|
16
|
+
});
|
|
17
|
+
Handlebars.registerHelper('capitalize', function (str) {
|
|
18
|
+
if (!str)
|
|
19
|
+
return str;
|
|
20
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
21
|
+
});
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
23
|
+
export function renderWorkerTemplate(name, data) {
|
|
24
|
+
if (!workerTemplateCache.has(name)) {
|
|
25
|
+
const templatePath = resolve(WORKER_TEMPLATE_DIR, `${name}.hbs`);
|
|
26
|
+
if (!templatePath.startsWith(WORKER_TEMPLATE_DIR + '/')) {
|
|
27
|
+
throw new Error(`Invalid worker template name: ${name}`);
|
|
28
|
+
}
|
|
29
|
+
const raw = readFileSync(templatePath, 'utf-8');
|
|
30
|
+
workerTemplateCache.set(name, Handlebars.compile(raw, { noEscape: true }));
|
|
31
|
+
}
|
|
32
|
+
return workerTemplateCache.get(name)(data);
|
|
33
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime configuration, sourced from the Workers `env` binding (not the Node
|
|
3
|
+
* process environment). Same `AppConfig` shape the shared HTTP executor + auth
|
|
4
|
+
* modules consume, so those are reused verbatim from the Node templates.
|
|
5
|
+
*/
|
|
6
|
+
export interface AppConfig {
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
{{#each authSchemes}}
|
|
9
|
+
{{#if (eq type "apiKey")}}
|
|
10
|
+
apiKey?: string;
|
|
11
|
+
{{/if}}
|
|
12
|
+
{{#if (eq type "http-bearer")}}
|
|
13
|
+
bearerToken?: string;
|
|
14
|
+
{{/if}}
|
|
15
|
+
{{#if (eq type "http-basic")}}
|
|
16
|
+
basicUsername?: string;
|
|
17
|
+
basicPassword?: string;
|
|
18
|
+
{{/if}}
|
|
19
|
+
{{/each}}
|
|
20
|
+
maxRetries: number;
|
|
21
|
+
requestIntervalMs: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type EnvLike = Record<string, string | undefined>;
|
|
25
|
+
|
|
26
|
+
export function loadConfig(env: EnvLike): AppConfig {
|
|
27
|
+
const baseUrl = requireEnv(env, 'BASE_URL');
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
baseUrl: baseUrl.replace(/\/$/, ''),
|
|
31
|
+
{{#each authSchemes}}
|
|
32
|
+
{{#if (eq type "apiKey")}}
|
|
33
|
+
apiKey: env.API_KEY,
|
|
34
|
+
{{/if}}
|
|
35
|
+
{{#if (eq type "http-bearer")}}
|
|
36
|
+
bearerToken: env.BEARER_TOKEN,
|
|
37
|
+
{{/if}}
|
|
38
|
+
{{#if (eq type "http-basic")}}
|
|
39
|
+
basicUsername: env.BASIC_USERNAME,
|
|
40
|
+
basicPassword: env.BASIC_PASSWORD,
|
|
41
|
+
{{/if}}
|
|
42
|
+
{{/each}}
|
|
43
|
+
maxRetries: parseInt(env.MAX_RETRIES ?? '3', 10),
|
|
44
|
+
requestIntervalMs: parseInt(env.REQUEST_INTERVAL_MS ?? '100', 10),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function requireEnv(env: EnvLike, name: string): string {
|
|
49
|
+
const value = env[name];
|
|
50
|
+
if (!value) {
|
|
51
|
+
throw new Error(`Missing required environment variable: ${name}`);
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Local dev secrets for `wrangler dev`. Copy to `.dev.vars` (gitignored) and fill in.
|
|
2
|
+
# In production these are set with `wrangler secret put <NAME>` instead.
|
|
3
|
+
|
|
4
|
+
# Bearer token clients must send as `Authorization: Bearer <token>`.
|
|
5
|
+
# Leave empty to disable auth (local/dev only — never deploy without it).
|
|
6
|
+
MCP_AUTH_TOKEN=
|
|
7
|
+
{{#each authEnvVars}}
|
|
8
|
+
# {{description}}
|
|
9
|
+
{{name}}=
|
|
10
|
+
{{/each}}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{serverName}}",
|
|
3
|
+
"version": "{{serverVersion}}",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"private": true,
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "wrangler dev",
|
|
8
|
+
"deploy": "wrangler deploy",
|
|
9
|
+
"start": "wrangler dev",
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
11
|
+
"test": "vitest run"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"zod": "^3.24.0",
|
|
15
|
+
"zod-to-json-schema": "^3.24.1"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@cloudflare/workers-types": "^4.20241127.0",
|
|
19
|
+
"@types/node": "^22.0.0",
|
|
20
|
+
"typescript": "^5.8.0",
|
|
21
|
+
"vitest": "^3.1.0",
|
|
22
|
+
"wrangler": "^3.90.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# {{serverName}}
|
|
2
|
+
|
|
3
|
+
An MCP server for **{{baseUrl}}**, generated by [mcpmake](https://mcpmake.dev) for
|
|
4
|
+
**Cloudflare Workers** (`--target cloudflare`).
|
|
5
|
+
|
|
6
|
+
It runs as a stateless [Fetch handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/):
|
|
7
|
+
every `POST /mcp` carries one self-contained JSON-RPC message and gets one JSON
|
|
8
|
+
response — which is exactly the MCP 2026-07-28 stateless transport, so no session
|
|
9
|
+
state is kept and any request can hit any edge location. Cloudflare's free plan
|
|
10
|
+
covers 100,000 requests/day.
|
|
11
|
+
|
|
12
|
+
## Tools
|
|
13
|
+
|
|
14
|
+
This server exposes **{{tools.length}}** tool(s). Run `tools/list` to see them.
|
|
15
|
+
|
|
16
|
+
## Develop
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install
|
|
20
|
+
cp .dev.vars.example .dev.vars # fill in MCP_AUTH_TOKEN + API credentials
|
|
21
|
+
npm run dev # wrangler dev — local Workers runtime
|
|
22
|
+
npm run typecheck
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Deploy
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# One-time: store each secret in Cloudflare (never commit them).
|
|
29
|
+
npx wrangler secret put MCP_AUTH_TOKEN # bearer token clients must present
|
|
30
|
+
{{#each authEnvVars}}
|
|
31
|
+
npx wrangler secret put {{name}}
|
|
32
|
+
{{/each}}
|
|
33
|
+
|
|
34
|
+
npx wrangler deploy
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`BASE_URL` and HTTP tuning live in `wrangler.toml` under `[vars]`; secrets are
|
|
38
|
+
set with `wrangler secret put` and injected via the `env` binding at runtime.
|
|
39
|
+
|
|
40
|
+
## Connect a client
|
|
41
|
+
|
|
42
|
+
Point an MCP client at `https://<your-worker>.workers.dev/mcp` with the header
|
|
43
|
+
`Authorization: Bearer <MCP_AUTH_TOKEN>`. Health checks: `GET /health`, `GET /ready`.
|
|
44
|
+
|
|
45
|
+
## Notes & limits (Workers target)
|
|
46
|
+
|
|
47
|
+
- **Auth:** every path except `/health` and `/ready` requires the bearer token
|
|
48
|
+
(constant-time check) when `MCP_AUTH_TOKEN` is set.
|
|
49
|
+
- **Stateless only:** there is no SSE stream or `Mcp-Session-Id` session; `GET`/
|
|
50
|
+
`DELETE /mcp` are not used. Long-running tools resolve within the request (no
|
|
51
|
+
Tasks extension / background polling).
|
|
52
|
+
- Need MCP resources, prompts, the Tasks extension, or OAuth2 outbound auth?
|
|
53
|
+
Regenerate with `--target node` (the default) for the full feature set.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { tools } from '../src/tools/index.js';
|
|
3
|
+
|
|
4
|
+
describe('{{serverName}} worker tools', () => {
|
|
5
|
+
it('registers every tool with a usable definition', () => {
|
|
6
|
+
expect(tools.length).toBe({{tools.length}});
|
|
7
|
+
for (const { definition, handler } of tools) {
|
|
8
|
+
expect(definition.name).toMatch(/^[a-zA-Z0-9_-]+$/);
|
|
9
|
+
expect(typeof definition.description).toBe('string');
|
|
10
|
+
expect(typeof handler).toBe('function');
|
|
11
|
+
// The input schema must accept a safeParse call (used for tools/call validation).
|
|
12
|
+
expect(typeof definition.inputSchema.safeParse).toBe('function');
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('has unique tool names', () => {
|
|
17
|
+
const names = tools.map((t) => t.definition.name);
|
|
18
|
+
expect(new Set(names).size).toBe(names.length);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { executeRequest } from '../http.js';
|
|
3
|
+
import type { AppConfig } from '../config.js';
|
|
4
|
+
|
|
5
|
+
const inputSchema = {{{inputSchemaCode}}};
|
|
6
|
+
|
|
7
|
+
export const definition = {
|
|
8
|
+
name: '{{name}}',
|
|
9
|
+
title: '{{title}}',
|
|
10
|
+
description: `{{{description}}}`,
|
|
11
|
+
inputSchema,
|
|
12
|
+
{{#if annotations}}
|
|
13
|
+
annotations: {
|
|
14
|
+
{{#if annotations.readOnlyHint}}
|
|
15
|
+
readOnlyHint: true,
|
|
16
|
+
{{/if}}
|
|
17
|
+
{{#if annotations.destructiveHint}}
|
|
18
|
+
destructiveHint: true,
|
|
19
|
+
{{/if}}
|
|
20
|
+
{{#if annotations.idempotentHint}}
|
|
21
|
+
idempotentHint: true,
|
|
22
|
+
{{/if}}
|
|
23
|
+
} as Record<string, boolean>,
|
|
24
|
+
{{/if}}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export interface ToolResult {
|
|
28
|
+
content: { type: 'text'; text: string }[];
|
|
29
|
+
isError?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Execute this tool. On the Workers (stateless) target a tool always runs to
|
|
34
|
+
* completion within the request — there is no Tasks extension / background
|
|
35
|
+
* polling, so async operations resolve synchronously here.
|
|
36
|
+
*/
|
|
37
|
+
export async function handler(
|
|
38
|
+
input: Record<string, unknown>,
|
|
39
|
+
config: AppConfig,
|
|
40
|
+
): Promise<ToolResult> {
|
|
41
|
+
try {
|
|
42
|
+
const result = await runRequest(input, config);
|
|
43
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
44
|
+
} catch (error) {
|
|
45
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
46
|
+
return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function runRequest(input: Record<string, unknown>, config: AppConfig): Promise<unknown> {
|
|
51
|
+
const url = buildUrl(input, config.baseUrl);
|
|
52
|
+
|
|
53
|
+
const response = await executeRequest({
|
|
54
|
+
method: '{{method}}',
|
|
55
|
+
url,
|
|
56
|
+
{{#if hasRequestBody}}
|
|
57
|
+
body: input.body,
|
|
58
|
+
contentType: '{{requestBodyContentType}}',
|
|
59
|
+
{{/if}}
|
|
60
|
+
headers: {},
|
|
61
|
+
config,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
{{#if jqFilter}}
|
|
65
|
+
// Apply x-mcp-jq-filter to trim response before returning to agent
|
|
66
|
+
let result: unknown = response.data;
|
|
67
|
+
try {
|
|
68
|
+
const parts = '{{{jqFilter}}}'.split('.');
|
|
69
|
+
for (const part of parts) {
|
|
70
|
+
if (part && result != null && typeof result === 'object') {
|
|
71
|
+
result = (result as Record<string, unknown>)[part];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
result = response.data;
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
{{else}}
|
|
79
|
+
return response.data;
|
|
80
|
+
{{/if}}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function buildUrl(params: Record<string, unknown>, baseUrl: string): string {
|
|
84
|
+
{{{buildUrlBody}}}
|
|
85
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { AppConfig } from '../config.js';
|
|
3
|
+
{{#each tools}}
|
|
4
|
+
import {
|
|
5
|
+
definition as def{{capitalize functionName}},
|
|
6
|
+
handler as handler{{capitalize functionName}},
|
|
7
|
+
} from './{{fileName}}.js';
|
|
8
|
+
{{/each}}
|
|
9
|
+
|
|
10
|
+
export interface ToolEntry {
|
|
11
|
+
definition: {
|
|
12
|
+
name: string;
|
|
13
|
+
title: string;
|
|
14
|
+
description: string;
|
|
15
|
+
inputSchema: z.ZodTypeAny;
|
|
16
|
+
annotations?: Record<string, boolean>;
|
|
17
|
+
};
|
|
18
|
+
handler: (
|
|
19
|
+
input: Record<string, unknown>,
|
|
20
|
+
config: AppConfig,
|
|
21
|
+
) => Promise<{ content: { type: 'text'; text: string }[]; isError?: boolean }>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const tools: ToolEntry[] = [
|
|
25
|
+
{{#each tools}}
|
|
26
|
+
{ definition: def{{capitalize functionName}}, handler: handler{{capitalize functionName}} },
|
|
27
|
+
{{/each}}
|
|
28
|
+
];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"types": ["@cloudflare/workers-types", "node"],
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"noEmit": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*.ts", "test/**/*.ts"],
|
|
16
|
+
"exclude": ["node_modules"]
|
|
17
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Workers entry — stateless MCP server (Fetch handler).
|
|
3
|
+
*
|
|
4
|
+
* The MCP 2026-07-28 transport is stateless (no Mcp-Session-Id, no persistent
|
|
5
|
+
* connection): each `POST /mcp` carries one self-contained JSON-RPC message (or
|
|
6
|
+
* a batch) and receives one JSON response. That maps cleanly onto the Workers
|
|
7
|
+
* request/response model, so we dispatch JSON-RPC by hand here instead of using
|
|
8
|
+
* the SDK's StreamableHTTPServerTransport, which is bound to node:http req/res
|
|
9
|
+
* objects and cannot run on Workers.
|
|
10
|
+
*
|
|
11
|
+
* Secrets (API credentials + the MCP bearer token) arrive via the `env` binding,
|
|
12
|
+
* not process.env — set them with `wrangler secret put <NAME>`.
|
|
13
|
+
*/
|
|
14
|
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
15
|
+
import { loadConfig, type AppConfig } from './config.js';
|
|
16
|
+
import { tools, type ToolEntry } from './tools/index.js';
|
|
17
|
+
import { deriveContext, runWithTrace } from './trace.js';
|
|
18
|
+
|
|
19
|
+
export interface Env {
|
|
20
|
+
[key: string]: string | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const PROTOCOL_VERSION = '2025-06-18'; // initialize handshake (back-compat with current clients)
|
|
24
|
+
const PROTOCOL_REVISION = '2026-07-28'; // server/discover (SEP-2575)
|
|
25
|
+
const SERVER_INFO = { name: '{{serverName}}', version: '{{serverVersion}}' } as const;
|
|
26
|
+
const MAX_BODY = 4 * 1024 * 1024; // 4 MB
|
|
27
|
+
|
|
28
|
+
const toolByName = new Map<string, ToolEntry>(tools.map((t) => [t.definition.name, t]));
|
|
29
|
+
|
|
30
|
+
/* ── Constant-time bearer check ──────────────────────────────────────────
|
|
31
|
+
* HMACs both tokens to fixed-length digests with a random per-isolate key,
|
|
32
|
+
* then compares the bytes — constant time and independent of token length, so
|
|
33
|
+
* neither equality nor length leaks through timing. The key is created lazily
|
|
34
|
+
* on first use: Workers forbids generating random values in global scope, so it
|
|
35
|
+
* must be minted inside a request handler. */
|
|
36
|
+
let cmpKeyPromise: Promise<CryptoKey> | null = null;
|
|
37
|
+
function compareKey(): Promise<CryptoKey> {
|
|
38
|
+
if (!cmpKeyPromise) {
|
|
39
|
+
cmpKeyPromise = crypto.subtle.importKey(
|
|
40
|
+
'raw',
|
|
41
|
+
crypto.getRandomValues(new Uint8Array(32)),
|
|
42
|
+
{ name: 'HMAC', hash: 'SHA-256' },
|
|
43
|
+
false,
|
|
44
|
+
['sign'],
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return cmpKeyPromise;
|
|
48
|
+
}
|
|
49
|
+
async function safeEqual(a: string, b: string): Promise<boolean> {
|
|
50
|
+
const key = await compareKey();
|
|
51
|
+
const enc = new TextEncoder();
|
|
52
|
+
const da = new Uint8Array(await crypto.subtle.sign('HMAC', key, enc.encode(a)));
|
|
53
|
+
const db = new Uint8Array(await crypto.subtle.sign('HMAC', key, enc.encode(b)));
|
|
54
|
+
let diff = 0;
|
|
55
|
+
for (let i = 0; i < da.length; i++) diff |= da[i] ^ db[i];
|
|
56
|
+
return diff === 0;
|
|
57
|
+
}
|
|
58
|
+
async function isAuthorized(request: Request, expected: string | undefined): Promise<boolean> {
|
|
59
|
+
if (!expected) return true; // auth disabled when MCP_AUTH_TOKEN is unset (local/dev only)
|
|
60
|
+
const header = request.headers.get('authorization');
|
|
61
|
+
if (!header || !header.startsWith('Bearer ')) return false;
|
|
62
|
+
const presented = header.slice(7).trim();
|
|
63
|
+
if (!presented) return false;
|
|
64
|
+
return safeEqual(presented, expected);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* ── HTTP / CORS helpers ─────────────────────────────────────────────── */
|
|
68
|
+
function corsHeaders(origin?: string | null): Record<string, string> {
|
|
69
|
+
return {
|
|
70
|
+
'access-control-allow-origin': origin || '*',
|
|
71
|
+
'access-control-allow-methods': 'POST, GET, OPTIONS',
|
|
72
|
+
'access-control-allow-headers':
|
|
73
|
+
'authorization, content-type, mcp-method, mcp-name, mcp-protocol-version, traceparent, tracestate',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function json(body: unknown, status = 200, extra?: Record<string, string>): Response {
|
|
77
|
+
return new Response(JSON.stringify(body), {
|
|
78
|
+
status,
|
|
79
|
+
headers: { 'content-type': 'application/json', ...corsHeaders(), ...extra },
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function ok(id: unknown, result: unknown): object {
|
|
83
|
+
return { jsonrpc: '2.0', id: id ?? null, result };
|
|
84
|
+
}
|
|
85
|
+
function err(id: unknown, code: number, message: string): object {
|
|
86
|
+
return { jsonrpc: '2.0', id: id ?? null, error: { code, message } };
|
|
87
|
+
}
|
|
88
|
+
function rpcErrorResponse(id: unknown, code: number, message: string): Response {
|
|
89
|
+
return json(err(id, code, message));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* ── server/discover (SEP-2575) — VERIFY result shape against the final spec ── */
|
|
93
|
+
function discoverResult(): Record<string, unknown> {
|
|
94
|
+
return {
|
|
95
|
+
server: SERVER_INFO,
|
|
96
|
+
protocolRevision: PROTOCOL_REVISION,
|
|
97
|
+
capabilities: { tools: { count: tools.length } },
|
|
98
|
+
extensions: [],
|
|
99
|
+
cacheTtlSeconds: 300,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** A tool's `tools/list` entry: its input schema rendered as JSON Schema. */
|
|
104
|
+
function toListEntry(t: ToolEntry): Record<string, unknown> {
|
|
105
|
+
const schema = zodToJsonSchema(t.definition.inputSchema, {
|
|
106
|
+
$refStrategy: 'none',
|
|
107
|
+
}) as Record<string, unknown>;
|
|
108
|
+
delete schema.$schema;
|
|
109
|
+
return {
|
|
110
|
+
name: t.definition.name,
|
|
111
|
+
title: t.definition.title,
|
|
112
|
+
description: t.definition.description,
|
|
113
|
+
inputSchema: schema,
|
|
114
|
+
...(t.definition.annotations ? { annotations: t.definition.annotations } : {}),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function handleToolCall(
|
|
119
|
+
id: unknown,
|
|
120
|
+
params: { name?: unknown; arguments?: unknown } | undefined,
|
|
121
|
+
config: AppConfig,
|
|
122
|
+
): Promise<object> {
|
|
123
|
+
const name = typeof params?.name === 'string' ? params.name : '';
|
|
124
|
+
const entry = toolByName.get(name);
|
|
125
|
+
if (!entry) return err(id, -32602, `Unknown tool: ${name}`);
|
|
126
|
+
const parsed = entry.definition.inputSchema.safeParse(params?.arguments ?? {});
|
|
127
|
+
if (!parsed.success) {
|
|
128
|
+
return err(id, -32602, `Invalid arguments for ${name}: ${parsed.error.message}`);
|
|
129
|
+
}
|
|
130
|
+
const result = await entry.handler(parsed.data as Record<string, unknown>, config);
|
|
131
|
+
return ok(id, result);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Dispatch one JSON-RPC message. Returns null for notifications (no reply). */
|
|
135
|
+
async function handleRpc(
|
|
136
|
+
msg: { id?: unknown; method?: unknown; params?: unknown } | null,
|
|
137
|
+
config: AppConfig,
|
|
138
|
+
): Promise<object | null> {
|
|
139
|
+
const id = msg?.id;
|
|
140
|
+
const method = msg?.method;
|
|
141
|
+
switch (method) {
|
|
142
|
+
case 'initialize': {
|
|
143
|
+
const requested = (msg?.params as { protocolVersion?: unknown })?.protocolVersion;
|
|
144
|
+
return ok(id, {
|
|
145
|
+
protocolVersion: typeof requested === 'string' ? requested : PROTOCOL_VERSION,
|
|
146
|
+
capabilities: { tools: { listChanged: false } },
|
|
147
|
+
serverInfo: SERVER_INFO,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
case 'ping':
|
|
151
|
+
return ok(id, {});
|
|
152
|
+
case 'tools/list':
|
|
153
|
+
return ok(id, { tools: tools.map(toListEntry) });
|
|
154
|
+
case 'tools/call':
|
|
155
|
+
return handleToolCall(id, msg?.params as { name?: unknown; arguments?: unknown }, config);
|
|
156
|
+
case 'server/discover':
|
|
157
|
+
return ok(id, discoverResult());
|
|
158
|
+
default:
|
|
159
|
+
// Notifications (e.g. notifications/initialized) carry no id and get no reply.
|
|
160
|
+
if (id === undefined || id === null) return null;
|
|
161
|
+
return err(id, -32601, `Method not found: ${String(method)}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export default {
|
|
166
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
167
|
+
const url = new URL(request.url);
|
|
168
|
+
const origin = request.headers.get('origin');
|
|
169
|
+
|
|
170
|
+
if (request.method === 'OPTIONS') {
|
|
171
|
+
return new Response(null, { status: 204, headers: corsHeaders(origin) });
|
|
172
|
+
}
|
|
173
|
+
if (url.pathname === '/health') return json({ status: 'ok' });
|
|
174
|
+
if (url.pathname === '/ready') return json({ status: 'ready' });
|
|
175
|
+
|
|
176
|
+
// Bearer authentication (every path except /health and /ready).
|
|
177
|
+
if (!(await isAuthorized(request, env.MCP_AUTH_TOKEN))) {
|
|
178
|
+
return json({ error: 'Unauthorized: missing or invalid bearer token' }, 401, {
|
|
179
|
+
'www-authenticate': 'Bearer',
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Routing-header observability (SEP-2243, additive — never rejects).
|
|
184
|
+
const mcpMethod = request.headers.get('mcp-method');
|
|
185
|
+
const mcpName = request.headers.get('mcp-name');
|
|
186
|
+
if (mcpMethod || mcpName) {
|
|
187
|
+
console.log(JSON.stringify({ level: 'info', msg: 'mcp-meta-headers', mcpMethod, mcpName }));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (url.pathname !== '/mcp') return json({ error: 'Not found' }, 404);
|
|
191
|
+
if (request.method !== 'POST') {
|
|
192
|
+
// Stateless: no SSE stream to open (GET) or session to delete (DELETE).
|
|
193
|
+
return rpcErrorResponse(null, -32601, 'Only POST is supported on this stateless server');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Origin guard (DNS-rebinding protection). Enforced only when configured —
|
|
197
|
+
// a Workers endpoint is public and gated by the bearer token, not by origin.
|
|
198
|
+
const allowedOrigin = env.ALLOWED_ORIGIN;
|
|
199
|
+
if (allowedOrigin && origin && origin !== allowedOrigin) {
|
|
200
|
+
return json({ error: 'Forbidden: invalid origin' }, 403);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const contentLength = Number(request.headers.get('content-length') ?? '0');
|
|
204
|
+
if (contentLength > MAX_BODY) return rpcErrorResponse(null, -32600, 'Request body too large');
|
|
205
|
+
const raw = await request.text();
|
|
206
|
+
if (raw.length > MAX_BODY) return rpcErrorResponse(null, -32600, 'Request body too large');
|
|
207
|
+
if (!raw) return rpcErrorResponse(null, -32600, 'Empty request body');
|
|
208
|
+
|
|
209
|
+
let parsed: unknown;
|
|
210
|
+
try {
|
|
211
|
+
parsed = JSON.parse(raw);
|
|
212
|
+
} catch {
|
|
213
|
+
return rpcErrorResponse(null, -32700, 'Parse error');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const config = loadConfig(env);
|
|
217
|
+
// Bind a W3C Trace Context for this request so upstream API calls made inside
|
|
218
|
+
// the tool handlers carry the traceparent and the trace spans the API hop.
|
|
219
|
+
const trace = deriveContext(
|
|
220
|
+
request.headers.get('traceparent') ?? undefined,
|
|
221
|
+
request.headers.get('tracestate') ?? undefined,
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
return runWithTrace(trace, async () => {
|
|
225
|
+
if (Array.isArray(parsed)) {
|
|
226
|
+
const settled = await Promise.all(
|
|
227
|
+
parsed.map((m) => handleRpc(m as { id?: unknown; method?: unknown }, config)),
|
|
228
|
+
);
|
|
229
|
+
const responses = settled.filter((r): r is object => r !== null);
|
|
230
|
+
if (responses.length === 0) {
|
|
231
|
+
return new Response(null, { status: 202, headers: corsHeaders(origin) });
|
|
232
|
+
}
|
|
233
|
+
return json(responses);
|
|
234
|
+
}
|
|
235
|
+
const response = await handleRpc(parsed as { id?: unknown; method?: unknown }, config);
|
|
236
|
+
if (response === null) {
|
|
237
|
+
return new Response(null, { status: 202, headers: corsHeaders(origin) });
|
|
238
|
+
}
|
|
239
|
+
return json(response);
|
|
240
|
+
});
|
|
241
|
+
},
|
|
242
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name = "{{serverName}}"
|
|
2
|
+
main = "src/index.ts"
|
|
3
|
+
compatibility_date = "2024-11-01"
|
|
4
|
+
# nodejs_compat backs the reused modules (AsyncLocalStorage trace context,
|
|
5
|
+
# node:crypto, Buffer). Required — do not remove.
|
|
6
|
+
compatibility_flags = ["nodejs_compat"]
|
|
7
|
+
|
|
8
|
+
# Non-secret configuration. Override per-environment as needed.
|
|
9
|
+
[vars]
|
|
10
|
+
BASE_URL = "{{baseUrl}}"
|
|
11
|
+
MAX_RETRIES = "3"
|
|
12
|
+
REQUEST_INTERVAL_MS = "100"
|
|
13
|
+
# ALLOWED_ORIGIN = "https://your-client.example.com"
|
|
14
|
+
|
|
15
|
+
# Secrets are NOT stored in this file. Set them before `wrangler deploy`:
|
|
16
|
+
# wrangler secret put MCP_AUTH_TOKEN # bearer token clients must present
|
|
17
|
+
{{#each authEnvVars}}
|
|
18
|
+
# wrangler secret put {{name}}
|
|
19
|
+
{{/each}}
|