@mcpmake/core 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.
Files changed (323) hide show
  1. package/dist/analyzer/auth-detector.d.ts +13 -0
  2. package/dist/analyzer/auth-detector.d.ts.map +1 -0
  3. package/dist/analyzer/auth-detector.js +143 -0
  4. package/dist/analyzer/auth-detector.js.map +1 -0
  5. package/dist/analyzer/dom-parser.d.ts +11 -0
  6. package/dist/analyzer/dom-parser.d.ts.map +1 -0
  7. package/dist/analyzer/dom-parser.js +260 -0
  8. package/dist/analyzer/dom-parser.js.map +1 -0
  9. package/dist/analyzer/goal-crawler.d.ts +26 -0
  10. package/dist/analyzer/goal-crawler.d.ts.map +1 -0
  11. package/dist/analyzer/goal-crawler.js +178 -0
  12. package/dist/analyzer/goal-crawler.js.map +1 -0
  13. package/dist/analyzer/hybrid-detector.d.ts +29 -0
  14. package/dist/analyzer/hybrid-detector.d.ts.map +1 -0
  15. package/dist/analyzer/hybrid-detector.js +97 -0
  16. package/dist/analyzer/hybrid-detector.js.map +1 -0
  17. package/dist/analyzer/index.d.ts +13 -0
  18. package/dist/analyzer/index.d.ts.map +1 -0
  19. package/dist/analyzer/index.js +9 -0
  20. package/dist/analyzer/index.js.map +1 -0
  21. package/dist/analyzer/screenshot-capture.d.ts +30 -0
  22. package/dist/analyzer/screenshot-capture.d.ts.map +1 -0
  23. package/dist/analyzer/screenshot-capture.js +43 -0
  24. package/dist/analyzer/screenshot-capture.js.map +1 -0
  25. package/dist/analyzer/selector-builder.d.ts +20 -0
  26. package/dist/analyzer/selector-builder.d.ts.map +1 -0
  27. package/dist/analyzer/selector-builder.js +200 -0
  28. package/dist/analyzer/selector-builder.js.map +1 -0
  29. package/dist/analyzer/semantic-analyzer.d.ts +14 -0
  30. package/dist/analyzer/semantic-analyzer.d.ts.map +1 -0
  31. package/dist/analyzer/semantic-analyzer.js +146 -0
  32. package/dist/analyzer/semantic-analyzer.js.map +1 -0
  33. package/dist/analyzer/site-crawler.d.ts +39 -0
  34. package/dist/analyzer/site-crawler.d.ts.map +1 -0
  35. package/dist/analyzer/site-crawler.js +236 -0
  36. package/dist/analyzer/site-crawler.js.map +1 -0
  37. package/dist/config/configurable-command.d.ts +14 -0
  38. package/dist/config/configurable-command.d.ts.map +1 -0
  39. package/dist/config/configurable-command.js +71 -0
  40. package/dist/config/configurable-command.js.map +1 -0
  41. package/dist/config/mcpmake-config.d.ts +69 -0
  42. package/dist/config/mcpmake-config.d.ts.map +1 -0
  43. package/dist/config/mcpmake-config.js +209 -0
  44. package/dist/config/mcpmake-config.js.map +1 -0
  45. package/dist/emitter/code-writer.d.ts +9 -0
  46. package/dist/emitter/code-writer.d.ts.map +1 -0
  47. package/dist/emitter/code-writer.js +26 -0
  48. package/dist/emitter/code-writer.js.map +1 -0
  49. package/dist/emitter/index.d.ts +33 -0
  50. package/dist/emitter/index.d.ts.map +1 -0
  51. package/dist/emitter/index.js +297 -0
  52. package/dist/emitter/index.js.map +1 -0
  53. package/dist/emitter/mcpb-bundler.d.ts +32 -0
  54. package/dist/emitter/mcpb-bundler.d.ts.map +1 -0
  55. package/dist/emitter/mcpb-bundler.js +173 -0
  56. package/dist/emitter/mcpb-bundler.js.map +1 -0
  57. package/dist/emitter/project-scaffolder.d.ts +5 -0
  58. package/dist/emitter/project-scaffolder.d.ts.map +1 -0
  59. package/dist/emitter/project-scaffolder.js +101 -0
  60. package/dist/emitter/project-scaffolder.js.map +1 -0
  61. package/dist/emitter/python-template-loader.d.ts +5 -0
  62. package/dist/emitter/python-template-loader.d.ts.map +1 -0
  63. package/dist/emitter/python-template-loader.js +31 -0
  64. package/dist/emitter/python-template-loader.js.map +1 -0
  65. package/dist/emitter/python-templates/dockerfile.hbs +14 -0
  66. package/dist/emitter/python-templates/env.example.hbs +12 -0
  67. package/dist/emitter/python-templates/requirements.txt.hbs +4 -0
  68. package/dist/emitter/python-templates/server.py.hbs +93 -0
  69. package/dist/emitter/site-scaffolder.d.ts +14 -0
  70. package/dist/emitter/site-scaffolder.d.ts.map +1 -0
  71. package/dist/emitter/site-scaffolder.js +71 -0
  72. package/dist/emitter/site-scaffolder.js.map +1 -0
  73. package/dist/emitter/site-template-loader.d.ts +6 -0
  74. package/dist/emitter/site-template-loader.d.ts.map +1 -0
  75. package/dist/emitter/site-template-loader.js +48 -0
  76. package/dist/emitter/site-template-loader.js.map +1 -0
  77. package/dist/emitter/site-templates/browser-manager.ts.hbs +233 -0
  78. package/dist/emitter/site-templates/config.ts.hbs +28 -0
  79. package/dist/emitter/site-templates/dockerfile.hbs +31 -0
  80. package/dist/emitter/site-templates/env.example.hbs +19 -0
  81. package/dist/emitter/site-templates/package.json.hbs +26 -0
  82. package/dist/emitter/site-templates/server-main-http.ts.hbs +108 -0
  83. package/dist/emitter/site-templates/server-main.ts.hbs +23 -0
  84. package/dist/emitter/site-templates/tool-handler-action.ts.hbs +86 -0
  85. package/dist/emitter/site-templates/tool-handler-form.ts.hbs +116 -0
  86. package/dist/emitter/site-templates/tool-handler-lifecycle.ts.hbs +146 -0
  87. package/dist/emitter/site-templates/tool-index.ts.hbs +11 -0
  88. package/dist/emitter/template-loader.d.ts +2 -0
  89. package/dist/emitter/template-loader.d.ts.map +1 -0
  90. package/dist/emitter/template-loader.js +28 -0
  91. package/dist/emitter/template-loader.js.map +1 -0
  92. package/dist/emitter/templates/auth-provider.ts.hbs +57 -0
  93. package/dist/emitter/templates/config.ts.hbs +110 -0
  94. package/dist/emitter/templates/discovery.ts.hbs +320 -0
  95. package/dist/emitter/templates/dockerfile.hbs +34 -0
  96. package/dist/emitter/templates/env.example.hbs +52 -0
  97. package/dist/emitter/templates/gitignore.hbs +5 -0
  98. package/dist/emitter/templates/http-executor.ts.hbs +159 -0
  99. package/dist/emitter/templates/oauth.ts.hbs +188 -0
  100. package/dist/emitter/templates/package.json.hbs +25 -0
  101. package/dist/emitter/templates/prompts.ts.hbs +22 -0
  102. package/dist/emitter/templates/readme.md.hbs +156 -0
  103. package/dist/emitter/templates/resources.ts.hbs +63 -0
  104. package/dist/emitter/templates/response-filter.ts.hbs +128 -0
  105. package/dist/emitter/templates/server-main-http.ts.hbs +442 -0
  106. package/dist/emitter/templates/server-main.ts.hbs +40 -0
  107. package/dist/emitter/templates/task-handlers.ts.hbs +189 -0
  108. package/dist/emitter/templates/task-manager.ts.hbs +139 -0
  109. package/dist/emitter/templates/task-sse.ts.hbs +105 -0
  110. package/dist/emitter/templates/tool-handler.ts.hbs +142 -0
  111. package/dist/emitter/templates/tool-index.ts.hbs +42 -0
  112. package/dist/emitter/templates/tool-test.ts.hbs +57 -0
  113. package/dist/emitter/templates/trace.ts.hbs +79 -0
  114. package/dist/emitter/templates/tsconfig.json.hbs +16 -0
  115. package/dist/emitter/templates/types.ts.hbs +5 -0
  116. package/dist/emitter/worker-template-loader.d.ts +6 -0
  117. package/dist/emitter/worker-template-loader.d.ts.map +1 -0
  118. package/dist/emitter/worker-template-loader.js +34 -0
  119. package/dist/emitter/worker-template-loader.js.map +1 -0
  120. package/dist/emitter/worker-templates/config.ts.hbs +93 -0
  121. package/dist/emitter/worker-templates/dev-vars.example.hbs +27 -0
  122. package/dist/emitter/worker-templates/gitignore.hbs +6 -0
  123. package/dist/emitter/worker-templates/package.json.hbs +24 -0
  124. package/dist/emitter/worker-templates/readme.md.hbs +73 -0
  125. package/dist/emitter/worker-templates/server.test.ts.hbs +20 -0
  126. package/dist/emitter/worker-templates/tool-handler.ts.hbs +103 -0
  127. package/dist/emitter/worker-templates/tool-index.ts.hbs +28 -0
  128. package/dist/emitter/worker-templates/tsconfig.json.hbs +17 -0
  129. package/dist/emitter/worker-templates/worker.ts.hbs +286 -0
  130. package/dist/emitter/worker-templates/wrangler.toml.hbs +19 -0
  131. package/dist/generator/spec-generator.d.ts +7 -0
  132. package/dist/generator/spec-generator.d.ts.map +1 -0
  133. package/dist/generator/spec-generator.js +51 -0
  134. package/dist/generator/spec-generator.js.map +1 -0
  135. package/dist/index.d.ts +75 -0
  136. package/dist/index.d.ts.map +1 -0
  137. package/dist/index.js +101 -0
  138. package/dist/index.js.map +1 -0
  139. package/dist/parser/har-filter.d.ts +9 -0
  140. package/dist/parser/har-filter.d.ts.map +1 -0
  141. package/dist/parser/har-filter.js +72 -0
  142. package/dist/parser/har-filter.js.map +1 -0
  143. package/dist/parser/har-loader.d.ts +3 -0
  144. package/dist/parser/har-loader.d.ts.map +1 -0
  145. package/dist/parser/har-loader.js +15 -0
  146. package/dist/parser/har-loader.js.map +1 -0
  147. package/dist/parser/har-normalizer.d.ts +21 -0
  148. package/dist/parser/har-normalizer.d.ts.map +1 -0
  149. package/dist/parser/har-normalizer.js +79 -0
  150. package/dist/parser/har-normalizer.js.map +1 -0
  151. package/dist/parser/index.d.ts +11 -0
  152. package/dist/parser/index.d.ts.map +1 -0
  153. package/dist/parser/index.js +7 -0
  154. package/dist/parser/index.js.map +1 -0
  155. package/dist/parser/openapi-loader.d.ts +7 -0
  156. package/dist/parser/openapi-loader.d.ts.map +1 -0
  157. package/dist/parser/openapi-loader.js +309 -0
  158. package/dist/parser/openapi-loader.js.map +1 -0
  159. package/dist/parser/operation-extractor.d.ts +14 -0
  160. package/dist/parser/operation-extractor.d.ts.map +1 -0
  161. package/dist/parser/operation-extractor.js +156 -0
  162. package/dist/parser/operation-extractor.js.map +1 -0
  163. package/dist/parser/overlay-loader.d.ts +11 -0
  164. package/dist/parser/overlay-loader.d.ts.map +1 -0
  165. package/dist/parser/overlay-loader.js +185 -0
  166. package/dist/parser/overlay-loader.js.map +1 -0
  167. package/dist/parser/postman-loader.d.ts +10 -0
  168. package/dist/parser/postman-loader.d.ts.map +1 -0
  169. package/dist/parser/postman-loader.js +107 -0
  170. package/dist/parser/postman-loader.js.map +1 -0
  171. package/dist/parser/schema-converter.d.ts +13 -0
  172. package/dist/parser/schema-converter.d.ts.map +1 -0
  173. package/dist/parser/schema-converter.js +118 -0
  174. package/dist/parser/schema-converter.js.map +1 -0
  175. package/dist/plugins/adapter.d.ts +41 -0
  176. package/dist/plugins/adapter.d.ts.map +1 -0
  177. package/dist/plugins/adapter.js +16 -0
  178. package/dist/plugins/adapter.js.map +1 -0
  179. package/dist/plugins/loader.d.ts +26 -0
  180. package/dist/plugins/loader.d.ts.map +1 -0
  181. package/dist/plugins/loader.js +59 -0
  182. package/dist/plugins/loader.js.map +1 -0
  183. package/dist/pricing.d.ts +56 -0
  184. package/dist/pricing.d.ts.map +1 -0
  185. package/dist/pricing.js +134 -0
  186. package/dist/pricing.js.map +1 -0
  187. package/dist/providers/index.d.ts +16 -0
  188. package/dist/providers/index.d.ts.map +1 -0
  189. package/dist/providers/index.js +57 -0
  190. package/dist/providers/index.js.map +1 -0
  191. package/dist/recorder/browser-recorder.d.ts +23 -0
  192. package/dist/recorder/browser-recorder.d.ts.map +1 -0
  193. package/dist/recorder/browser-recorder.js +206 -0
  194. package/dist/recorder/browser-recorder.js.map +1 -0
  195. package/dist/rescan/diff-engine.d.ts +6 -0
  196. package/dist/rescan/diff-engine.d.ts.map +1 -0
  197. package/dist/rescan/diff-engine.js +313 -0
  198. package/dist/rescan/diff-engine.js.map +1 -0
  199. package/dist/rescan/index.d.ts +4 -0
  200. package/dist/rescan/index.d.ts.map +1 -0
  201. package/dist/rescan/index.js +3 -0
  202. package/dist/rescan/index.js.map +1 -0
  203. package/dist/rescan/rescan-runner.d.ts +43 -0
  204. package/dist/rescan/rescan-runner.d.ts.map +1 -0
  205. package/dist/rescan/rescan-runner.js +70 -0
  206. package/dist/rescan/rescan-runner.js.map +1 -0
  207. package/dist/rescan/rescan-scheduler.d.ts +42 -0
  208. package/dist/rescan/rescan-scheduler.d.ts.map +1 -0
  209. package/dist/rescan/rescan-scheduler.js +180 -0
  210. package/dist/rescan/rescan-scheduler.js.map +1 -0
  211. package/dist/site-transformer/browser-tools.d.ts +11 -0
  212. package/dist/site-transformer/browser-tools.d.ts.map +1 -0
  213. package/dist/site-transformer/browser-tools.js +60 -0
  214. package/dist/site-transformer/browser-tools.js.map +1 -0
  215. package/dist/site-transformer/index.d.ts +3 -0
  216. package/dist/site-transformer/index.d.ts.map +1 -0
  217. package/dist/site-transformer/index.js +3 -0
  218. package/dist/site-transformer/index.js.map +1 -0
  219. package/dist/site-transformer/selector-healer.d.ts +9 -0
  220. package/dist/site-transformer/selector-healer.d.ts.map +1 -0
  221. package/dist/site-transformer/selector-healer.js +107 -0
  222. package/dist/site-transformer/selector-healer.js.map +1 -0
  223. package/dist/site-transformer/tool-generator.d.ts +14 -0
  224. package/dist/site-transformer/tool-generator.d.ts.map +1 -0
  225. package/dist/site-transformer/tool-generator.js +246 -0
  226. package/dist/site-transformer/tool-generator.js.map +1 -0
  227. package/dist/transformer/auth-detector.d.ts +29 -0
  228. package/dist/transformer/auth-detector.d.ts.map +1 -0
  229. package/dist/transformer/auth-detector.js +104 -0
  230. package/dist/transformer/auth-detector.js.map +1 -0
  231. package/dist/transformer/catalog-builder.d.ts +19 -0
  232. package/dist/transformer/catalog-builder.d.ts.map +1 -0
  233. package/dist/transformer/catalog-builder.js +57 -0
  234. package/dist/transformer/catalog-builder.js.map +1 -0
  235. package/dist/transformer/client-compat.d.ts +7 -0
  236. package/dist/transformer/client-compat.d.ts.map +1 -0
  237. package/dist/transformer/client-compat.js +45 -0
  238. package/dist/transformer/client-compat.js.map +1 -0
  239. package/dist/transformer/har-clusterer.d.ts +10 -0
  240. package/dist/transformer/har-clusterer.d.ts.map +1 -0
  241. package/dist/transformer/har-clusterer.js +28 -0
  242. package/dist/transformer/har-clusterer.js.map +1 -0
  243. package/dist/transformer/har-dedup.d.ts +11 -0
  244. package/dist/transformer/har-dedup.d.ts.map +1 -0
  245. package/dist/transformer/har-dedup.js +82 -0
  246. package/dist/transformer/har-dedup.js.map +1 -0
  247. package/dist/transformer/har-schema-inferrer.d.ts +16 -0
  248. package/dist/transformer/har-schema-inferrer.d.ts.map +1 -0
  249. package/dist/transformer/har-schema-inferrer.js +91 -0
  250. package/dist/transformer/har-schema-inferrer.js.map +1 -0
  251. package/dist/transformer/har-to-operations.d.ts +14 -0
  252. package/dist/transformer/har-to-operations.d.ts.map +1 -0
  253. package/dist/transformer/har-to-operations.js +193 -0
  254. package/dist/transformer/har-to-operations.js.map +1 -0
  255. package/dist/transformer/index.d.ts +9 -0
  256. package/dist/transformer/index.d.ts.map +1 -0
  257. package/dist/transformer/index.js +7 -0
  258. package/dist/transformer/index.js.map +1 -0
  259. package/dist/transformer/llm-namer.d.ts +7 -0
  260. package/dist/transformer/llm-namer.d.ts.map +1 -0
  261. package/dist/transformer/llm-namer.js +60 -0
  262. package/dist/transformer/llm-namer.js.map +1 -0
  263. package/dist/transformer/naming.d.ts +5 -0
  264. package/dist/transformer/naming.d.ts.map +1 -0
  265. package/dist/transformer/naming.js +31 -0
  266. package/dist/transformer/naming.js.map +1 -0
  267. package/dist/transformer/operation-filter.d.ts +14 -0
  268. package/dist/transformer/operation-filter.d.ts.map +1 -0
  269. package/dist/transformer/operation-filter.js +53 -0
  270. package/dist/transformer/operation-filter.js.map +1 -0
  271. package/dist/transformer/resource-builder.d.ts +13 -0
  272. package/dist/transformer/resource-builder.d.ts.map +1 -0
  273. package/dist/transformer/resource-builder.js +87 -0
  274. package/dist/transformer/resource-builder.js.map +1 -0
  275. package/dist/transformer/schema-merger.d.ts +15 -0
  276. package/dist/transformer/schema-merger.d.ts.map +1 -0
  277. package/dist/transformer/schema-merger.js +66 -0
  278. package/dist/transformer/schema-merger.js.map +1 -0
  279. package/dist/transformer/stainless-config.d.ts +75 -0
  280. package/dist/transformer/stainless-config.d.ts.map +1 -0
  281. package/dist/transformer/stainless-config.js +59 -0
  282. package/dist/transformer/stainless-config.js.map +1 -0
  283. package/dist/transformer/stainless-translator.d.ts +61 -0
  284. package/dist/transformer/stainless-translator.d.ts.map +1 -0
  285. package/dist/transformer/stainless-translator.js +375 -0
  286. package/dist/transformer/stainless-translator.js.map +1 -0
  287. package/dist/transformer/tool-builder.d.ts +4 -0
  288. package/dist/transformer/tool-builder.d.ts.map +1 -0
  289. package/dist/transformer/tool-builder.js +119 -0
  290. package/dist/transformer/tool-builder.js.map +1 -0
  291. package/dist/types/index.d.ts +140 -0
  292. package/dist/types/index.d.ts.map +1 -0
  293. package/dist/types/index.js +2 -0
  294. package/dist/types/index.js.map +1 -0
  295. package/dist/types/site.d.ts +285 -0
  296. package/dist/types/site.d.ts.map +1 -0
  297. package/dist/types/site.js +9 -0
  298. package/dist/types/site.js.map +1 -0
  299. package/dist/utils/fail.d.ts +49 -0
  300. package/dist/utils/fail.d.ts.map +1 -0
  301. package/dist/utils/fail.js +223 -0
  302. package/dist/utils/fail.js.map +1 -0
  303. package/dist/utils/fs.d.ts +6 -0
  304. package/dist/utils/fs.d.ts.map +1 -0
  305. package/dist/utils/fs.js +29 -0
  306. package/dist/utils/fs.js.map +1 -0
  307. package/dist/utils/interactive.d.ts +7 -0
  308. package/dist/utils/interactive.d.ts.map +1 -0
  309. package/dist/utils/interactive.js +31 -0
  310. package/dist/utils/interactive.js.map +1 -0
  311. package/dist/utils/logger.d.ts +2 -0
  312. package/dist/utils/logger.d.ts.map +1 -0
  313. package/dist/utils/logger.js +3 -0
  314. package/dist/utils/logger.js.map +1 -0
  315. package/dist/utils/sanitize.d.ts +29 -0
  316. package/dist/utils/sanitize.d.ts.map +1 -0
  317. package/dist/utils/sanitize.js +45 -0
  318. package/dist/utils/sanitize.js.map +1 -0
  319. package/dist/utils/watcher.d.ts +12 -0
  320. package/dist/utils/watcher.d.ts.map +1 -0
  321. package/dist/utils/watcher.js +37 -0
  322. package/dist/utils/watcher.js.map +1 -0
  323. package/package.json +45 -0
