mcpmake 0.1.0 → 0.2.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/dist/commands/bundle.d.ts +1 -0
- package/dist/commands/bundle.d.ts.map +1 -0
- package/dist/commands/bundle.js +5 -4
- package/dist/commands/bundle.js.map +1 -0
- package/dist/commands/ci.d.ts +1 -0
- package/dist/commands/ci.d.ts.map +1 -0
- package/dist/commands/ci.js +3 -2
- package/dist/commands/ci.js.map +1 -0
- package/dist/commands/deploy.d.ts +1 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +4 -3
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/diff.d.ts +1 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +5 -4
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/from/describe.d.ts +1 -0
- package/dist/commands/from/describe.d.ts.map +1 -0
- package/dist/commands/from/describe.js +11 -10
- package/dist/commands/from/describe.js.map +1 -0
- package/dist/commands/from/har.d.ts +1 -0
- package/dist/commands/from/har.d.ts.map +1 -0
- package/dist/commands/from/har.js +14 -13
- package/dist/commands/from/har.js.map +1 -0
- package/dist/commands/from/openapi.d.ts +1 -0
- package/dist/commands/from/openapi.d.ts.map +1 -0
- package/dist/commands/from/openapi.js +17 -16
- package/dist/commands/from/openapi.js.map +1 -0
- package/dist/commands/from/postman.d.ts +1 -0
- package/dist/commands/from/postman.d.ts.map +1 -0
- package/dist/commands/from/postman.js +13 -12
- package/dist/commands/from/postman.js.map +1 -0
- package/dist/commands/from/stainless.d.ts +110 -0
- package/dist/commands/from/stainless.d.ts.map +1 -0
- package/dist/commands/from/stainless.js +272 -0
- package/dist/commands/from/stainless.js.map +1 -0
- package/dist/commands/from/target-support.d.ts +1 -0
- package/dist/commands/from/target-support.d.ts.map +1 -0
- package/dist/commands/from/target-support.js +2 -1
- package/dist/commands/from/target-support.js.map +1 -0
- package/dist/commands/from/url.d.ts +1 -0
- package/dist/commands/from/url.d.ts.map +1 -0
- package/dist/commands/from/url.js +14 -13
- package/dist/commands/from/url.js.map +1 -0
- package/dist/commands/from/website.d.ts +1 -0
- package/dist/commands/from/website.d.ts.map +1 -0
- package/dist/commands/from/website.js +17 -16
- package/dist/commands/from/website.js.map +1 -0
- package/dist/commands/lint.d.ts +1 -0
- package/dist/commands/lint.d.ts.map +1 -0
- package/dist/commands/lint.js +6 -5
- package/dist/commands/lint.js.map +1 -0
- package/dist/commands/merge.d.ts +1 -0
- package/dist/commands/merge.d.ts.map +1 -0
- package/dist/commands/merge.js +3 -2
- package/dist/commands/merge.js.map +1 -0
- package/dist/commands/publish.d.ts +1 -0
- package/dist/commands/publish.d.ts.map +1 -0
- package/dist/commands/publish.js +4 -3
- package/dist/commands/publish.js.map +1 -0
- package/dist/commands/rescan.d.ts +1 -0
- package/dist/commands/rescan.d.ts.map +1 -0
- package/dist/commands/rescan.js +12 -11
- package/dist/commands/rescan.js.map +1 -0
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +10 -9
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/verify.d.ts +1 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +7 -6
- package/dist/commands/verify.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -2
- package/dist/index.js.map +1 -0
- package/dist/registry/official-registry.d.ts +1 -0
- package/dist/registry/official-registry.d.ts.map +1 -0
- package/dist/registry/official-registry.js +1 -0
- package/dist/registry/official-registry.js.map +1 -0
- package/package.json +24 -42
- package/README.md +0 -691
- package/dist/analyzer/auth-detector.d.ts +0 -12
- package/dist/analyzer/auth-detector.js +0 -142
- package/dist/analyzer/dom-parser.d.ts +0 -10
- package/dist/analyzer/dom-parser.js +0 -259
- package/dist/analyzer/goal-crawler.d.ts +0 -25
- package/dist/analyzer/goal-crawler.js +0 -177
- package/dist/analyzer/hybrid-detector.d.ts +0 -28
- package/dist/analyzer/hybrid-detector.js +0 -96
- package/dist/analyzer/index.d.ts +0 -12
- package/dist/analyzer/index.js +0 -8
- package/dist/analyzer/screenshot-capture.d.ts +0 -29
- package/dist/analyzer/screenshot-capture.js +0 -42
- package/dist/analyzer/selector-builder.d.ts +0 -19
- package/dist/analyzer/selector-builder.js +0 -199
- package/dist/analyzer/semantic-analyzer.d.ts +0 -13
- package/dist/analyzer/semantic-analyzer.js +0 -145
- package/dist/analyzer/site-crawler.d.ts +0 -38
- package/dist/analyzer/site-crawler.js +0 -235
- package/dist/cloud/billing/billing-engine.d.ts +0 -44
- package/dist/cloud/billing/billing-engine.js +0 -81
- package/dist/cloud/billing/credit-store.d.ts +0 -64
- package/dist/cloud/billing/credit-store.js +0 -168
- package/dist/cloud/billing/index.d.ts +0 -4
- package/dist/cloud/billing/index.js +0 -2
- package/dist/cloud/billing/usage-store.d.ts +0 -42
- package/dist/cloud/billing/usage-store.js +0 -85
- package/dist/cloud/billing/usage-tracker.d.ts +0 -38
- package/dist/cloud/billing/usage-tracker.js +0 -95
- package/dist/cloud/build-pipeline.d.ts +0 -39
- package/dist/cloud/build-pipeline.js +0 -310
- package/dist/cloud/build-queue.d.ts +0 -30
- package/dist/cloud/build-queue.js +0 -70
- package/dist/cloud/caddy-manager.d.ts +0 -18
- package/dist/cloud/caddy-manager.js +0 -97
- package/dist/cloud/container-backend.d.ts +0 -62
- package/dist/cloud/container-backend.js +0 -59
- package/dist/cloud/container-manager.d.ts +0 -64
- package/dist/cloud/container-manager.js +0 -301
- package/dist/cloud/crypto.d.ts +0 -27
- package/dist/cloud/crypto.js +0 -63
- package/dist/cloud/db/index.d.ts +0 -27
- package/dist/cloud/db/index.js +0 -53
- package/dist/cloud/db/migrations.d.ts +0 -12
- package/dist/cloud/db/migrations.js +0 -329
- package/dist/cloud/db/pg-store.d.ts +0 -45
- package/dist/cloud/db/pg-store.js +0 -336
- package/dist/cloud/failure-tracker.d.ts +0 -51
- package/dist/cloud/failure-tracker.js +0 -102
- package/dist/cloud/idle-monitor.d.ts +0 -30
- package/dist/cloud/idle-monitor.js +0 -70
- package/dist/cloud/mailer.d.ts +0 -21
- package/dist/cloud/mailer.js +0 -193
- package/dist/cloud/mcp-proxy.d.ts +0 -58
- package/dist/cloud/mcp-proxy.js +0 -203
- package/dist/cloud/metric-samples.d.ts +0 -43
- package/dist/cloud/metric-samples.js +0 -85
- package/dist/cloud/metrics.d.ts +0 -26
- package/dist/cloud/metrics.js +0 -59
- package/dist/cloud/multipart.d.ts +0 -26
- package/dist/cloud/multipart.js +0 -132
- package/dist/cloud/observability.d.ts +0 -27
- package/dist/cloud/observability.js +0 -98
- package/dist/cloud/rate-limiter.d.ts +0 -31
- package/dist/cloud/rate-limiter.js +0 -58
- package/dist/cloud/request-security.d.ts +0 -5
- package/dist/cloud/request-security.js +0 -74
- package/dist/cloud/resource-monitor.d.ts +0 -69
- package/dist/cloud/resource-monitor.js +0 -130
- package/dist/cloud/secret-store.d.ts +0 -38
- package/dist/cloud/secret-store.js +0 -103
- package/dist/cloud/security.d.ts +0 -26
- package/dist/cloud/security.js +0 -142
- package/dist/cloud/server.d.ts +0 -21
- package/dist/cloud/server.js +0 -1079
- package/dist/cloud/shared-state.d.ts +0 -72
- package/dist/cloud/shared-state.js +0 -159
- package/dist/cloud/ssrf.d.ts +0 -43
- package/dist/cloud/ssrf.js +0 -150
- package/dist/cloud/store.d.ts +0 -41
- package/dist/cloud/store.js +0 -75
- package/dist/cloud/stripe.d.ts +0 -78
- package/dist/cloud/stripe.js +0 -317
- package/dist/cloud/telemetry-store.d.ts +0 -53
- package/dist/cloud/telemetry-store.js +0 -108
- package/dist/cloud/web/auth.d.ts +0 -225
- package/dist/cloud/web/auth.js +0 -555
- package/dist/cloud/web/charts.d.ts +0 -70
- package/dist/cloud/web/charts.js +0 -178
- package/dist/cloud/web/csrf.d.ts +0 -14
- package/dist/cloud/web/csrf.js +0 -22
- package/dist/cloud/web/docs.d.ts +0 -40
- package/dist/cloud/web/docs.js +0 -174
- package/dist/cloud/web/router.d.ts +0 -25
- package/dist/cloud/web/router.js +0 -1921
- package/dist/cloud/web/static/alpine.min.js +0 -5
- package/dist/cloud/web/static/favicon.svg +0 -4
- package/dist/cloud/web/static/htmx-sse.js +0 -290
- package/dist/cloud/web/static/htmx.min.js +0 -1
- package/dist/cloud/web/static/style.css +0 -2683
- package/dist/cloud/web/static-server.d.ts +0 -13
- package/dist/cloud/web/static-server.js +0 -73
- package/dist/cloud/web/template-engine.d.ts +0 -27
- package/dist/cloud/web/template-engine.js +0 -146
- package/dist/cloud/web/templates/layouts/admin.hbs +0 -57
- package/dist/cloud/web/templates/layouts/auth.hbs +0 -138
- package/dist/cloud/web/templates/layouts/base.hbs +0 -16
- package/dist/cloud/web/templates/layouts/dashboard.hbs +0 -39
- package/dist/cloud/web/templates/layouts/landing.hbs +0 -82
- package/dist/cloud/web/templates/pages/admin/overview.hbs +0 -123
- package/dist/cloud/web/templates/pages/admin/servers.hbs +0 -129
- package/dist/cloud/web/templates/pages/admin/telemetry.hbs +0 -39
- package/dist/cloud/web/templates/pages/admin/user-edit.hbs +0 -91
- package/dist/cloud/web/templates/pages/admin/users.hbs +0 -179
- package/dist/cloud/web/templates/pages/auth/forgot-password.hbs +0 -25
- package/dist/cloud/web/templates/pages/auth/login.hbs +0 -33
- package/dist/cloud/web/templates/pages/auth/register.hbs +0 -32
- package/dist/cloud/web/templates/pages/auth/reset-password.hbs +0 -34
- package/dist/cloud/web/templates/pages/dashboard/billing.hbs +0 -140
- package/dist/cloud/web/templates/pages/dashboard/create.hbs +0 -173
- package/dist/cloud/web/templates/pages/dashboard/index.hbs +0 -8
- package/dist/cloud/web/templates/pages/dashboard/server-detail.hbs +0 -280
- package/dist/cloud/web/templates/pages/dashboard/server-logs.hbs +0 -35
- package/dist/cloud/web/templates/pages/dashboard/server-metrics.hbs +0 -63
- package/dist/cloud/web/templates/pages/dashboard/servers-partial.hbs +0 -21
- package/dist/cloud/web/templates/pages/dashboard/servers.hbs +0 -44
- package/dist/cloud/web/templates/pages/docs/show.hbs +0 -16
- package/dist/cloud/web/templates/pages/errors/404.hbs +0 -9
- package/dist/cloud/web/templates/pages/errors/500.hbs +0 -8
- package/dist/cloud/web/templates/pages/landing/index.hbs +0 -223
- package/dist/cloud/web/templates/pages/legal/privacy.hbs +0 -71
- package/dist/cloud/web/templates/pages/legal/terms.hbs +0 -73
- package/dist/cloud/web/templates/partials/admin-stats.hbs +0 -52
- package/dist/cloud/web/templates/partials/flash-message.hbs +0 -6
- package/dist/cloud/web/templates/partials/pricing-table.hbs +0 -103
- package/dist/cloud/web/templates/partials/server-card.hbs +0 -19
- package/dist/cloud/web/templates/partials/status-badge.hbs +0 -1
- package/dist/config/configurable-command.d.ts +0 -13
- package/dist/config/configurable-command.js +0 -70
- package/dist/config/mcpmake-config.d.ts +0 -68
- package/dist/config/mcpmake-config.js +0 -207
- package/dist/docs/cli.md +0 -400
- package/dist/docs/mcp-2026-07-28-migration.md +0 -78
- package/dist/docs/migrate-from-stainless.md +0 -94
- package/dist/docs/quickstart.md +0 -166
- package/dist/docs/show-hn.md +0 -26
- package/dist/docs/website-servers.md +0 -169
- package/dist/emitter/code-writer.d.ts +0 -8
- package/dist/emitter/code-writer.js +0 -25
- package/dist/emitter/index.d.ts +0 -32
- package/dist/emitter/index.js +0 -280
- package/dist/emitter/mcpb-bundler.d.ts +0 -31
- package/dist/emitter/mcpb-bundler.js +0 -172
- package/dist/emitter/project-scaffolder.d.ts +0 -4
- package/dist/emitter/project-scaffolder.js +0 -89
- package/dist/emitter/python-template-loader.d.ts +0 -4
- package/dist/emitter/python-template-loader.js +0 -30
- package/dist/emitter/python-templates/dockerfile.hbs +0 -14
- package/dist/emitter/python-templates/env.example.hbs +0 -6
- package/dist/emitter/python-templates/requirements.txt.hbs +0 -4
- package/dist/emitter/python-templates/server.py.hbs +0 -77
- package/dist/emitter/site-scaffolder.d.ts +0 -13
- package/dist/emitter/site-scaffolder.js +0 -70
- package/dist/emitter/site-template-loader.d.ts +0 -5
- package/dist/emitter/site-template-loader.js +0 -47
- package/dist/emitter/site-templates/browser-manager.ts.hbs +0 -233
- package/dist/emitter/site-templates/config.ts.hbs +0 -28
- package/dist/emitter/site-templates/dockerfile.hbs +0 -31
- package/dist/emitter/site-templates/env.example.hbs +0 -19
- package/dist/emitter/site-templates/package.json.hbs +0 -26
- package/dist/emitter/site-templates/server-main-http.ts.hbs +0 -108
- package/dist/emitter/site-templates/server-main.ts.hbs +0 -23
- package/dist/emitter/site-templates/tool-handler-action.ts.hbs +0 -86
- package/dist/emitter/site-templates/tool-handler-form.ts.hbs +0 -116
- package/dist/emitter/site-templates/tool-handler-lifecycle.ts.hbs +0 -146
- package/dist/emitter/site-templates/tool-index.ts.hbs +0 -11
- package/dist/emitter/template-loader.d.ts +0 -1
- package/dist/emitter/template-loader.js +0 -27
- package/dist/emitter/templates/auth-provider.ts.hbs +0 -57
- package/dist/emitter/templates/config.ts.hbs +0 -63
- package/dist/emitter/templates/discovery.ts.hbs +0 -301
- package/dist/emitter/templates/dockerfile.hbs +0 -34
- package/dist/emitter/templates/env.example.hbs +0 -28
- package/dist/emitter/templates/gitignore.hbs +0 -5
- package/dist/emitter/templates/http-executor.ts.hbs +0 -117
- package/dist/emitter/templates/oauth.ts.hbs +0 -188
- package/dist/emitter/templates/package.json.hbs +0 -25
- package/dist/emitter/templates/prompts.ts.hbs +0 -22
- package/dist/emitter/templates/readme.md.hbs +0 -123
- package/dist/emitter/templates/resources.ts.hbs +0 -63
- package/dist/emitter/templates/server-main-http.ts.hbs +0 -407
- package/dist/emitter/templates/server-main.ts.hbs +0 -40
- package/dist/emitter/templates/task-handlers.ts.hbs +0 -189
- package/dist/emitter/templates/task-manager.ts.hbs +0 -139
- package/dist/emitter/templates/task-sse.ts.hbs +0 -105
- package/dist/emitter/templates/tool-handler.ts.hbs +0 -124
- package/dist/emitter/templates/tool-index.ts.hbs +0 -11
- package/dist/emitter/templates/tool-test.ts.hbs +0 -57
- package/dist/emitter/templates/trace.ts.hbs +0 -79
- package/dist/emitter/templates/tsconfig.json.hbs +0 -16
- package/dist/emitter/templates/types.ts.hbs +0 -5
- package/dist/emitter/worker-template-loader.d.ts +0 -5
- package/dist/emitter/worker-template-loader.js +0 -33
- package/dist/emitter/worker-templates/config.ts.hbs +0 -54
- package/dist/emitter/worker-templates/dev-vars.example.hbs +0 -10
- package/dist/emitter/worker-templates/gitignore.hbs +0 -6
- package/dist/emitter/worker-templates/package.json.hbs +0 -24
- package/dist/emitter/worker-templates/readme.md.hbs +0 -53
- package/dist/emitter/worker-templates/server.test.ts.hbs +0 -20
- package/dist/emitter/worker-templates/tool-handler.ts.hbs +0 -85
- package/dist/emitter/worker-templates/tool-index.ts.hbs +0 -28
- package/dist/emitter/worker-templates/tsconfig.json.hbs +0 -17
- package/dist/emitter/worker-templates/worker.ts.hbs +0 -242
- package/dist/emitter/worker-templates/wrangler.toml.hbs +0 -19
- package/dist/generator/spec-generator.d.ts +0 -6
- package/dist/generator/spec-generator.js +0 -50
- package/dist/parser/har-filter.d.ts +0 -8
- package/dist/parser/har-filter.js +0 -71
- package/dist/parser/har-loader.d.ts +0 -2
- package/dist/parser/har-loader.js +0 -14
- package/dist/parser/har-normalizer.d.ts +0 -20
- package/dist/parser/har-normalizer.js +0 -78
- package/dist/parser/index.d.ts +0 -10
- package/dist/parser/index.js +0 -6
- package/dist/parser/openapi-loader.d.ts +0 -6
- package/dist/parser/openapi-loader.js +0 -308
- package/dist/parser/operation-extractor.d.ts +0 -13
- package/dist/parser/operation-extractor.js +0 -155
- package/dist/parser/overlay-loader.d.ts +0 -10
- package/dist/parser/overlay-loader.js +0 -184
- package/dist/parser/postman-loader.d.ts +0 -9
- package/dist/parser/postman-loader.js +0 -106
- package/dist/parser/schema-converter.d.ts +0 -12
- package/dist/parser/schema-converter.js +0 -117
- package/dist/plugins/adapter.d.ts +0 -40
- package/dist/plugins/adapter.js +0 -15
- package/dist/plugins/loader.d.ts +0 -25
- package/dist/plugins/loader.js +0 -58
- package/dist/pricing.d.ts +0 -55
- package/dist/pricing.js +0 -133
- package/dist/providers/index.d.ts +0 -15
- package/dist/providers/index.js +0 -56
- package/dist/recorder/browser-recorder.d.ts +0 -22
- package/dist/recorder/browser-recorder.js +0 -205
- package/dist/rescan/diff-engine.d.ts +0 -5
- package/dist/rescan/diff-engine.js +0 -312
- package/dist/rescan/index.d.ts +0 -3
- package/dist/rescan/index.js +0 -2
- package/dist/rescan/rescan-runner.d.ts +0 -42
- package/dist/rescan/rescan-runner.js +0 -69
- package/dist/rescan/rescan-scheduler.d.ts +0 -41
- package/dist/rescan/rescan-scheduler.js +0 -179
- package/dist/site-transformer/browser-tools.d.ts +0 -10
- package/dist/site-transformer/browser-tools.js +0 -59
- package/dist/site-transformer/index.d.ts +0 -2
- package/dist/site-transformer/index.js +0 -2
- package/dist/site-transformer/selector-healer.d.ts +0 -8
- package/dist/site-transformer/selector-healer.js +0 -106
- package/dist/site-transformer/tool-generator.d.ts +0 -13
- package/dist/site-transformer/tool-generator.js +0 -245
- package/dist/transformer/auth-detector.d.ts +0 -13
- package/dist/transformer/auth-detector.js +0 -90
- package/dist/transformer/catalog-builder.d.ts +0 -18
- package/dist/transformer/catalog-builder.js +0 -56
- package/dist/transformer/client-compat.d.ts +0 -6
- package/dist/transformer/client-compat.js +0 -44
- package/dist/transformer/har-clusterer.d.ts +0 -9
- package/dist/transformer/har-clusterer.js +0 -27
- package/dist/transformer/har-dedup.d.ts +0 -10
- package/dist/transformer/har-dedup.js +0 -81
- package/dist/transformer/har-schema-inferrer.d.ts +0 -15
- package/dist/transformer/har-schema-inferrer.js +0 -90
- package/dist/transformer/har-to-operations.d.ts +0 -13
- package/dist/transformer/har-to-operations.js +0 -192
- package/dist/transformer/index.d.ts +0 -8
- package/dist/transformer/index.js +0 -6
- package/dist/transformer/llm-namer.d.ts +0 -6
- package/dist/transformer/llm-namer.js +0 -59
- package/dist/transformer/naming.d.ts +0 -4
- package/dist/transformer/naming.js +0 -30
- package/dist/transformer/operation-filter.d.ts +0 -13
- package/dist/transformer/operation-filter.js +0 -52
- package/dist/transformer/resource-builder.d.ts +0 -12
- package/dist/transformer/resource-builder.js +0 -80
- package/dist/transformer/schema-merger.d.ts +0 -14
- package/dist/transformer/schema-merger.js +0 -65
- package/dist/transformer/tool-builder.d.ts +0 -3
- package/dist/transformer/tool-builder.js +0 -114
- package/dist/types/index.d.ts +0 -131
- package/dist/types/index.js +0 -1
- package/dist/types/site.d.ts +0 -284
- package/dist/types/site.js +0 -8
- package/dist/utils/fail.d.ts +0 -48
- package/dist/utils/fail.js +0 -204
- package/dist/utils/fs.d.ts +0 -5
- package/dist/utils/fs.js +0 -28
- package/dist/utils/interactive.d.ts +0 -6
- package/dist/utils/interactive.js +0 -30
- package/dist/utils/logger.d.ts +0 -1
- package/dist/utils/logger.js +0 -2
- package/dist/utils/sanitize.d.ts +0 -28
- package/dist/utils/sanitize.js +0 -44
- package/dist/utils/watcher.d.ts +0 -11
- package/dist/utils/watcher.js +0 -36
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dynamic tool discovery — 4 meta-tools that let LLMs discover and call tools
|
|
3
|
-
* without all tool schemas loaded into context upfront.
|
|
4
|
-
*
|
|
5
|
-
* Reduces initial context from ~25-40K tokens (for large APIs) to ~1,300 tokens.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { z } from 'zod';
|
|
9
|
-
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
10
|
-
import { executeRequest } from './http.js';
|
|
11
|
-
import type { AppConfig } from './config.js';
|
|
12
|
-
import catalogData from './tool-catalog.json' with { type: 'json' };
|
|
13
|
-
|
|
14
|
-
interface CatalogEntry {
|
|
15
|
-
name: string;
|
|
16
|
-
title: string;
|
|
17
|
-
description: string;
|
|
18
|
-
method: string;
|
|
19
|
-
path: string;
|
|
20
|
-
inputSchema: Record<string, unknown>;
|
|
21
|
-
pathParams: string[];
|
|
22
|
-
queryParams: string[];
|
|
23
|
-
hasRequestBody: boolean;
|
|
24
|
-
requestBodyContentType: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const catalog: CatalogEntry[] = catalogData as CatalogEntry[];
|
|
28
|
-
|
|
29
|
-
// Simple TF-IDF search index built at startup
|
|
30
|
-
const searchIndex = buildSearchIndex(catalog);
|
|
31
|
-
|
|
32
|
-
export function registerDiscoveryTools(server: McpServer, config: AppConfig): void {
|
|
33
|
-
// 1. list_tools — list available tools with optional query filter
|
|
34
|
-
server.registerTool(
|
|
35
|
-
'list_tools',
|
|
36
|
-
{
|
|
37
|
-
title: 'List Available Tools',
|
|
38
|
-
description: `List all ${catalog.length} available API tools. Use "query" to filter by name or description.`,
|
|
39
|
-
inputSchema: z.object({
|
|
40
|
-
query: z.string().optional().describe('Filter tools by name or description'),
|
|
41
|
-
offset: z.number().int().optional().describe('Pagination offset (default: 0)'),
|
|
42
|
-
limit: z.number().int().optional().describe('Max results (default: 20)'),
|
|
43
|
-
}),
|
|
44
|
-
annotations: { readOnlyHint: true },
|
|
45
|
-
},
|
|
46
|
-
async (input) => {
|
|
47
|
-
const query = (input as Record<string, unknown>).query as string | undefined;
|
|
48
|
-
const offset = ((input as Record<string, unknown>).offset as number) ?? 0;
|
|
49
|
-
const limit = ((input as Record<string, unknown>).limit as number) ?? 20;
|
|
50
|
-
|
|
51
|
-
let results = catalog;
|
|
52
|
-
if (query) {
|
|
53
|
-
results = searchTools(query);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const page = results.slice(offset, offset + limit);
|
|
57
|
-
const hasMore = offset + limit < results.length;
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
content: [
|
|
61
|
-
{
|
|
62
|
-
type: 'text' as const,
|
|
63
|
-
text: JSON.stringify(
|
|
64
|
-
{
|
|
65
|
-
tools: page.map((t) => ({ name: t.name, description: t.description })),
|
|
66
|
-
total: results.length,
|
|
67
|
-
hasMore,
|
|
68
|
-
nextOffset: hasMore ? offset + limit : undefined,
|
|
69
|
-
},
|
|
70
|
-
null,
|
|
71
|
-
2,
|
|
72
|
-
),
|
|
73
|
-
},
|
|
74
|
-
],
|
|
75
|
-
};
|
|
76
|
-
},
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
// 2. get_tool_schema — get the full input schema for a specific tool
|
|
80
|
-
server.registerTool(
|
|
81
|
-
'get_tool_schema',
|
|
82
|
-
{
|
|
83
|
-
title: 'Get Tool Schema',
|
|
84
|
-
description: 'Get the full input schema and details for a specific tool by name.',
|
|
85
|
-
inputSchema: z.object({
|
|
86
|
-
name: z.string().describe('Tool name (from list_tools)'),
|
|
87
|
-
}),
|
|
88
|
-
annotations: { readOnlyHint: true },
|
|
89
|
-
},
|
|
90
|
-
async (input) => {
|
|
91
|
-
const name = (input as Record<string, unknown>).name as string;
|
|
92
|
-
const tool = catalog.find((t) => t.name === name);
|
|
93
|
-
|
|
94
|
-
if (!tool) {
|
|
95
|
-
return {
|
|
96
|
-
content: [{ type: 'text' as const, text: `Tool "${name}" not found.` }],
|
|
97
|
-
isError: true,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
content: [
|
|
103
|
-
{
|
|
104
|
-
type: 'text' as const,
|
|
105
|
-
text: JSON.stringify(
|
|
106
|
-
{
|
|
107
|
-
name: tool.name,
|
|
108
|
-
title: tool.title,
|
|
109
|
-
description: tool.description,
|
|
110
|
-
method: tool.method,
|
|
111
|
-
path: tool.path,
|
|
112
|
-
inputSchema: tool.inputSchema,
|
|
113
|
-
},
|
|
114
|
-
null,
|
|
115
|
-
2,
|
|
116
|
-
),
|
|
117
|
-
},
|
|
118
|
-
],
|
|
119
|
-
};
|
|
120
|
-
},
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
// 3. execute_tool — execute a tool by name with arguments
|
|
124
|
-
server.registerTool(
|
|
125
|
-
'execute_tool',
|
|
126
|
-
{
|
|
127
|
-
title: 'Execute Tool',
|
|
128
|
-
description: 'Execute any API tool by name. Get the schema first with get_tool_schema.',
|
|
129
|
-
inputSchema: z.object({
|
|
130
|
-
name: z.string().describe('Tool name'),
|
|
131
|
-
args: z.record(z.unknown()).optional().describe('Tool arguments (match the input schema)'),
|
|
132
|
-
}),
|
|
133
|
-
},
|
|
134
|
-
async (input) => {
|
|
135
|
-
const name = (input as Record<string, unknown>).name as string;
|
|
136
|
-
const args = ((input as Record<string, unknown>).args as Record<string, unknown>) ?? {};
|
|
137
|
-
const tool = catalog.find((t) => t.name === name);
|
|
138
|
-
|
|
139
|
-
if (!tool) {
|
|
140
|
-
return {
|
|
141
|
-
content: [{ type: 'text' as const, text: `Tool "${name}" not found.` }],
|
|
142
|
-
isError: true,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
try {
|
|
147
|
-
// Build URL from path template
|
|
148
|
-
let url = config.baseUrl + tool.path;
|
|
149
|
-
for (const param of tool.pathParams) {
|
|
150
|
-
if (args[param] !== undefined) {
|
|
151
|
-
url = url.replace(`{${param}}`, encodeURIComponent(String(args[param])));
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Add query params
|
|
156
|
-
const queryParams = new URLSearchParams();
|
|
157
|
-
for (const param of tool.queryParams) {
|
|
158
|
-
if (args[param] !== undefined) {
|
|
159
|
-
queryParams.set(param, String(args[param]));
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
const qs = queryParams.toString();
|
|
163
|
-
if (qs) url += `?${qs}`;
|
|
164
|
-
|
|
165
|
-
const response = await executeRequest({
|
|
166
|
-
method: tool.method,
|
|
167
|
-
url,
|
|
168
|
-
...(tool.hasRequestBody && args.body
|
|
169
|
-
? { body: args.body, contentType: tool.requestBodyContentType }
|
|
170
|
-
: {}),
|
|
171
|
-
headers: {},
|
|
172
|
-
config,
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
content: [
|
|
177
|
-
{
|
|
178
|
-
type: 'text' as const,
|
|
179
|
-
text: JSON.stringify(response.data, null, 2),
|
|
180
|
-
},
|
|
181
|
-
],
|
|
182
|
-
};
|
|
183
|
-
} catch (error) {
|
|
184
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
185
|
-
return {
|
|
186
|
-
content: [{ type: 'text' as const, text: `Error: ${message}` }],
|
|
187
|
-
isError: true,
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
},
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
// 4. search_tools — semantic search over tool descriptions
|
|
194
|
-
server.registerTool(
|
|
195
|
-
'search_tools',
|
|
196
|
-
{
|
|
197
|
-
title: 'Search Tools',
|
|
198
|
-
description: 'Search for tools by description. Uses fuzzy matching.',
|
|
199
|
-
inputSchema: z.object({
|
|
200
|
-
description: z.string().describe('What you want to do (natural language)'),
|
|
201
|
-
limit: z.number().int().optional().describe('Max results (default: 5)'),
|
|
202
|
-
}),
|
|
203
|
-
annotations: { readOnlyHint: true },
|
|
204
|
-
},
|
|
205
|
-
async (input) => {
|
|
206
|
-
const description = (input as Record<string, unknown>).description as string;
|
|
207
|
-
const limit = ((input as Record<string, unknown>).limit as number) ?? 5;
|
|
208
|
-
|
|
209
|
-
const results = searchTools(description).slice(0, limit);
|
|
210
|
-
|
|
211
|
-
return {
|
|
212
|
-
content: [
|
|
213
|
-
{
|
|
214
|
-
type: 'text' as const,
|
|
215
|
-
text: JSON.stringify(
|
|
216
|
-
results.map((t) => ({ name: t.name, description: t.description, method: t.method, path: t.path })),
|
|
217
|
-
null,
|
|
218
|
-
2,
|
|
219
|
-
),
|
|
220
|
-
},
|
|
221
|
-
],
|
|
222
|
-
};
|
|
223
|
-
},
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// ---------------------------------------------------------------------------
|
|
228
|
-
// TF-IDF Search
|
|
229
|
-
// ---------------------------------------------------------------------------
|
|
230
|
-
|
|
231
|
-
interface SearchEntry {
|
|
232
|
-
tool: CatalogEntry;
|
|
233
|
-
terms: Map<string, number>; // term -> TF-IDF weight
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function buildSearchIndex(tools: CatalogEntry[]): SearchEntry[] {
|
|
237
|
-
const docFreq = new Map<string, number>();
|
|
238
|
-
const docs: Array<{ tool: CatalogEntry; terms: string[] }> = [];
|
|
239
|
-
|
|
240
|
-
for (const tool of tools) {
|
|
241
|
-
const text = `${tool.name} ${tool.title} ${tool.description} ${tool.method} ${tool.path}`;
|
|
242
|
-
const terms = tokenize(text);
|
|
243
|
-
docs.push({ tool, terms });
|
|
244
|
-
|
|
245
|
-
const unique = new Set(terms);
|
|
246
|
-
for (const term of unique) {
|
|
247
|
-
docFreq.set(term, (docFreq.get(term) ?? 0) + 1);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const N = tools.length;
|
|
252
|
-
return docs.map(({ tool, terms }) => {
|
|
253
|
-
const termFreq = new Map<string, number>();
|
|
254
|
-
for (const t of terms) {
|
|
255
|
-
termFreq.set(t, (termFreq.get(t) ?? 0) + 1);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const weighted = new Map<string, number>();
|
|
259
|
-
for (const [term, tf] of termFreq) {
|
|
260
|
-
const df = docFreq.get(term) ?? 1;
|
|
261
|
-
const idf = Math.log(N / df);
|
|
262
|
-
weighted.set(term, tf * idf);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return { tool, terms: weighted };
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function searchTools(query: string): CatalogEntry[] {
|
|
270
|
-
const queryTerms = tokenize(query);
|
|
271
|
-
if (queryTerms.length === 0) return catalog;
|
|
272
|
-
|
|
273
|
-
const scores: Array<{ tool: CatalogEntry; score: number }> = [];
|
|
274
|
-
|
|
275
|
-
for (const entry of searchIndex) {
|
|
276
|
-
let score = 0;
|
|
277
|
-
for (const qt of queryTerms) {
|
|
278
|
-
// Exact match
|
|
279
|
-
score += entry.terms.get(qt) ?? 0;
|
|
280
|
-
// Prefix match (fuzzy)
|
|
281
|
-
for (const [term, weight] of entry.terms) {
|
|
282
|
-
if (term.startsWith(qt) || qt.startsWith(term)) {
|
|
283
|
-
score += weight * 0.5;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
if (score > 0) {
|
|
288
|
-
scores.push({ tool: entry.tool, score });
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return scores.sort((a, b) => b.score - a.score).map((s) => s.tool);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function tokenize(text: string): string[] {
|
|
296
|
-
return text
|
|
297
|
-
.toLowerCase()
|
|
298
|
-
.replace(/[^a-z0-9]/g, ' ')
|
|
299
|
-
.split(/\s+/)
|
|
300
|
-
.filter((t) => t.length > 1);
|
|
301
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
FROM node:20-alpine AS builder
|
|
2
|
-
|
|
3
|
-
WORKDIR /app
|
|
4
|
-
|
|
5
|
-
COPY package.json package-lock.json ./
|
|
6
|
-
RUN npm ci
|
|
7
|
-
|
|
8
|
-
COPY tsconfig.json ./
|
|
9
|
-
COPY src/ src/
|
|
10
|
-
RUN npm run build
|
|
11
|
-
|
|
12
|
-
FROM node:20-alpine
|
|
13
|
-
|
|
14
|
-
LABEL org.opencontainers.image.title="{{serverName}}"
|
|
15
|
-
|
|
16
|
-
ENV NODE_ENV=production
|
|
17
|
-
ENV TRANSPORT=http
|
|
18
|
-
ENV PORT=3000
|
|
19
|
-
|
|
20
|
-
WORKDIR /app
|
|
21
|
-
|
|
22
|
-
COPY package.json package-lock.json ./
|
|
23
|
-
RUN npm ci --omit=dev
|
|
24
|
-
|
|
25
|
-
COPY --from=builder /app/dist dist/
|
|
26
|
-
|
|
27
|
-
USER node
|
|
28
|
-
|
|
29
|
-
EXPOSE 3000
|
|
30
|
-
|
|
31
|
-
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
32
|
-
CMD node -e "fetch('http://localhost:3000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
|
33
|
-
|
|
34
|
-
CMD ["node", "dist/index.js"]
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# {{serverName}} configuration
|
|
2
|
-
|
|
3
|
-
# Base URL for the API
|
|
4
|
-
BASE_URL={{baseUrl}}
|
|
5
|
-
|
|
6
|
-
{{#each authEnvVars}}
|
|
7
|
-
# {{description}}
|
|
8
|
-
{{name}}=
|
|
9
|
-
{{/each}}
|
|
10
|
-
|
|
11
|
-
# HTTP tuning
|
|
12
|
-
MAX_RETRIES=3
|
|
13
|
-
REQUEST_INTERVAL_MS=100
|
|
14
|
-
{{#if (eq transport "http")}}
|
|
15
|
-
|
|
16
|
-
# Transport: "stdio" or "http"
|
|
17
|
-
TRANSPORT=http
|
|
18
|
-
PORT=3000
|
|
19
|
-
# Allowed origin for CORS/DNS-rebinding protection (leave empty to allow all in dev)
|
|
20
|
-
ALLOWED_ORIGIN=
|
|
21
|
-
# Bearer token required on every /mcp request. When set, clients must send
|
|
22
|
-
# `Authorization: Bearer <token>`. Leave empty to disable auth (local/dev only).
|
|
23
|
-
MCP_AUTH_TOKEN=
|
|
24
|
-
# Session mode (MCP 2026-07-28): leave empty for stateless (default, recommended
|
|
25
|
-
# — any request can hit any instance). Set to "true" for stateful per-session
|
|
26
|
-
# (Mcp-Session-Id) mode if a client needs resumable streams.
|
|
27
|
-
MCP_STATEFUL=
|
|
28
|
-
{{/if}}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { getAuthHeaders } from './auth.js';
|
|
2
|
-
import { traceHeaders } from './trace.js';
|
|
3
|
-
import type { AppConfig } from './config.js';
|
|
4
|
-
|
|
5
|
-
export interface RequestOptions {
|
|
6
|
-
method: string;
|
|
7
|
-
url: string;
|
|
8
|
-
body?: unknown;
|
|
9
|
-
contentType?: string;
|
|
10
|
-
headers: Record<string, string>;
|
|
11
|
-
config: AppConfig;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface ApiResponse {
|
|
15
|
-
status: number;
|
|
16
|
-
data: unknown;
|
|
17
|
-
headers: Record<string, string>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export class ApiError extends Error {
|
|
21
|
-
constructor(
|
|
22
|
-
message: string,
|
|
23
|
-
public readonly status: number,
|
|
24
|
-
public readonly data: unknown,
|
|
25
|
-
) {
|
|
26
|
-
super(message);
|
|
27
|
-
this.name = 'ApiError';
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const RETRYABLE_STATUS_CODES = [429, 500, 502, 503, 504];
|
|
32
|
-
|
|
33
|
-
let lastRequestTime = 0;
|
|
34
|
-
|
|
35
|
-
export async function executeRequest(options: RequestOptions): Promise<ApiResponse> {
|
|
36
|
-
const { config } = options;
|
|
37
|
-
|
|
38
|
-
// Rate limiting
|
|
39
|
-
const now = Date.now();
|
|
40
|
-
const elapsed = now - lastRequestTime;
|
|
41
|
-
if (elapsed < config.requestIntervalMs) {
|
|
42
|
-
await sleep(config.requestIntervalMs - elapsed);
|
|
43
|
-
}
|
|
44
|
-
lastRequestTime = Date.now();
|
|
45
|
-
|
|
46
|
-
const authHeaders = await getAuthHeaders(config);
|
|
47
|
-
// Propagate W3C Trace Context to the upstream API (no-op outside an HTTP
|
|
48
|
-
// request, e.g. stdio transport, where there is no active trace).
|
|
49
|
-
const headers: Record<string, string> = {
|
|
50
|
-
...authHeaders,
|
|
51
|
-
...traceHeaders(),
|
|
52
|
-
...options.headers,
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
if (options.body && options.contentType) {
|
|
56
|
-
headers['Content-Type'] = options.contentType;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
let lastError: Error | undefined;
|
|
60
|
-
|
|
61
|
-
for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
|
|
62
|
-
try {
|
|
63
|
-
const response = await fetch(options.url, {
|
|
64
|
-
method: options.method.toUpperCase(),
|
|
65
|
-
headers,
|
|
66
|
-
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
if (RETRYABLE_STATUS_CODES.includes(response.status) && attempt < config.maxRetries) {
|
|
70
|
-
const retryAfter = response.headers.get('Retry-After');
|
|
71
|
-
const delay = retryAfter
|
|
72
|
-
? parseInt(retryAfter, 10) * 1000
|
|
73
|
-
: 1000 * Math.pow(2, attempt);
|
|
74
|
-
await sleep(delay);
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const data = await parseResponseBody(response);
|
|
79
|
-
|
|
80
|
-
if (!response.ok) {
|
|
81
|
-
throw new ApiError(
|
|
82
|
-
`${response.status} ${response.statusText}`,
|
|
83
|
-
response.status,
|
|
84
|
-
data,
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
status: response.status,
|
|
90
|
-
data,
|
|
91
|
-
headers: Object.fromEntries(response.headers.entries()),
|
|
92
|
-
};
|
|
93
|
-
} catch (error) {
|
|
94
|
-
lastError = error as Error;
|
|
95
|
-
if (error instanceof ApiError && !RETRYABLE_STATUS_CODES.includes(error.status)) {
|
|
96
|
-
throw error;
|
|
97
|
-
}
|
|
98
|
-
if (attempt < config.maxRetries) {
|
|
99
|
-
await sleep(1000 * Math.pow(2, attempt));
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
throw lastError ?? new Error('Request failed after retries');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function parseResponseBody(response: Response): Promise<unknown> {
|
|
108
|
-
const contentType = response.headers.get('content-type') ?? '';
|
|
109
|
-
if (contentType.includes('application/json')) {
|
|
110
|
-
return response.json();
|
|
111
|
-
}
|
|
112
|
-
return response.text();
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function sleep(ms: number): Promise<void> {
|
|
116
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
117
|
-
}
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OAuth 2.1 token management.
|
|
3
|
-
* Supports Authorization Code + PKCE and Client Credentials flows.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import crypto from 'node:crypto';
|
|
7
|
-
|
|
8
|
-
export interface OAuthConfig {
|
|
9
|
-
clientId: string;
|
|
10
|
-
clientSecret?: string;
|
|
11
|
-
authorizationUrl: string;
|
|
12
|
-
tokenUrl: string;
|
|
13
|
-
scopes: string[];
|
|
14
|
-
redirectUri: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface TokenResponse {
|
|
18
|
-
access_token: string;
|
|
19
|
-
token_type: string;
|
|
20
|
-
expires_in?: number;
|
|
21
|
-
refresh_token?: string;
|
|
22
|
-
scope?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
let cachedToken: { accessToken: string; expiresAt: number; refreshToken?: string } | undefined;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Get a valid access token, refreshing if needed.
|
|
29
|
-
*/
|
|
30
|
-
export async function getAccessToken(config: OAuthConfig): Promise<string> {
|
|
31
|
-
if (cachedToken && Date.now() < cachedToken.expiresAt - 60_000) {
|
|
32
|
-
return cachedToken.accessToken;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Try refresh first
|
|
36
|
-
if (cachedToken?.refreshToken) {
|
|
37
|
-
try {
|
|
38
|
-
const token = await refreshToken(config, cachedToken.refreshToken);
|
|
39
|
-
cacheToken(token);
|
|
40
|
-
return token.access_token;
|
|
41
|
-
} catch {
|
|
42
|
-
// Refresh failed — fall through to re-authenticate
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Client Credentials flow (machine-to-machine)
|
|
47
|
-
if (config.clientSecret && !process.env.OAUTH2_TOKEN) {
|
|
48
|
-
const token = await clientCredentialsGrant(config);
|
|
49
|
-
cacheToken(token);
|
|
50
|
-
return token.access_token;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Pre-obtained token from env
|
|
54
|
-
if (process.env.OAUTH2_TOKEN) {
|
|
55
|
-
return process.env.OAUTH2_TOKEN;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
throw new Error(
|
|
59
|
-
'No OAuth token available. Set OAUTH2_TOKEN or configure OAUTH2_CLIENT_ID + OAUTH2_CLIENT_SECRET for client credentials flow.',
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Client Credentials grant (RFC 6749 Section 4.4).
|
|
65
|
-
*/
|
|
66
|
-
async function clientCredentialsGrant(config: OAuthConfig): Promise<TokenResponse> {
|
|
67
|
-
const params = new URLSearchParams({
|
|
68
|
-
grant_type: 'client_credentials',
|
|
69
|
-
client_id: config.clientId,
|
|
70
|
-
...(config.clientSecret ? { client_secret: config.clientSecret } : {}),
|
|
71
|
-
...(config.scopes.length > 0 ? { scope: config.scopes.join(' ') } : {}),
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
const response = await fetch(config.tokenUrl, {
|
|
75
|
-
method: 'POST',
|
|
76
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
77
|
-
body: params.toString(),
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
if (!response.ok) {
|
|
81
|
-
const body = await response.text();
|
|
82
|
-
throw new Error(`OAuth client credentials failed (${response.status}): ${body}`);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return response.json() as Promise<TokenResponse>;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Refresh an access token using a refresh token.
|
|
90
|
-
*/
|
|
91
|
-
async function refreshToken(config: OAuthConfig, refreshTokenValue: string): Promise<TokenResponse> {
|
|
92
|
-
const params = new URLSearchParams({
|
|
93
|
-
grant_type: 'refresh_token',
|
|
94
|
-
client_id: config.clientId,
|
|
95
|
-
refresh_token: refreshTokenValue,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const response = await fetch(config.tokenUrl, {
|
|
99
|
-
method: 'POST',
|
|
100
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
101
|
-
body: params.toString(),
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
if (!response.ok) {
|
|
105
|
-
throw new Error(`Token refresh failed: ${response.status}`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return response.json() as Promise<TokenResponse>;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Generate PKCE code verifier and challenge for Authorization Code flow.
|
|
113
|
-
*/
|
|
114
|
-
export function generatePkce(): { codeVerifier: string; codeChallenge: string } {
|
|
115
|
-
const codeVerifier = crypto.randomBytes(32).toString('base64url');
|
|
116
|
-
const codeChallenge = crypto
|
|
117
|
-
.createHash('sha256')
|
|
118
|
-
.update(codeVerifier)
|
|
119
|
-
.digest('base64url');
|
|
120
|
-
return { codeVerifier, codeChallenge };
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Build the authorization URL for the Authorization Code + PKCE flow.
|
|
125
|
-
*/
|
|
126
|
-
export function buildAuthorizationUrl(config: OAuthConfig, state: string, codeChallenge: string): string {
|
|
127
|
-
const params = new URLSearchParams({
|
|
128
|
-
response_type: 'code',
|
|
129
|
-
client_id: config.clientId,
|
|
130
|
-
redirect_uri: config.redirectUri,
|
|
131
|
-
state,
|
|
132
|
-
code_challenge: codeChallenge,
|
|
133
|
-
code_challenge_method: 'S256',
|
|
134
|
-
...(config.scopes.length > 0 ? { scope: config.scopes.join(' ') } : {}),
|
|
135
|
-
});
|
|
136
|
-
return `${config.authorizationUrl}?${params.toString()}`;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Exchange an authorization code for tokens (PKCE flow).
|
|
141
|
-
*/
|
|
142
|
-
export async function exchangeCode(config: OAuthConfig, code: string, codeVerifier: string): Promise<TokenResponse> {
|
|
143
|
-
const params = new URLSearchParams({
|
|
144
|
-
grant_type: 'authorization_code',
|
|
145
|
-
client_id: config.clientId,
|
|
146
|
-
code,
|
|
147
|
-
redirect_uri: config.redirectUri,
|
|
148
|
-
code_verifier: codeVerifier,
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
const response = await fetch(config.tokenUrl, {
|
|
152
|
-
method: 'POST',
|
|
153
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
154
|
-
body: params.toString(),
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
if (!response.ok) {
|
|
158
|
-
const body = await response.text();
|
|
159
|
-
throw new Error(`OAuth code exchange failed (${response.status}): ${body}`);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const token = await response.json() as TokenResponse;
|
|
163
|
-
cacheToken(token);
|
|
164
|
-
return token;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function cacheToken(token: TokenResponse): void {
|
|
168
|
-
cachedToken = {
|
|
169
|
-
accessToken: token.access_token,
|
|
170
|
-
expiresAt: Date.now() + (token.expires_in ?? 3600) * 1000,
|
|
171
|
-
refreshToken: token.refresh_token,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* OAuth Authorization Server Metadata (RFC 8414).
|
|
177
|
-
*/
|
|
178
|
-
export function getAuthServerMetadata(config: OAuthConfig, issuer: string): Record<string, unknown> {
|
|
179
|
-
return {
|
|
180
|
-
issuer,
|
|
181
|
-
authorization_endpoint: config.authorizationUrl,
|
|
182
|
-
token_endpoint: config.tokenUrl,
|
|
183
|
-
response_types_supported: ['code'],
|
|
184
|
-
grant_types_supported: ['authorization_code', 'client_credentials', 'refresh_token'],
|
|
185
|
-
code_challenge_methods_supported: ['S256'],
|
|
186
|
-
scopes_supported: config.scopes,
|
|
187
|
-
};
|
|
188
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "{{serverName}}",
|
|
3
|
-
"version": "{{serverVersion}}",
|
|
4
|
-
"type": "module",
|
|
5
|
-
"private": true,
|
|
6
|
-
"scripts": {
|
|
7
|
-
"build": "tsc",
|
|
8
|
-
"start": "node dist/index.js",
|
|
9
|
-
"dev": "tsx src/index.ts",
|
|
10
|
-
"test": "vitest run"
|
|
11
|
-
},
|
|
12
|
-
"dependencies": {
|
|
13
|
-
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
14
|
-
"zod": "^3.24.0"
|
|
15
|
-
},
|
|
16
|
-
"devDependencies": {
|
|
17
|
-
"@types/node": "^22.0.0",
|
|
18
|
-
"typescript": "^5.8.0",
|
|
19
|
-
"tsx": "^4.19.0",
|
|
20
|
-
"vitest": "^3.1.0"
|
|
21
|
-
},
|
|
22
|
-
"engines": {
|
|
23
|
-
"node": ">=20.0.0"
|
|
24
|
-
}
|
|
25
|
-
}
|