@@ -0,0 +1,110 @@
1
+ export interface AppConfig {
2
+ baseUrl: string;
3
+ {{#each authSchemes}}
4
+ {{#if (eq type "apiKey")}}
5
+ apiKey?: string;
6
+ {{/if}}
7
+ {{#if (eq type "http-bearer")}}
8
+ bearerToken?: string;
9
+ {{/if}}
10
+ {{#if (eq type "http-basic")}}
11
+ basicUsername?: string;
12
+ basicPassword?: string;
13
+ {{/if}}
14
+ {{#if (eq type "oauth2")}}
15
+ oauth2Token?: string;
16
+ oauth2ClientId?: string;
17
+ oauth2ClientSecret?: string;
18
+ oauth2AuthorizationUrl?: string;
19
+ oauth2TokenUrl?: string;
20
+ oauth2RedirectUri?: string;
21
+ {{/if}}
22
+ {{/each}}
23
+ maxRetries: number;
24
+ requestIntervalMs: number;
25
+ /** Extra headers added to every upstream request (non-breaking; empty by default). */
26
+ defaultHeaders: Record<string, string>;
27
+ }
28
+
29
+ /**
30
+ * Resolve the upstream base URL with optional named environments.
31
+ *
32
+ * `MCP_ENVIRONMENTS` is a JSON object mapping an environment name to a base URL,
33
+ * e.g. `{"production":"https://api.example.com","sandbox":"https://sandbox.example.com"}`.
34
+ * `API_ENVIRONMENT` selects one of those names. When either is unset (or the
35
+ * name is unknown) the resolver falls back to `BASE_URL`, so existing
36
+ * deployments are unaffected.
37
+ */
38
+ function resolveBaseUrl(): string {
39
+ const raw = process.env.MCP_ENVIRONMENTS;
40
+ const selected = process.env.API_ENVIRONMENT;
41
+ if (raw && selected) {
42
+ try {
43
+ const envs = JSON.parse(raw) as Record<string, unknown>;
44
+ const url = envs[selected];
45
+ if (typeof url === 'string' && url) {
46
+ return url.replace(/\/$/, '');
47
+ }
48
+ } catch {
49
+ // Malformed MCP_ENVIRONMENTS — fall through to BASE_URL.
50
+ }
51
+ }
52
+ return requireEnv('BASE_URL').replace(/\/$/, '');
53
+ }
54
+
55
+ /**
56
+ * Parse `MCP_DEFAULT_HEADERS` (a JSON object of header name → value) into a map
57
+ * applied to every upstream request. Invalid JSON or non-string values are
58
+ * ignored so a bad value never breaks the server.
59
+ */
60
+ function loadDefaultHeaders(): Record<string, string> {
61
+ const raw = process.env.MCP_DEFAULT_HEADERS;
62
+ if (!raw) return {};
63
+ try {
64
+ const parsed = JSON.parse(raw) as Record<string, unknown>;
65
+ const headers: Record<string, string> = {};
66
+ for (const [key, value] of Object.entries(parsed)) {
67
+ if (typeof value === 'string') headers[key] = value;
68
+ }
69
+ return headers;
70
+ } catch {
71
+ return {};
72
+ }
73
+ }
74
+
75
+ export function loadConfig(): AppConfig {
76
+ return {
77
+ baseUrl: resolveBaseUrl(),
78
+ {{#each authSchemes}}
79
+ {{#if (eq type "apiKey")}}
80
+ apiKey: process.env.{{envVarName}},
81
+ {{/if}}
82
+ {{#if (eq type "http-bearer")}}
83
+ bearerToken: process.env.{{envVarName}},
84
+ {{/if}}
85
+ {{#if (eq type "http-basic")}}
86
+ basicUsername: process.env.BASIC_USERNAME,
87
+ basicPassword: process.env.BASIC_PASSWORD,
88
+ {{/if}}
89
+ {{#if (eq type "oauth2")}}
90
+ oauth2Token: process.env.OAUTH2_TOKEN,
91
+ oauth2ClientId: process.env.OAUTH2_CLIENT_ID,
92
+ oauth2ClientSecret: process.env.OAUTH2_CLIENT_SECRET,
93
+ oauth2AuthorizationUrl: process.env.OAUTH2_AUTHORIZATION_URL,
94
+ oauth2TokenUrl: process.env.OAUTH2_TOKEN_URL,
95
+ oauth2RedirectUri: process.env.OAUTH2_REDIRECT_URI,
96
+ {{/if}}
97
+ {{/each}}
98
+ maxRetries: parseInt(process.env.MAX_RETRIES ?? '3', 10),
99
+ requestIntervalMs: parseInt(process.env.REQUEST_INTERVAL_MS ?? '100', 10),
100
+ defaultHeaders: loadDefaultHeaders(),
101
+ };
102
+ }
103
+
104
+ function requireEnv(name: string): string {
105
+ const value = process.env[name];
106
+ if (!value) {
107
+ throw new Error(`Missing required environment variable: ${name}`);
108
+ }
109
+ return value;
110
+ }
@@ -0,0 +1,320 @@
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 { applyJqFilter } from './response-filter.js';
12
+ import type { AppConfig } from './config.js';
13
+ import catalogData from './tool-catalog.json' with { type: 'json' };
14
+
15
+ interface CatalogEntry {
16
+ name: string;
17
+ title: string;
18
+ description: string;
19
+ method: string;
20
+ path: string;
21
+ inputSchema: Record<string, unknown>;
22
+ pathParams: string[];
23
+ queryParams: string[];
24
+ hasRequestBody: boolean;
25
+ requestBodyContentType: string;
26
+ }
27
+
28
+ const catalog: CatalogEntry[] = catalogData as CatalogEntry[];
29
+
30
+ // Simple TF-IDF search index built at startup
31
+ const searchIndex = buildSearchIndex(catalog);
32
+
33
+ export function registerDiscoveryTools(server: McpServer, config: AppConfig): void {
34
+ // 1. list_tools — list available tools with optional query filter
35
+ server.registerTool(
36
+ 'list_tools',
37
+ {
38
+ title: 'List Available Tools',
39
+ description: `List all ${catalog.length} available API tools. Use "query" to filter by name or description.`,
40
+ inputSchema: z.object({
41
+ query: z.string().optional().describe('Filter tools by name or description'),
42
+ offset: z.number().int().optional().describe('Pagination offset (default: 0)'),
43
+ limit: z.number().int().optional().describe('Max results (default: 20)'),
44
+ }),
45
+ annotations: { readOnlyHint: true },
46
+ },
47
+ async (input) => {
48
+ const query = (input as Record<string, unknown>).query as string | undefined;
49
+ const offset = ((input as Record<string, unknown>).offset as number) ?? 0;
50
+ const limit = ((input as Record<string, unknown>).limit as number) ?? 20;
51
+
52
+ let results = catalog;
53
+ if (query) {
54
+ results = searchTools(query);
55
+ }
56
+
57
+ const page = results.slice(offset, offset + limit);
58
+ const hasMore = offset + limit < results.length;
59
+
60
+ return {
61
+ content: [
62
+ {
63
+ type: 'text' as const,
64
+ text: JSON.stringify(
65
+ {
66
+ tools: page.map((t) => ({ name: t.name, description: t.description })),
67
+ total: results.length,
68
+ hasMore,
69
+ nextOffset: hasMore ? offset + limit : undefined,
70
+ },
71
+ null,
72
+ 2,
73
+ ),
74
+ },
75
+ ],
76
+ };
77
+ },
78
+ );
79
+
80
+ // 2. get_tool_schema — get the full input schema for a specific tool
81
+ server.registerTool(
82
+ 'get_tool_schema',
83
+ {
84
+ title: 'Get Tool Schema',
85
+ description: 'Get the full input schema and details for a specific tool by name.',
86
+ inputSchema: z.object({
87
+ name: z.string().describe('Tool name (from list_tools)'),
88
+ }),
89
+ annotations: { readOnlyHint: true },
90
+ },
91
+ async (input) => {
92
+ const name = (input as Record<string, unknown>).name as string;
93
+ const tool = catalog.find((t) => t.name === name);
94
+
95
+ if (!tool) {
96
+ return {
97
+ content: [{ type: 'text' as const, text: `Tool "${name}" not found.` }],
98
+ isError: true,
99
+ };
100
+ }
101
+
102
+ return {
103
+ content: [
104
+ {
105
+ type: 'text' as const,
106
+ text: JSON.stringify(
107
+ {
108
+ name: tool.name,
109
+ title: tool.title,
110
+ description: tool.description,
111
+ method: tool.method,
112
+ path: tool.path,
113
+ inputSchema: tool.inputSchema,
114
+ },
115
+ null,
116
+ 2,
117
+ ),
118
+ },
119
+ ],
120
+ };
121
+ },
122
+ );
123
+
124
+ // 3. execute_tool — execute a tool by name with arguments
125
+ server.registerTool(
126
+ 'execute_tool',
127
+ {
128
+ title: 'Execute Tool',
129
+ description: 'Execute any API tool by name. Get the schema first with get_tool_schema.',
130
+ inputSchema: z.object({
131
+ name: z.string().describe('Tool name'),
132
+ args: z.record(z.unknown()).optional().describe('Tool arguments (match the input schema)'),
133
+ jq_filter: z
134
+ .string()
135
+ .optional()
136
+ .describe('Optional jq-style filter applied to the response (e.g. ".data", ".items[]").'),
137
+ idempotency_key: z
138
+ .string()
139
+ .optional()
140
+ .describe('Optional Idempotency-Key header for safe retries of mutating requests.'),
141
+ }),
142
+ },
143
+ async (input) => {
144
+ const name = (input as Record<string, unknown>).name as string;
145
+ const args = ((input as Record<string, unknown>).args as Record<string, unknown>) ?? {};
146
+ const jqFilter = (input as Record<string, unknown>).jq_filter as string | undefined;
147
+ const idempotencyKey = (input as Record<string, unknown>).idempotency_key as
148
+ | string
149
+ | undefined;
150
+ const tool = catalog.find((t) => t.name === name);
151
+
152
+ if (!tool) {
153
+ return {
154
+ content: [{ type: 'text' as const, text: `Tool "${name}" not found.` }],
155
+ isError: true,
156
+ };
157
+ }
158
+
159
+ try {
160
+ // Build URL from path template
161
+ let url = config.baseUrl + tool.path;
162
+ for (const param of tool.pathParams) {
163
+ if (args[param] !== undefined) {
164
+ url = url.replace(`{${param}}`, encodeURIComponent(String(args[param])));
165
+ }
166
+ }
167
+
168
+ // Add query params
169
+ const queryParams = new URLSearchParams();
170
+ for (const param of tool.queryParams) {
171
+ if (args[param] !== undefined) {
172
+ queryParams.set(param, String(args[param]));
173
+ }
174
+ }
175
+ const qs = queryParams.toString();
176
+ if (qs) url += `?${qs}`;
177
+
178
+ const response = await executeRequest({
179
+ method: tool.method,
180
+ url,
181
+ ...(tool.hasRequestBody && args.body
182
+ ? { body: args.body, contentType: tool.requestBodyContentType }
183
+ : {}),
184
+ headers: {},
185
+ config,
186
+ idempotencyKey,
187
+ });
188
+
189
+ const result =
190
+ typeof jqFilter === 'string' && jqFilter
191
+ ? applyJqFilter(response.data, jqFilter)
192
+ : response.data;
193
+
194
+ return {
195
+ content: [
196
+ {
197
+ type: 'text' as const,
198
+ text: JSON.stringify(result, null, 2),
199
+ },
200
+ ],
201
+ };
202
+ } catch (error) {
203
+ const message = error instanceof Error ? error.message : String(error);
204
+ return {
205
+ content: [{ type: 'text' as const, text: `Error: ${message}` }],
206
+ isError: true,
207
+ };
208
+ }
209
+ },
210
+ );
211
+
212
+ // 4. search_tools — semantic search over tool descriptions
213
+ server.registerTool(
214
+ 'search_tools',
215
+ {
216
+ title: 'Search Tools',
217
+ description: 'Search for tools by description. Uses fuzzy matching.',
218
+ inputSchema: z.object({
219
+ description: z.string().describe('What you want to do (natural language)'),
220
+ limit: z.number().int().optional().describe('Max results (default: 5)'),
221
+ }),
222
+ annotations: { readOnlyHint: true },
223
+ },
224
+ async (input) => {
225
+ const description = (input as Record<string, unknown>).description as string;
226
+ const limit = ((input as Record<string, unknown>).limit as number) ?? 5;
227
+
228
+ const results = searchTools(description).slice(0, limit);
229
+
230
+ return {
231
+ content: [
232
+ {
233
+ type: 'text' as const,
234
+ text: JSON.stringify(
235
+ results.map((t) => ({ name: t.name, description: t.description, method: t.method, path: t.path })),
236
+ null,
237
+ 2,
238
+ ),
239
+ },
240
+ ],
241
+ };
242
+ },
243
+ );
244
+ }
245
+
246
+ // ---------------------------------------------------------------------------
247
+ // TF-IDF Search
248
+ // ---------------------------------------------------------------------------
249
+
250
+ interface SearchEntry {
251
+ tool: CatalogEntry;
252
+ terms: Map<string, number>; // term -> TF-IDF weight
253
+ }
254
+
255
+ function buildSearchIndex(tools: CatalogEntry[]): SearchEntry[] {
256
+ const docFreq = new Map<string, number>();
257
+ const docs: Array<{ tool: CatalogEntry; terms: string[] }> = [];
258
+
259
+ for (const tool of tools) {
260
+ const text = `${tool.name} ${tool.title} ${tool.description} ${tool.method} ${tool.path}`;
261
+ const terms = tokenize(text);
262
+ docs.push({ tool, terms });
263
+
264
+ const unique = new Set(terms);
265
+ for (const term of unique) {
266
+ docFreq.set(term, (docFreq.get(term) ?? 0) + 1);
267
+ }
268
+ }
269
+
270
+ const N = tools.length;
271
+ return docs.map(({ tool, terms }) => {
272
+ const termFreq = new Map<string, number>();
273
+ for (const t of terms) {
274
+ termFreq.set(t, (termFreq.get(t) ?? 0) + 1);
275
+ }
276
+
277
+ const weighted = new Map<string, number>();
278
+ for (const [term, tf] of termFreq) {
279
+ const df = docFreq.get(term) ?? 1;
280
+ const idf = Math.log(N / df);
281
+ weighted.set(term, tf * idf);
282
+ }
283
+
284
+ return { tool, terms: weighted };
285
+ });
286
+ }
287
+
288
+ function searchTools(query: string): CatalogEntry[] {
289
+ const queryTerms = tokenize(query);
290
+ if (queryTerms.length === 0) return catalog;
291
+
292
+ const scores: Array<{ tool: CatalogEntry; score: number }> = [];
293
+
294
+ for (const entry of searchIndex) {
295
+ let score = 0;
296
+ for (const qt of queryTerms) {
297
+ // Exact match
298
+ score += entry.terms.get(qt) ?? 0;
299
+ // Prefix match (fuzzy)
300
+ for (const [term, weight] of entry.terms) {
301
+ if (term.startsWith(qt) || qt.startsWith(term)) {
302
+ score += weight * 0.5;
303
+ }
304
+ }
305
+ }
306
+ if (score > 0) {
307
+ scores.push({ tool: entry.tool, score });
308
+ }
309
+ }
310
+
311
+ return scores.sort((a, b) => b.score - a.score).map((s) => s.tool);
312
+ }
313
+
314
+ function tokenize(text: string): string[] {
315
+ return text
316
+ .toLowerCase()
317
+ .replace(/[^a-z0-9]/g, ' ')
318
+ .split(/\s+/)
319
+ .filter((t) => t.length > 1);
320
+ }
@@ -0,0 +1,34 @@
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"]
@@ -0,0 +1,52 @@
1
+ # {{serverName}} configuration
2
+
3
+ # Base URL for the API
4
+ BASE_URL={{baseUrl}}
5
+
6
+ # Named environments (optional). MCP_ENVIRONMENTS is a JSON object mapping a
7
+ # name to a base URL; API_ENVIRONMENT selects one. Falls back to BASE_URL when
8
+ # unset or the name is unknown.
9
+ {{#if environmentsJson}}
10
+ MCP_ENVIRONMENTS={{environmentsJson}}
11
+ API_ENVIRONMENT={{defaultEnvironment}}
12
+ {{else}}
13
+ # MCP_ENVIRONMENTS={"production":"{{baseUrl}}","sandbox":"https://sandbox.example.com"}
14
+ # API_ENVIRONMENT=production
15
+ {{/if}}
16
+
17
+ {{#each authEnvVars}}
18
+ # {{description}}
19
+ {{name}}=
20
+ {{/each}}
21
+
22
+ # HTTP tuning
23
+ MAX_RETRIES=3
24
+ REQUEST_INTERVAL_MS=100
25
+
26
+ # Runtime tool filtering (optional). Comma-separated tool names.
27
+ # MCP_TOOLS — allowlist: only these tools are registered.
28
+ # MCP_EXCLUDE_TOOLS — denylist: these tools are never registered (applied after the allowlist).
29
+ # MCP_TOOLS=
30
+ # MCP_EXCLUDE_TOOLS=
31
+
32
+ # Custom default headers added to every upstream request (optional, JSON object).
33
+ # MCP_DEFAULT_HEADERS={"X-Tenant-Id":"acme","X-Trace":"1"}
34
+
35
+ # Auto-generate an Idempotency-Key header for mutating (POST/PUT/PATCH/DELETE)
36
+ # requests when no per-call idempotency_key is supplied (optional).
37
+ # MCP_IDEMPOTENCY_AUTO=true
38
+ {{#if (eq transport "http")}}
39
+
40
+ # Transport: "stdio" or "http"
41
+ TRANSPORT=http
42
+ PORT=3000
43
+ # Allowed origin for CORS/DNS-rebinding protection (leave empty to allow all in dev)
44
+ ALLOWED_ORIGIN=
45
+ # Bearer token required on every /mcp request. When set, clients must send
46
+ # `Authorization: Bearer <token>`. Leave empty to disable auth (local/dev only).
47
+ MCP_AUTH_TOKEN=
48
+ # Session mode (MCP 2026-07-28): leave empty for stateless (default, recommended
49
+ # — any request can hit any instance). Set to "true" for stateful per-session
50
+ # (Mcp-Session-Id) mode if a client needs resumable streams.
51
+ MCP_STATEFUL=
52
+ {{/if}}
@@ -0,0 +1,5 @@
1
+ node_modules/
2
+ dist/
3
+ .env
4
+ *.tgz
5
+ .DS_Store
@@ -0,0 +1,159 @@
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
+ * Optional Idempotency-Key for mutating (POST/PUT/PATCH/DELETE) requests. When
14
+ * absent and `MCP_IDEMPOTENCY_AUTO=true`, a UUID is generated automatically.
15
+ */
16
+ idempotencyKey?: string;
17
+ }
18
+
19
+ export interface ApiResponse {
20
+ status: number;
21
+ data: unknown;
22
+ headers: Record<string, string>;
23
+ }
24
+
25
+ export class ApiError extends Error {
26
+ constructor(
27
+ message: string,
28
+ public readonly status: number,
29
+ public readonly data: unknown,
30
+ ) {
31
+ super(message);
32
+ this.name = 'ApiError';
33
+ }
34
+ }
35
+
36
+ const RETRYABLE_STATUS_CODES = [429, 500, 502, 503, 504];
37
+
38
+ /** Methods that mutate server state and therefore benefit from an idempotency key. */
39
+ const MUTATING_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
40
+
41
+ let lastRequestTime = 0;
42
+
43
+ /**
44
+ * Resolve the Idempotency-Key for a mutating request. Precedence:
45
+ * 1. an explicit per-call key passed in `options.idempotencyKey`
46
+ * 2. when `MCP_IDEMPOTENCY_AUTO=true`, a generated UUID (so retries of the
47
+ * same upstream call carry a stable key for the duration of the attempt loop)
48
+ * Returns undefined for non-mutating methods or when neither is configured, so
49
+ * the default behavior is unchanged.
50
+ */
51
+ function resolveIdempotencyKey(method: string, explicit: string | undefined): string | undefined {
52
+ if (!MUTATING_METHODS.has(method)) return undefined;
53
+ if (explicit) return explicit;
54
+ // `process` is absent on Workers (nodejs_compat leaves it undefined here), so
55
+ // guard it; auto-keying is then simply off on that target unless a per-call
56
+ // key is supplied. The Web Crypto `crypto` global exists on both runtimes.
57
+ const auto =
58
+ typeof process !== 'undefined' && process.env?.MCP_IDEMPOTENCY_AUTO === 'true';
59
+ if (auto) {
60
+ return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
61
+ ? crypto.randomUUID()
62
+ : `idem-${Date.now()}-${Math.random().toString(36).slice(2)}`;
63
+ }
64
+ return undefined;
65
+ }
66
+
67
+ export async function executeRequest(options: RequestOptions): Promise<ApiResponse> {
68
+ const { config } = options;
69
+
70
+ // Rate limiting
71
+ const now = Date.now();
72
+ const elapsed = now - lastRequestTime;
73
+ if (elapsed < config.requestIntervalMs) {
74
+ await sleep(config.requestIntervalMs - elapsed);
75
+ }
76
+ lastRequestTime = Date.now();
77
+
78
+ const authHeaders = await getAuthHeaders(config);
79
+ // Header precedence (lowest → highest): auth, runtime default headers
80
+ // (MCP_DEFAULT_HEADERS), W3C Trace Context, then per-call headers.
81
+ // Propagate W3C Trace Context to the upstream API (no-op outside an HTTP
82
+ // request, e.g. stdio transport, where there is no active trace).
83
+ const method = options.method.toUpperCase();
84
+ const headers: Record<string, string> = {
85
+ ...authHeaders,
86
+ ...config.defaultHeaders,
87
+ ...traceHeaders(),
88
+ ...options.headers,
89
+ };
90
+
91
+ if (options.body && options.contentType) {
92
+ headers['Content-Type'] = options.contentType;
93
+ }
94
+
95
+ // Idempotency-Key for mutating requests (explicit per-call, or auto when enabled).
96
+ const idempotencyKey = resolveIdempotencyKey(method, options.idempotencyKey);
97
+ if (idempotencyKey && !headers['Idempotency-Key']) {
98
+ headers['Idempotency-Key'] = idempotencyKey;
99
+ }
100
+
101
+ let lastError: Error | undefined;
102
+
103
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
104
+ try {
105
+ const response = await fetch(options.url, {
106
+ method,
107
+ headers,
108
+ body: options.body ? JSON.stringify(options.body) : undefined,
109
+ });
110
+
111
+ if (RETRYABLE_STATUS_CODES.includes(response.status) && attempt < config.maxRetries) {
112
+ const retryAfter = response.headers.get('Retry-After');
113
+ const delay = retryAfter
114
+ ? parseInt(retryAfter, 10) * 1000
115
+ : 1000 * Math.pow(2, attempt);
116
+ await sleep(delay);
117
+ continue;
118
+ }
119
+
120
+ const data = await parseResponseBody(response);
121
+
122
+ if (!response.ok) {
123
+ throw new ApiError(
124
+ `${response.status} ${response.statusText}`,
125
+ response.status,
126
+ data,
127
+ );
128
+ }
129
+
130
+ return {
131
+ status: response.status,
132
+ data,
133
+ headers: Object.fromEntries(response.headers.entries()),
134
+ };
135
+ } catch (error) {
136
+ lastError = error as Error;
137
+ if (error instanceof ApiError && !RETRYABLE_STATUS_CODES.includes(error.status)) {
138
+ throw error;
139
+ }
140
+ if (attempt < config.maxRetries) {
141
+ await sleep(1000 * Math.pow(2, attempt));
142
+ }
143
+ }
144
+ }
145
+
146
+ throw lastError ?? new Error('Request failed after retries');
147
+ }
148
+
149
+ async function parseResponseBody(response: Response): Promise<unknown> {
150
+ const contentType = response.headers.get('content-type') ?? '';
151
+ if (contentType.includes('application/json')) {
152
+ return response.json();
153
+ }
154
+ return response.text();
155
+ }
156
+
157
+ function sleep(ms: number): Promise<void> {
158
+ return new Promise((resolve) => setTimeout(resolve, ms));
159
+ }