mcp4openapi 0.2.8 → 0.3.1

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 (358) hide show
  1. package/README.md +143 -63
  2. package/dist/scripts/validate-profile.js +3 -3
  3. package/dist/scripts/validate-profile.js.map +1 -1
  4. package/dist/src/argument-normalizer.d.ts +5 -0
  5. package/dist/src/argument-normalizer.d.ts.map +1 -0
  6. package/dist/src/argument-normalizer.js +61 -0
  7. package/dist/src/argument-normalizer.js.map +1 -0
  8. package/dist/src/auth/oauth-provider.d.ts +131 -0
  9. package/dist/src/auth/oauth-provider.d.ts.map +1 -0
  10. package/dist/src/auth/oauth-provider.js +839 -0
  11. package/dist/src/auth/oauth-provider.js.map +1 -0
  12. package/dist/src/cli-config.d.ts +9 -0
  13. package/dist/src/cli-config.d.ts.map +1 -0
  14. package/dist/src/cli-config.js +111 -0
  15. package/dist/src/cli-config.js.map +1 -0
  16. package/dist/src/core/cli-config.d.ts +9 -0
  17. package/dist/src/core/cli-config.d.ts.map +1 -0
  18. package/dist/src/core/cli-config.js +125 -0
  19. package/dist/src/core/cli-config.js.map +1 -0
  20. package/dist/src/core/constants.d.ts +86 -0
  21. package/dist/src/core/constants.d.ts.map +1 -0
  22. package/dist/src/core/constants.js +86 -0
  23. package/dist/src/core/constants.js.map +1 -0
  24. package/dist/src/core/errors.d.ts +59 -0
  25. package/dist/src/core/errors.d.ts.map +1 -0
  26. package/dist/src/core/errors.js +119 -0
  27. package/dist/src/core/errors.js.map +1 -0
  28. package/dist/src/core/filtering.d.ts +19 -0
  29. package/dist/src/core/filtering.d.ts.map +1 -0
  30. package/dist/src/core/filtering.js +292 -0
  31. package/dist/src/core/filtering.js.map +1 -0
  32. package/dist/src/core/index.d.ts +26 -0
  33. package/dist/src/core/index.d.ts.map +1 -0
  34. package/dist/src/core/index.js +276 -0
  35. package/dist/src/core/index.js.map +1 -0
  36. package/dist/src/core/lib.d.ts +8 -0
  37. package/dist/src/core/lib.d.ts.map +1 -0
  38. package/dist/src/core/lib.js +7 -0
  39. package/dist/src/core/lib.js.map +1 -0
  40. package/dist/src/core/logger.d.ts +59 -0
  41. package/dist/src/core/logger.d.ts.map +1 -0
  42. package/dist/src/core/logger.js +197 -0
  43. package/dist/src/core/logger.js.map +1 -0
  44. package/dist/src/core/metrics.d.ts +97 -0
  45. package/dist/src/core/metrics.d.ts.map +1 -0
  46. package/dist/src/core/metrics.js +273 -0
  47. package/dist/src/core/metrics.js.map +1 -0
  48. package/dist/src/core/naming-warnings.d.ts +23 -0
  49. package/dist/src/core/naming-warnings.d.ts.map +1 -0
  50. package/dist/src/core/naming-warnings.js +83 -0
  51. package/dist/src/core/naming-warnings.js.map +1 -0
  52. package/dist/src/core/naming.d.ts +58 -0
  53. package/dist/src/core/naming.d.ts.map +1 -0
  54. package/dist/src/core/naming.js +510 -0
  55. package/dist/src/core/naming.js.map +1 -0
  56. package/dist/src/errors.d.ts +6 -0
  57. package/dist/src/errors.d.ts.map +1 -1
  58. package/dist/src/errors.js +15 -6
  59. package/dist/src/errors.js.map +1 -1
  60. package/dist/src/filtering.d.ts +19 -0
  61. package/dist/src/filtering.d.ts.map +1 -0
  62. package/dist/src/filtering.js +292 -0
  63. package/dist/src/filtering.js.map +1 -0
  64. package/dist/src/generated-schemas.d.ts +290 -79
  65. package/dist/src/generated-schemas.d.ts.map +1 -1
  66. package/dist/src/generated-schemas.js +17 -2
  67. package/dist/src/generated-schemas.js.map +1 -1
  68. package/dist/src/http-transport-config.d.ts +6 -0
  69. package/dist/src/http-transport-config.d.ts.map +1 -0
  70. package/dist/src/http-transport-config.js +47 -0
  71. package/dist/src/http-transport-config.js.map +1 -0
  72. package/dist/src/http-transport.d.ts +63 -13
  73. package/dist/src/http-transport.d.ts.map +1 -1
  74. package/dist/src/http-transport.js +1045 -482
  75. package/dist/src/http-transport.js.map +1 -1
  76. package/dist/src/index.d.ts +1 -6
  77. package/dist/src/index.d.ts.map +1 -1
  78. package/dist/src/index.js +1 -170
  79. package/dist/src/index.js.map +1 -1
  80. package/dist/src/interceptors.d.ts +1 -0
  81. package/dist/src/interceptors.d.ts.map +1 -1
  82. package/dist/src/interceptors.js +73 -63
  83. package/dist/src/interceptors.js.map +1 -1
  84. package/dist/src/lib.d.ts +1 -7
  85. package/dist/src/lib.d.ts.map +1 -1
  86. package/dist/src/lib.js +1 -6
  87. package/dist/src/lib.js.map +1 -1
  88. package/dist/src/logger.d.ts +5 -0
  89. package/dist/src/logger.d.ts.map +1 -1
  90. package/dist/src/logger.js +9 -1
  91. package/dist/src/logger.js.map +1 -1
  92. package/dist/src/mcp/mcp-server-manager.d.ts +20 -0
  93. package/dist/src/mcp/mcp-server-manager.d.ts.map +1 -0
  94. package/dist/src/mcp/mcp-server-manager.js +38 -0
  95. package/dist/src/mcp/mcp-server-manager.js.map +1 -0
  96. package/dist/src/mcp/mcp-server.d.ts +205 -0
  97. package/dist/src/mcp/mcp-server.d.ts.map +1 -0
  98. package/dist/src/mcp/mcp-server.js +1473 -0
  99. package/dist/src/mcp/mcp-server.js.map +1 -0
  100. package/dist/src/mcp-server-manager.d.ts +20 -0
  101. package/dist/src/mcp-server-manager.d.ts.map +1 -0
  102. package/dist/src/mcp-server-manager.js +38 -0
  103. package/dist/src/mcp-server-manager.js.map +1 -0
  104. package/dist/src/mcp-server.d.ts +28 -0
  105. package/dist/src/mcp-server.d.ts.map +1 -1
  106. package/dist/src/mcp-server.js +406 -109
  107. package/dist/src/mcp-server.js.map +1 -1
  108. package/dist/src/metrics.d.ts +11 -0
  109. package/dist/src/metrics.d.ts.map +1 -1
  110. package/dist/src/metrics.js +61 -0
  111. package/dist/src/metrics.js.map +1 -1
  112. package/dist/src/oauth-provider.d.ts +5 -0
  113. package/dist/src/oauth-provider.d.ts.map +1 -1
  114. package/dist/src/oauth-provider.js +29 -1
  115. package/dist/src/oauth-provider.js.map +1 -1
  116. package/dist/src/openapi/openapi-parser.d.ts +70 -0
  117. package/dist/src/openapi/openapi-parser.d.ts.map +1 -0
  118. package/dist/src/openapi/openapi-parser.js +458 -0
  119. package/dist/src/openapi/openapi-parser.js.map +1 -0
  120. package/dist/src/profile/profile-loader.d.ts +78 -0
  121. package/dist/src/profile/profile-loader.d.ts.map +1 -0
  122. package/dist/src/profile/profile-loader.js +490 -0
  123. package/dist/src/profile/profile-loader.js.map +1 -0
  124. package/dist/src/profile/profile-registry.d.ts +19 -0
  125. package/dist/src/profile/profile-registry.d.ts.map +1 -0
  126. package/dist/src/profile/profile-registry.js +43 -0
  127. package/dist/src/profile/profile-registry.js.map +1 -0
  128. package/dist/src/profile/profile-resolver.d.ts +41 -0
  129. package/dist/src/profile/profile-resolver.d.ts.map +1 -0
  130. package/dist/src/profile/profile-resolver.js +324 -0
  131. package/dist/src/profile/profile-resolver.js.map +1 -0
  132. package/dist/src/profile/startup-profile.d.ts +17 -0
  133. package/dist/src/profile/startup-profile.d.ts.map +1 -0
  134. package/dist/src/profile/startup-profile.js +30 -0
  135. package/dist/src/profile/startup-profile.js.map +1 -0
  136. package/dist/src/profile/startup-validation.d.ts +11 -0
  137. package/dist/src/profile/startup-validation.d.ts.map +1 -0
  138. package/dist/src/profile/startup-validation.js +21 -0
  139. package/dist/src/profile/startup-validation.js.map +1 -0
  140. package/dist/src/profile-loader.d.ts +1 -0
  141. package/dist/src/profile-loader.d.ts.map +1 -1
  142. package/dist/src/profile-loader.js +14 -3
  143. package/dist/src/profile-loader.js.map +1 -1
  144. package/dist/src/profile-registry.d.ts +18 -0
  145. package/dist/src/profile-registry.d.ts.map +1 -0
  146. package/dist/src/profile-registry.js +26 -0
  147. package/dist/src/profile-registry.js.map +1 -0
  148. package/dist/src/profile-resolver.d.ts +19 -0
  149. package/dist/src/profile-resolver.d.ts.map +1 -0
  150. package/dist/src/profile-resolver.js +167 -0
  151. package/dist/src/profile-resolver.js.map +1 -0
  152. package/dist/src/proxy-executor.d.ts.map +1 -1
  153. package/dist/src/proxy-executor.js +7 -0
  154. package/dist/src/proxy-executor.js.map +1 -1
  155. package/dist/src/startup-profile.d.ts +17 -0
  156. package/dist/src/startup-profile.d.ts.map +1 -0
  157. package/dist/src/startup-profile.js +30 -0
  158. package/dist/src/startup-profile.js.map +1 -0
  159. package/dist/src/startup-validation.d.ts +11 -0
  160. package/dist/src/startup-validation.d.ts.map +1 -0
  161. package/dist/src/startup-validation.js +21 -0
  162. package/dist/src/startup-validation.js.map +1 -0
  163. package/dist/src/testing/dynamic-mock-server.d.ts +24 -0
  164. package/dist/src/testing/dynamic-mock-server.d.ts.map +1 -0
  165. package/dist/src/testing/dynamic-mock-server.js +138 -0
  166. package/dist/src/testing/dynamic-mock-server.js.map +1 -0
  167. package/dist/src/testing/request-assertions.d.ts +5 -0
  168. package/dist/src/testing/request-assertions.d.ts.map +1 -0
  169. package/dist/src/testing/request-assertions.js +165 -0
  170. package/dist/src/testing/request-assertions.js.map +1 -0
  171. package/dist/src/testing/template-utils.d.ts +10 -0
  172. package/dist/src/testing/template-utils.d.ts.map +1 -0
  173. package/dist/src/testing/template-utils.js +72 -0
  174. package/dist/src/testing/template-utils.js.map +1 -0
  175. package/dist/src/testing/test-http-utils.d.ts +1 -1
  176. package/dist/src/testing/test-http-utils.d.ts.map +1 -1
  177. package/dist/src/testing/test-http-utils.js +1 -1
  178. package/dist/src/testing/test-http-utils.js.map +1 -1
  179. package/dist/src/testing/test-loader.d.ts +6 -0
  180. package/dist/src/testing/test-loader.d.ts.map +1 -0
  181. package/dist/src/testing/test-loader.js +212 -0
  182. package/dist/src/testing/test-loader.js.map +1 -0
  183. package/dist/src/testing/test-schema.d.ts +1270 -0
  184. package/dist/src/testing/test-schema.d.ts.map +1 -0
  185. package/dist/src/testing/test-schema.js +76 -0
  186. package/dist/src/testing/test-schema.js.map +1 -0
  187. package/dist/src/tool-filter/compat.d.ts +49 -0
  188. package/dist/src/tool-filter/compat.d.ts.map +1 -0
  189. package/dist/src/tool-filter/compat.js +72 -0
  190. package/dist/src/tool-filter/compat.js.map +1 -0
  191. package/dist/src/tool-filter/config/env-config-parser.d.ts +38 -0
  192. package/dist/src/tool-filter/config/env-config-parser.d.ts.map +1 -0
  193. package/dist/src/tool-filter/config/env-config-parser.js +103 -0
  194. package/dist/src/tool-filter/config/env-config-parser.js.map +1 -0
  195. package/dist/src/tool-filter/config/header-config-parser.d.ts +37 -0
  196. package/dist/src/tool-filter/config/header-config-parser.d.ts.map +1 -0
  197. package/dist/src/tool-filter/config/header-config-parser.js +118 -0
  198. package/dist/src/tool-filter/config/header-config-parser.js.map +1 -0
  199. package/dist/src/tool-filter/errors.d.ts +18 -0
  200. package/dist/src/tool-filter/errors.d.ts.map +1 -0
  201. package/dist/src/tool-filter/errors.js +21 -0
  202. package/dist/src/tool-filter/errors.js.map +1 -0
  203. package/dist/src/tool-filter/filter/filter-engine.d.ts +45 -0
  204. package/dist/src/tool-filter/filter/filter-engine.d.ts.map +1 -0
  205. package/dist/src/tool-filter/filter/filter-engine.js +94 -0
  206. package/dist/src/tool-filter/filter/filter-engine.js.map +1 -0
  207. package/dist/src/tool-filter/filter/filter-rules.d.ts +44 -0
  208. package/dist/src/tool-filter/filter/filter-rules.d.ts.map +1 -0
  209. package/dist/src/tool-filter/filter/filter-rules.js +72 -0
  210. package/dist/src/tool-filter/filter/filter-rules.js.map +1 -0
  211. package/dist/src/tool-filter/filter/global-tool-filter.d.ts +40 -0
  212. package/dist/src/tool-filter/filter/global-tool-filter.d.ts.map +1 -0
  213. package/dist/src/tool-filter/filter/global-tool-filter.js +92 -0
  214. package/dist/src/tool-filter/filter/global-tool-filter.js.map +1 -0
  215. package/dist/src/tool-filter/filter/session-tool-filter.d.ts +29 -0
  216. package/dist/src/tool-filter/filter/session-tool-filter.d.ts.map +1 -0
  217. package/dist/src/tool-filter/filter/session-tool-filter.js +69 -0
  218. package/dist/src/tool-filter/filter/session-tool-filter.js.map +1 -0
  219. package/dist/src/tool-filter/index.d.ts +25 -0
  220. package/dist/src/tool-filter/index.d.ts.map +1 -0
  221. package/dist/src/tool-filter/index.js +30 -0
  222. package/dist/src/tool-filter/index.js.map +1 -0
  223. package/dist/src/tool-filter/integration/tool-filter-service.d.ts +44 -0
  224. package/dist/src/tool-filter/integration/tool-filter-service.d.ts.map +1 -0
  225. package/dist/src/tool-filter/integration/tool-filter-service.js +68 -0
  226. package/dist/src/tool-filter/integration/tool-filter-service.js.map +1 -0
  227. package/dist/src/tool-filter/operation/operation-classifier.d.ts +20 -0
  228. package/dist/src/tool-filter/operation/operation-classifier.d.ts.map +1 -0
  229. package/dist/src/tool-filter/operation/operation-classifier.js +26 -0
  230. package/dist/src/tool-filter/operation/operation-classifier.js.map +1 -0
  231. package/dist/src/tool-filter/operation/operation-detector.d.ts +30 -0
  232. package/dist/src/tool-filter/operation/operation-detector.d.ts.map +1 -0
  233. package/dist/src/tool-filter/operation/operation-detector.js +96 -0
  234. package/dist/src/tool-filter/operation/operation-detector.js.map +1 -0
  235. package/dist/src/tool-filter/operation/operation-resolver.d.ts +22 -0
  236. package/dist/src/tool-filter/operation/operation-resolver.d.ts.map +1 -0
  237. package/dist/src/tool-filter/operation/operation-resolver.js +32 -0
  238. package/dist/src/tool-filter/operation/operation-resolver.js.map +1 -0
  239. package/dist/src/tool-filter/regex/regex-compiler.d.ts +22 -0
  240. package/dist/src/tool-filter/regex/regex-compiler.d.ts.map +1 -0
  241. package/dist/src/tool-filter/regex/regex-compiler.js +56 -0
  242. package/dist/src/tool-filter/regex/regex-compiler.js.map +1 -0
  243. package/dist/src/tool-filter/regex/regex-validator.d.ts +24 -0
  244. package/dist/src/tool-filter/regex/regex-validator.d.ts.map +1 -0
  245. package/dist/src/tool-filter/regex/regex-validator.js +58 -0
  246. package/dist/src/tool-filter/regex/regex-validator.js.map +1 -0
  247. package/dist/src/tool-filter/types.d.ts +92 -0
  248. package/dist/src/tool-filter/types.d.ts.map +1 -0
  249. package/dist/src/tool-filter/types.js +5 -0
  250. package/dist/src/tool-filter/types.js.map +1 -0
  251. package/dist/src/tool-filter/utils.d.ts +11 -0
  252. package/dist/src/tool-filter/utils.d.ts.map +1 -0
  253. package/dist/src/tool-filter/utils.js +13 -0
  254. package/dist/src/tool-filter/utils.js.map +1 -0
  255. package/dist/src/tool-filter.d.ts +65 -0
  256. package/dist/src/tool-filter.d.ts.map +1 -0
  257. package/dist/src/tool-filter.js +471 -0
  258. package/dist/src/tool-filter.js.map +1 -0
  259. package/dist/src/tool-generator.d.ts +1 -0
  260. package/dist/src/tool-generator.d.ts.map +1 -1
  261. package/dist/src/tool-generator.js +15 -6
  262. package/dist/src/tool-generator.js.map +1 -1
  263. package/dist/src/tooling/composite-executor.d.ts +77 -0
  264. package/dist/src/tooling/composite-executor.d.ts.map +1 -0
  265. package/dist/src/tooling/composite-executor.js +198 -0
  266. package/dist/src/tooling/composite-executor.js.map +1 -0
  267. package/dist/src/tooling/dag-executor.d.ts +49 -0
  268. package/dist/src/tooling/dag-executor.d.ts.map +1 -0
  269. package/dist/src/tooling/dag-executor.js +138 -0
  270. package/dist/src/tooling/dag-executor.js.map +1 -0
  271. package/dist/src/tooling/proxy-executor.d.ts +86 -0
  272. package/dist/src/tooling/proxy-executor.d.ts.map +1 -0
  273. package/dist/src/tooling/proxy-executor.js +501 -0
  274. package/dist/src/tooling/proxy-executor.js.map +1 -0
  275. package/dist/src/tooling/tool-generator.d.ts +67 -0
  276. package/dist/src/tooling/tool-generator.d.ts.map +1 -0
  277. package/dist/src/tooling/tool-generator.js +222 -0
  278. package/dist/src/tooling/tool-generator.js.map +1 -0
  279. package/dist/src/transport/http-client-factory.d.ts +65 -0
  280. package/dist/src/transport/http-client-factory.d.ts.map +1 -0
  281. package/dist/src/transport/http-client-factory.js +143 -0
  282. package/dist/src/transport/http-client-factory.js.map +1 -0
  283. package/dist/src/transport/http-transport-config.d.ts +6 -0
  284. package/dist/src/transport/http-transport-config.d.ts.map +1 -0
  285. package/dist/src/transport/http-transport-config.js +63 -0
  286. package/dist/src/transport/http-transport-config.js.map +1 -0
  287. package/dist/src/transport/http-transport.d.ts +329 -0
  288. package/dist/src/transport/http-transport.d.ts.map +1 -0
  289. package/dist/src/transport/http-transport.js +2584 -0
  290. package/dist/src/transport/http-transport.js.map +1 -0
  291. package/dist/src/transport/interceptors.d.ts +119 -0
  292. package/dist/src/transport/interceptors.d.ts.map +1 -0
  293. package/dist/src/transport/interceptors.js +413 -0
  294. package/dist/src/transport/interceptors.js.map +1 -0
  295. package/dist/src/transport/profile-index.d.ts +84 -0
  296. package/dist/src/transport/profile-index.d.ts.map +1 -0
  297. package/dist/src/transport/profile-index.js +405 -0
  298. package/dist/src/transport/profile-index.js.map +1 -0
  299. package/dist/src/types/http-transport.d.ts +26 -0
  300. package/dist/src/types/http-transport.d.ts.map +1 -1
  301. package/dist/src/types/openapi.d.ts +3 -0
  302. package/dist/src/types/openapi.d.ts.map +1 -1
  303. package/dist/src/types/profile.d.ts +16 -1
  304. package/dist/src/types/profile.d.ts.map +1 -1
  305. package/dist/src/validation/argument-normalizer.d.ts +6 -0
  306. package/dist/src/validation/argument-normalizer.d.ts.map +1 -0
  307. package/dist/src/validation/argument-normalizer.js +70 -0
  308. package/dist/src/validation/argument-normalizer.js.map +1 -0
  309. package/dist/src/validation/jsonrpc-validator.d.ts +27 -0
  310. package/dist/src/validation/jsonrpc-validator.d.ts.map +1 -0
  311. package/dist/src/validation/jsonrpc-validator.js +58 -0
  312. package/dist/src/validation/jsonrpc-validator.js.map +1 -0
  313. package/dist/src/validation/schema-validator.d.ts +30 -0
  314. package/dist/src/validation/schema-validator.d.ts.map +1 -0
  315. package/dist/src/validation/schema-validator.js +128 -0
  316. package/dist/src/validation/schema-validator.js.map +1 -0
  317. package/dist/src/validation/validation-utils.d.ts +49 -0
  318. package/dist/src/validation/validation-utils.d.ts.map +1 -0
  319. package/dist/src/validation/validation-utils.js +139 -0
  320. package/dist/src/validation/validation-utils.js.map +1 -0
  321. package/html/profile-index.html +386 -0
  322. package/package.json +10 -3
  323. package/profile-schema.json +77 -3
  324. package/profiles/gitlab/developer-profile-oauth.json +1520 -0
  325. package/profiles/gitlab/developer-profile-oauth.test.json +3432 -0
  326. package/profiles/gitlab/developer-profile.json +1508 -0
  327. package/profiles/gitlab/developer-profile.test.json +3432 -0
  328. package/profiles/gitlab/openapi.yaml +6891 -0
  329. package/profiles/n8n/openapi.yaml +2441 -0
  330. package/profiles/n8n/profile-optimized.json +965 -0
  331. package/profiles/n8n/profile-optimized.test.json +1078 -0
  332. package/profiles/n8n/profile.json +1033 -0
  333. package/profiles/n8n/profile.test.json +983 -0
  334. package/profiles/n8n-nodes/openapi.yaml +24 -0
  335. package/profiles/n8n-nodes/profile-nodes.json +44 -0
  336. package/profiles/n8n-nodes/profile-nodes.test.json +91 -0
  337. package/profiles/semgrep/openapi.yaml +4706 -0
  338. package/profiles/semgrep/profile.json +692 -0
  339. package/profiles/semgrep/profile.test.json +471 -0
  340. package/profiles/youtrack/openapi.json +16976 -0
  341. package/profiles/youtrack/profile.json +608 -0
  342. package/profiles/youtrack/profile.test.json +1926 -0
  343. package/dist/src/testing/fixtures.d.ts +0 -684
  344. package/dist/src/testing/fixtures.d.ts.map +0 -1
  345. package/dist/src/testing/fixtures.js +0 -528
  346. package/dist/src/testing/fixtures.js.map +0 -1
  347. package/dist/src/testing/mock-gitlab-server.d.ts +0 -43
  348. package/dist/src/testing/mock-gitlab-server.d.ts.map +0 -1
  349. package/dist/src/testing/mock-gitlab-server.js +0 -1026
  350. package/dist/src/testing/mock-gitlab-server.js.map +0 -1
  351. package/dist/src/testing/mock-semgrep-server.d.ts +0 -32
  352. package/dist/src/testing/mock-semgrep-server.d.ts.map +0 -1
  353. package/dist/src/testing/mock-semgrep-server.js +0 -213
  354. package/dist/src/testing/mock-semgrep-server.js.map +0 -1
  355. package/dist/src/testing/mock-youtrack-server.d.ts +0 -11
  356. package/dist/src/testing/mock-youtrack-server.d.ts.map +0 -1
  357. package/dist/src/testing/mock-youtrack-server.js +0 -152
  358. package/dist/src/testing/mock-youtrack-server.js.map +0 -1
@@ -10,10 +10,12 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextpro
10
10
  import { OpenAPIParser } from './openapi-parser.js';
11
11
  import { ProfileLoader } from './profile-loader.js';
12
12
  import { ToolGenerator } from './tool-generator.js';
13
+ import { normalizeArguments } from './argument-normalizer.js';
13
14
  import { CompositeExecutor } from './composite-executor.js';
14
15
  import { ProxyDownloadExecutor } from './proxy-executor.js';
15
- import { ConfigurationError, OperationNotFoundError, ValidationError, AuthenticationError, AuthorizationError, RateLimitError, NetworkError, generateCorrelationId } from './errors.js';
16
- import { TIMEOUTS, OAUTH_RATE_LIMIT } from './constants.js';
16
+ import { enforceFiltering, parseFilteringHeader } from './filtering.js';
17
+ import { ConfigurationError, OperationNotFoundError, ResourceNotFoundError, ValidationError, AuthenticationError, AuthorizationError, RateLimitError, NetworkError, generateCorrelationId } from './errors.js';
18
+ import { OAUTH_RATE_LIMIT } from './constants.js';
17
19
  import { HttpClientFactory } from './http-client-factory.js';
18
20
  import { SchemaValidator } from './schema-validator.js';
19
21
  import { ConsoleLogger, JsonLogger } from './logger.js';
@@ -21,7 +23,25 @@ import { isInitializeRequest, isToolCallRequest } from './jsonrpc-validator.js';
21
23
  import { generateNameWarnings } from './naming-warnings.js';
22
24
  import { NamingStrategy } from './naming.js';
23
25
  import { isSafePropertyName } from './validation-utils.js';
26
+ import { ToolFilterService, EnvConfigParser, HeaderConfigParser, RegexCompiler, RegexValidator, OperationClassifier, OpenAPIOperationResolver, OperationDetector, applySessionToolFilter, } from './tool-filter/index.js';
27
+ import { buildHttpTransportBaseConfig } from './http-transport-config.js';
24
28
  export class MCPServer {
29
+ /**
30
+ * Execute a tools/call request via the JSON-RPC handler.
31
+ * Intended for internal use and tests to avoid accessing private methods.
32
+ */
33
+ async callToolRpc(name, args, sessionId, requestId = 1) {
34
+ const message = {
35
+ jsonrpc: '2.0',
36
+ id: requestId,
37
+ method: 'tools/call',
38
+ params: {
39
+ name,
40
+ arguments: args,
41
+ },
42
+ };
43
+ return this.handleToolCall(message, sessionId);
44
+ }
25
45
  /**
26
46
  * Filter response payload to include only specified fields.
27
47
  *
@@ -177,6 +197,9 @@ export class MCPServer {
177
197
  if (error instanceof OperationNotFoundError) {
178
198
  return `Operation not found: ${error.message} (correlation ID: ${correlationId})`;
179
199
  }
200
+ if (error instanceof ResourceNotFoundError) {
201
+ return `${error.message} (correlation ID: ${correlationId})`;
202
+ }
180
203
  // Configuration errors - safe to show (helps admin fix setup)
181
204
  if (error instanceof ConfigurationError) {
182
205
  return `Configuration error: ${error.message} (correlation ID: ${correlationId})`;
@@ -223,6 +246,7 @@ export class MCPServer {
223
246
  // Check if we should warn about long names
224
247
  this.checkToolNameLengths();
225
248
  }
249
+ this.applyGlobalToolFiltering();
226
250
  // Re-create logger with auth config for token redaction
227
251
  const authConfigs = this.getAuthConfigs();
228
252
  if (authConfigs.length > 0) {
@@ -346,6 +370,59 @@ export class MCPServer {
346
370
  const oauthConfig = configs.find(c => c.type === 'oauth');
347
371
  return oauthConfig?.oauth_config;
348
372
  }
373
+ buildOAuthConfigWithAllowedRedirectHosts(oauthConfig) {
374
+ if (!oauthConfig) {
375
+ return undefined;
376
+ }
377
+ return {
378
+ ...oauthConfig,
379
+ allowed_redirect_hosts: oauthConfig.allowed_redirect_hosts
380
+ || (process.env.MCP4_ALLOWED_ORIGINS
381
+ ? this.extractHostsFromOrigins(process.env.MCP4_ALLOWED_ORIGINS)
382
+ : undefined),
383
+ };
384
+ }
385
+ getProfileIdValue() {
386
+ if (!this.profile) {
387
+ throw new ConfigurationError('Profile not initialized. Call initialize() first.');
388
+ }
389
+ const profileId = this.profile.profile_id?.trim() || this.profile.profile_name;
390
+ if (!profileId) {
391
+ throw new ConfigurationError('Profile is missing profile_id and profile_name.');
392
+ }
393
+ return profileId;
394
+ }
395
+ getOAuthRateLimitConfig() {
396
+ const authConfigs = this.getAuthConfigs();
397
+ const oauthAuthConfig = authConfigs.find(c => c.type === 'oauth');
398
+ const oauthRateLimit = oauthAuthConfig?.oauth_rate_limit;
399
+ const max = oauthRateLimit?.max_requests
400
+ || parseInt(process.env.MCP4_OAUTH_RATE_LIMIT_MAX || String(OAUTH_RATE_LIMIT.MAX_REQUESTS), 10);
401
+ const windowMs = oauthRateLimit?.window_ms
402
+ || parseInt(process.env.MCP4_OAUTH_RATE_LIMIT_WINDOW_MS || String(OAUTH_RATE_LIMIT.WINDOW_MS), 10);
403
+ return { max, windowMs };
404
+ }
405
+ getHttpProfileContext() {
406
+ if (!this.profile) {
407
+ throw new ConfigurationError('Profile not initialized. Call initialize() first.');
408
+ }
409
+ const authConfigs = this.getAuthConfigs();
410
+ const baseUrl = this.getBaseUrl();
411
+ const oauthConfig = this.buildOAuthConfigWithAllowedRedirectHosts(this.getOAuthConfig());
412
+ const resourceMetadata = this.parser.getResourceMetadata();
413
+ const oauthRateLimit = this.getOAuthRateLimitConfig();
414
+ return {
415
+ profileId: this.getProfileIdValue(),
416
+ oauthConfig,
417
+ authConfigs,
418
+ baseUrl,
419
+ rateLimitOAuthMax: oauthRateLimit.max,
420
+ rateLimitOAuthWindowMs: oauthRateLimit.windowMs,
421
+ resourceName: this.profile.resource_name || resourceMetadata.name || 'MCP Server',
422
+ resourceDocumentation: this.profile.resource_documentation || resourceMetadata.documentation,
423
+ parser: this.parser,
424
+ };
425
+ }
349
426
  /**
350
427
  * Extract hostnames from origin patterns for OAuth redirect validation
351
428
  * e.g., "http://localhost:*,https://app.example.com" -> ["localhost", "app.example.com"]
@@ -385,7 +462,7 @@ export class MCPServer {
385
462
  /**
386
463
  * Get or create HTTP client for session
387
464
  */
388
- async getHttpClientForSession(sessionId) {
465
+ async getHttpClientForSession(sessionId, profileId) {
389
466
  if (!sessionId) {
390
467
  // Fallback to global client for stdio transport
391
468
  if (!this.httpClientFactory.hasGlobalClient()) {
@@ -408,7 +485,7 @@ export class MCPServer {
408
485
  throw new ConfigurationError('Profile not initialized. Call initialize() first.');
409
486
  }
410
487
  // Get auth token from session (ensures token is valid/refreshed)
411
- const authToken = await this.getAuthTokenFromSession(sessionId);
488
+ const authToken = await this.getAuthTokenFromSession(sessionId, profileId);
412
489
  // Create or get session client using factory
413
490
  return this.httpClientFactory.getOrCreateSessionClient(sessionId, {
414
491
  profile: this.profile,
@@ -420,7 +497,7 @@ export class MCPServer {
420
497
  * Get auth token from HTTP transport session
421
498
  * Ensures token is valid (refreshes if expired) before returning
422
499
  */
423
- async getAuthTokenFromSession(sessionId) {
500
+ async getAuthTokenFromSession(sessionId, profileId) {
424
501
  // Early return if sessionId is missing/empty
425
502
  // Prevents misleading warn logs with empty sessionId
426
503
  if (!sessionId) {
@@ -430,23 +507,24 @@ export class MCPServer {
430
507
  return undefined;
431
508
  }
432
509
  // Ensure token is valid (refresh if expired)
433
- const isValid = await this.httpTransport.ensureValidSessionToken(sessionId);
510
+ const effectiveProfileId = profileId || this.getProfileIdValue();
511
+ const isValid = await this.httpTransport.ensureValidSessionToken(effectiveProfileId, sessionId);
434
512
  if (!isValid) {
435
- this.logger.warn('Session token validation/refresh failed', { sessionId });
513
+ this.logger.warn('Session token validation/refresh failed', { profileId: effectiveProfileId, sessionId });
436
514
  // Still return token if available - let the API call fail with proper error
437
515
  }
438
516
  // Use public API instead of type casting
439
- return this.httpTransport.getSessionToken(sessionId);
517
+ return this.httpTransport.getSessionToken(effectiveProfileId, sessionId);
440
518
  }
441
519
  /**
442
520
  * Cleanup HTTP client for destroyed session
443
521
  *
444
522
  * Why: Prevent memory leak - sessions expire but cached clients stay forever
445
523
  */
446
- cleanupSessionClient(sessionId) {
524
+ cleanupSessionClient(profileId, sessionId) {
447
525
  const removed = this.httpClientFactory.cleanupSessionClient(sessionId);
448
526
  if (removed) {
449
- this.logger.info('Cleaned up session HTTP client', { sessionId });
527
+ this.logger.info('Cleaned up session HTTP client', { profileId, sessionId });
450
528
  }
451
529
  }
452
530
  /**
@@ -530,15 +608,16 @@ export class MCPServer {
530
608
  * Why separate: Simple tools map directly to single OpenAPI operation.
531
609
  * No result aggregation needed.
532
610
  */
533
- async executeSimpleTool(toolDef, args, sessionId) {
611
+ async executeSimpleTool(toolDef, args, sessionId, profileId) {
612
+ const normalizedArgs = normalizeArguments(toolDef, args);
534
613
  this.logger.debug('Executing simple tool', {
535
614
  toolName: toolDef.name,
536
- action: args['action'],
537
- resourceType: args['resource_type'],
615
+ action: normalizedArgs['action'],
616
+ resourceType: normalizedArgs['resource_type'],
538
617
  sessionId
539
618
  });
540
619
  // Get operation definition (can be string or ProxyDownloadOperation)
541
- const operationDef = this.toolGenerator.getOperationDefinition(toolDef, args);
620
+ const operationDef = this.toolGenerator.getOperationDefinition(toolDef, normalizedArgs);
542
621
  if (!operationDef) {
543
622
  throw new ValidationError(`Could not map tool action to operation`, {
544
623
  toolName: toolDef.name,
@@ -549,7 +628,7 @@ export class MCPServer {
549
628
  }
550
629
  // Check if this is a proxy download operation
551
630
  if (typeof operationDef === 'object' && operationDef.type === 'proxy_download') {
552
- return this.executeProxyDownload(operationDef, args, sessionId);
631
+ return this.executeProxyDownload(operationDef, normalizedArgs, sessionId, profileId);
553
632
  }
554
633
  // Regular string operation
555
634
  const operationId = operationDef;
@@ -558,9 +637,9 @@ export class MCPServer {
558
637
  throw new OperationNotFoundError(operationId);
559
638
  }
560
639
  // Build request
561
- const path = this.resolvePath(operation.path, args);
562
- const queryParams = this.extractQueryParams(operation, args);
563
- const body = this.extractBody(operation, args, toolDef);
640
+ const path = this.resolvePath(operation.path, normalizedArgs);
641
+ const queryParams = this.extractQueryParams(operation, normalizedArgs);
642
+ const body = this.extractBody(operation, normalizedArgs, toolDef);
564
643
  this.logger.debug('Executing HTTP request', {
565
644
  operationId,
566
645
  method: operation.method,
@@ -579,9 +658,9 @@ export class MCPServer {
579
658
  }
580
659
  }
581
660
  // Execute with session-specific client
582
- const httpClient = await this.getHttpClientForSession(sessionId);
661
+ const httpClient = await this.getHttpClientForSession(sessionId, profileId);
583
662
  // Set fields parameter if response_fields are configured for this action AND enabled
584
- const action = args.action;
663
+ const action = normalizedArgs.action;
585
664
  if (toolDef.send_response_fields_as_param && toolDef.response_fields && action && toolDef.response_fields[action]) {
586
665
  const fields = toolDef.response_fields[action];
587
666
  queryParams.fields = fields.join(',');
@@ -594,7 +673,7 @@ export class MCPServer {
594
673
  // Apply response field filtering if configured
595
674
  let result = response.body;
596
675
  if (toolDef.response_fields) {
597
- const action = args.action;
676
+ const action = normalizedArgs.action;
598
677
  if (action && toolDef.response_fields[action]) {
599
678
  const fields = toolDef.response_fields[action];
600
679
  result = this.filterFields(result, fields);
@@ -608,7 +687,7 @@ export class MCPServer {
608
687
  * Why: Some APIs return authenticated URLs that LLMs cannot fetch directly.
609
688
  * This proxies the download through the MCP server.
610
689
  */
611
- async executeProxyDownload(operation, args, sessionId) {
690
+ async executeProxyDownload(operation, args, sessionId, profileId) {
612
691
  this.logger.debug('Executing proxy download', {
613
692
  metadataEndpoint: operation.metadata_endpoint,
614
693
  urlField: operation.url_field,
@@ -634,7 +713,7 @@ export class MCPServer {
634
713
  };
635
714
  }
636
715
  // Get auth credentials for download
637
- const httpClient = await this.getHttpClientForSession(sessionId);
716
+ const httpClient = await this.getHttpClientForSession(sessionId, profileId);
638
717
  const authCredentials = httpClient.getAuthCredentials();
639
718
  // Execute proxy download
640
719
  const proxyExecutor = new ProxyDownloadExecutor(httpClient, this.logger);
@@ -781,75 +860,25 @@ export class MCPServer {
781
860
  */
782
861
  async runHttp(host, port) {
783
862
  const { HttpTransport } = await import('./http-transport.js');
784
- // Get OAuth config from profile (supports multi-auth)
785
- const oauthConfig = this.getOAuthConfig();
786
- if (oauthConfig) {
863
+ const profileContext = this.getHttpProfileContext();
864
+ if (profileContext.oauthConfig) {
787
865
  this.logger.info('OAuth authentication enabled for HTTP transport');
788
866
  }
789
- // Get auth configs for token validation
790
- const authConfigs = this.getAuthConfigs();
791
- const baseUrl = this.getBaseUrl();
792
- // Extract OAuth rate limit from profile (if configured)
793
- const oauthAuthConfig = authConfigs.find(c => c.type === 'oauth');
794
- const oauthRateLimit = oauthAuthConfig?.oauth_rate_limit;
795
- // Extract resource metadata from OpenAPI spec or profile
796
- const resourceMetadata = this.parser.getResourceMetadata();
867
+ const baseConfig = buildHttpTransportBaseConfig(host, port);
797
868
  const config = {
798
- host,
799
- port,
800
- sessionTimeoutMs: parseInt(process.env.MCP4_SESSION_TIMEOUT_MS || String(TIMEOUTS.SESSION_TIMEOUT_MS), 10),
801
- heartbeatEnabled: process.env.MCP4_HEARTBEAT_ENABLED === 'true',
802
- heartbeatIntervalMs: parseInt(process.env.MCP4_HEARTBEAT_INTERVAL_MS || String(TIMEOUTS.HEARTBEAT_INTERVAL_MS), 10),
803
- metricsEnabled: process.env.MCP4_METRICS_ENABLED === 'true',
804
- metricsPath: process.env.MCP4_METRICS_PATH || '/metrics',
805
- allowedOrigins: process.env.MCP4_ALLOWED_ORIGINS
806
- ? process.env.MCP4_ALLOWED_ORIGINS.split(',').map(o => o.trim())
807
- : undefined,
808
- rateLimitEnabled: process.env.MCP4_HTTP_RATE_LIMIT_ENABLED !== 'false', // default: true
809
- rateLimitWindowMs: parseInt(process.env.MCP4_HTTP_RATE_LIMIT_WINDOW_MS || String(TIMEOUTS.RATE_LIMIT_WINDOW_MS), 10),
810
- rateLimitMaxRequests: parseInt(process.env.MCP4_HTTP_RATE_LIMIT_MAX_REQUESTS || '100', 10),
811
- rateLimitMetricsMax: parseInt(process.env.MCP4_HTTP_RATE_LIMIT_METRICS_MAX || '10', 10),
869
+ ...baseConfig,
870
+ profileRoutingEnabled: false,
871
+ defaultProfileId: profileContext.profileId,
812
872
  // OAuth rate limiting (priority: profile > env vars > defaults)
813
- rateLimitOAuthMax: oauthRateLimit?.max_requests
814
- || parseInt(process.env.MCP4_OAUTH_RATE_LIMIT_MAX || String(OAUTH_RATE_LIMIT.MAX_REQUESTS), 10),
815
- rateLimitOAuthWindowMs: oauthRateLimit?.window_ms
816
- || parseInt(process.env.MCP4_OAUTH_RATE_LIMIT_WINDOW_MS || String(OAUTH_RATE_LIMIT.WINDOW_MS), 10),
817
- maxTokenLength: process.env.MCP4_TOKEN_MAX_LENGTH
818
- ? parseInt(process.env.MCP4_TOKEN_MAX_LENGTH, 10)
819
- : undefined, // Uses default from http-transport.ts if undefined
820
- // Pass OAuth config with allowed_redirect_hosts derived from MCP4_ALLOWED_ORIGINS
821
- oauthConfig: oauthConfig ? {
822
- ...oauthConfig,
823
- allowed_redirect_hosts: oauthConfig.allowed_redirect_hosts
824
- || (process.env.MCP4_ALLOWED_ORIGINS
825
- ? this.extractHostsFromOrigins(process.env.MCP4_ALLOWED_ORIGINS)
826
- : undefined),
827
- } : undefined,
828
- baseUrl, // Pass base URL for token validation
829
- authConfigs, // Pass auth configs for token validation
830
- // OAuth resource metadata (priority: profile > OpenAPI > fallback)
831
- resourceName: this.profile?.resource_name || resourceMetadata.name || 'MCP Server',
832
- resourceDocumentation: this.profile?.resource_documentation || resourceMetadata.documentation,
833
- sslCertFile: process.env.MCP4_SSL_CERT_FILE,
834
- sslKeyFile: process.env.MCP4_SSL_KEY_FILE,
835
- oauthSessionTimeoutMs: (() => {
836
- if (process.env.MCP4_OAUTH_SESSION_TIMEOUT_MS === undefined)
837
- return undefined;
838
- const parsed = parseInt(process.env.MCP4_OAUTH_SESSION_TIMEOUT_MS, 10);
839
- if (Number.isNaN(parsed)) {
840
- throw new ConfigurationError(`Invalid MCP4_OAUTH_SESSION_TIMEOUT_MS: expected integer milliseconds, got '${process.env.MCP4_OAUTH_SESSION_TIMEOUT_MS}'`);
841
- }
842
- return parsed;
843
- })(),
844
- oauthRefreshThresholdMs: (() => {
845
- if (process.env.MCP4_OAUTH_REFRESH_THRESHOLD_MS === undefined)
846
- return undefined;
847
- const parsed = parseInt(process.env.MCP4_OAUTH_REFRESH_THRESHOLD_MS, 10);
848
- if (Number.isNaN(parsed)) {
849
- throw new ConfigurationError(`Invalid MCP4_OAUTH_REFRESH_THRESHOLD_MS: expected integer milliseconds, got '${process.env.MCP4_OAUTH_REFRESH_THRESHOLD_MS}'`);
850
- }
851
- return parsed;
852
- })(),
873
+ rateLimitOAuthMax: profileContext.rateLimitOAuthMax,
874
+ rateLimitOAuthWindowMs: profileContext.rateLimitOAuthWindowMs,
875
+ // OAuth config already merged with allowed_redirect_hosts
876
+ oauthConfig: profileContext.oauthConfig,
877
+ baseUrl: profileContext.baseUrl,
878
+ authConfigs: profileContext.authConfigs,
879
+ resourceName: profileContext.resourceName,
880
+ resourceDocumentation: profileContext.resourceDocumentation,
881
+ parser: profileContext.parser,
853
882
  };
854
883
  // Warn if binding to non-localhost without explicit MCP4_ALLOWED_ORIGINS
855
884
  const isLocalhost = host === 'localhost' || host === '127.0.0.1' || host === '::1';
@@ -858,37 +887,58 @@ export class MCPServer {
858
887
  this.logger.warn('Binding to non-localhost with empty MCP4_ALLOWED_ORIGINS. Set MCP4_ALLOWED_ORIGINS or bind to localhost.');
859
888
  }
860
889
  this.httpTransport = new HttpTransport(config, this.logger);
890
+ this.recordGlobalToolFilterMetrics();
861
891
  // Set message handler to process JSON-RPC messages
862
- this.httpTransport.setMessageHandler(async (message, sessionId) => {
863
- return await this.handleJsonRpcMessage(message, sessionId);
892
+ this.httpTransport.setMessageHandler(async (message, sessionId, profileId) => {
893
+ return await this.handleJsonRpcMessage(message, sessionId, profileId);
864
894
  });
865
895
  // Register cleanup listener for session destruction (memory leak prevention)
866
- this.httpTransport.onSessionDestroyed((sessionId) => {
867
- this.cleanupSessionClient(sessionId);
896
+ this.httpTransport.onSessionDestroyed((profileId, sessionId) => {
897
+ this.cleanupSessionClient(profileId, sessionId);
868
898
  });
869
899
  await this.httpTransport.start();
870
900
  this.logger.info('MCP server running on HTTP', { host, port });
871
901
  }
902
+ attachHttpTransport(transport) {
903
+ this.httpTransport = transport;
904
+ }
905
+ handleSessionDestroyed(profileId, sessionId) {
906
+ this.cleanupSessionClient(profileId, sessionId);
907
+ }
872
908
  /**
873
909
  * Handle JSON-RPC message from HTTP transport
874
910
  *
875
911
  * Why: Unified message handling for both stdio and HTTP transports
876
912
  */
877
- async handleJsonRpcMessage(message, sessionId) {
913
+ async handleJsonRpcMessage(message, sessionId, profileId) {
878
914
  // Handle initialize
879
915
  if (isInitializeRequest(message)) {
880
- return this.handleInitialize(message, sessionId);
916
+ return this.handleInitialize(message, sessionId, profileId);
881
917
  }
882
918
  // Handle tool calls
883
919
  if (isToolCallRequest(message)) {
884
- return await this.handleToolCall(message, sessionId);
920
+ return await this.handleToolCall(message, sessionId, profileId);
885
921
  }
886
922
  // Handle other JSON-RPC requests
887
923
  // (tools/list, prompts/list, etc.)
888
- return await this.handleOtherRequest(message, sessionId);
924
+ return await this.handleOtherRequest(message, sessionId, profileId);
889
925
  }
890
- handleInitialize(message, sessionId) {
926
+ async handleHttpMessage(message, sessionId, profileId) {
927
+ return this.handleJsonRpcMessage(message, sessionId, profileId);
928
+ }
929
+ handleInitialize(message, sessionId, profileId) {
891
930
  const req = message;
931
+ const params = req.params;
932
+ if (!this.httpTransport && params?.filtering !== undefined) {
933
+ if (typeof params.filtering !== 'string') {
934
+ throw new ValidationError('Invalid X-Mcp4-Params header. Expected comma-separated key=value pairs.');
935
+ }
936
+ const parsed = parseFilteringHeader(params.filtering);
937
+ this.stdioFiltering = parsed.filtering;
938
+ }
939
+ if (this.httpTransport && sessionId) {
940
+ this.applySessionToolFiltering(sessionId, profileId);
941
+ }
892
942
  const result = {
893
943
  protocolVersion: '2025-03-26',
894
944
  serverInfo: {
@@ -911,14 +961,14 @@ export class MCPServer {
911
961
  result,
912
962
  };
913
963
  }
914
- async handleToolCall(message, sessionId) {
964
+ async handleToolCall(message, sessionId, profileId) {
915
965
  const req = message;
916
966
  const params = req.params;
917
967
  const toolName = params.name;
918
968
  const args = params.arguments;
919
969
  // Check OAuth authentication for tool operations
920
- if (this.httpTransport && this.httpTransport.hasOAuthProvider()) {
921
- const authToken = await this.getAuthTokenFromSession(sessionId || '');
970
+ if (this.httpTransport && this.httpTransport.hasOAuthProvider(profileId)) {
971
+ const authToken = await this.getAuthTokenFromSession(sessionId || '', profileId);
922
972
  if (!authToken) {
923
973
  // Return OAuth required error with WWW-Authenticate header
924
974
  // This should trigger the OAuth flow in the client
@@ -930,7 +980,7 @@ export class MCPServer {
930
980
  message: 'Authentication required. Please authorize via OAuth.',
931
981
  data: {
932
982
  oauth_required: true,
933
- resource_metadata: `${this.httpTransport.getServerUrl()}/.well-known/oauth-protected-resource/mcp`,
983
+ resource_metadata: this.httpTransport.getOAuthProtectedResourceUrl(profileId),
934
984
  scope: 'api'
935
985
  }
936
986
  }
@@ -942,12 +992,30 @@ export class MCPServer {
942
992
  // Find tool definition
943
993
  const toolDef = this.profile?.tools.find(t => t.name === toolName);
944
994
  if (!toolDef) {
945
- throw new OperationNotFoundError(toolName);
995
+ throw new ResourceNotFoundError(toolName, 'Tool');
996
+ }
997
+ const toolFilter = this.getToolFilterForSession(sessionId, profileId);
998
+ if (toolFilter && !toolFilter.allowedToolNames.has(toolName)) {
999
+ this.recordToolFilterRejection(toolName, 'session');
1000
+ const reason = toolFilter.reasons.get(toolName)?.[0];
1001
+ const reasonSuffix = reason ? ` Blocked by: ${reason}.` : '';
1002
+ throw new AuthorizationError(`Tool '${toolName}' not allowed by X-Mcp4-Tools filter.${reasonSuffix}`);
1003
+ }
1004
+ const filtering = this.getFilteringForSession(sessionId, profileId);
1005
+ if (filtering) {
1006
+ const operation = this.getFilteringOperationInfo(toolDef, args);
1007
+ enforceFiltering({
1008
+ filtering,
1009
+ toolDef,
1010
+ args,
1011
+ parameterAliases: this.profile?.parameter_aliases,
1012
+ operation,
1013
+ });
946
1014
  }
947
1015
  // Execute tool (reuse existing execution logic)
948
1016
  let result;
949
1017
  if (toolDef.composite && toolDef.steps) {
950
- const httpClient = await this.getHttpClientForSession(sessionId);
1018
+ const httpClient = await this.getHttpClientForSession(sessionId, profileId);
951
1019
  const compositeResult = await this.compositeExecutor.execute(toolDef.steps, args, toolDef.partial_results || false, httpClient);
952
1020
  result = {
953
1021
  data: compositeResult.data,
@@ -958,7 +1026,7 @@ export class MCPServer {
958
1026
  };
959
1027
  }
960
1028
  else {
961
- result = await this.executeSimpleTool(toolDef, args, sessionId);
1029
+ result = await this.executeSimpleTool(toolDef, args, sessionId, profileId);
962
1030
  }
963
1031
  return {
964
1032
  jsonrpc: '2.0',
@@ -1003,6 +1071,9 @@ export class MCPServer {
1003
1071
  else if (error instanceof OperationNotFoundError) {
1004
1072
  errorCode = -32601; // Method not found
1005
1073
  }
1074
+ else if (error instanceof ResourceNotFoundError) {
1075
+ errorCode = -32601; // Method not found
1076
+ }
1006
1077
  return {
1007
1078
  jsonrpc: '2.0',
1008
1079
  id: req.id,
@@ -1013,11 +1084,35 @@ export class MCPServer {
1013
1084
  };
1014
1085
  }
1015
1086
  }
1016
- async handleOtherRequest(message, sessionId) {
1087
+ getFilteringForSession(sessionId, profileId) {
1088
+ if (this.httpTransport && sessionId) {
1089
+ const effectiveProfileId = profileId || this.getProfileIdValue();
1090
+ return this.httpTransport.getSessionFiltering(effectiveProfileId, sessionId);
1091
+ }
1092
+ return this.stdioFiltering;
1093
+ }
1094
+ getToolFilterForSession(sessionId, profileId) {
1095
+ if (this.httpTransport && sessionId && typeof this.httpTransport.getSessionToolFilter === 'function') {
1096
+ const effectiveProfileId = profileId || this.getProfileIdValue();
1097
+ return this.httpTransport.getSessionToolFilter(effectiveProfileId, sessionId);
1098
+ }
1099
+ return undefined;
1100
+ }
1101
+ getFilteringOperationInfo(toolDef, args) {
1102
+ if (toolDef.composite) {
1103
+ return undefined;
1104
+ }
1105
+ const operationId = this.toolGenerator.mapActionToOperation(toolDef, args);
1106
+ if (!operationId) {
1107
+ return undefined;
1108
+ }
1109
+ return this.parser.getOperation(operationId);
1110
+ }
1111
+ async handleOtherRequest(message, sessionId, profileId) {
1017
1112
  const req = message;
1018
1113
  // Check OAuth authentication for other operations (like tools/list)
1019
- if (this.httpTransport && this.httpTransport.hasOAuthProvider()) {
1020
- const authToken = await this.getAuthTokenFromSession(sessionId || '');
1114
+ if (this.httpTransport && this.httpTransport.hasOAuthProvider(profileId)) {
1115
+ const authToken = await this.getAuthTokenFromSession(sessionId || '', profileId);
1021
1116
  if (!authToken) {
1022
1117
  // Return OAuth required error with WWW-Authenticate header
1023
1118
  // This should trigger the OAuth flow in the client
@@ -1029,7 +1124,7 @@ export class MCPServer {
1029
1124
  message: 'Authentication required. Please authorize via OAuth.',
1030
1125
  data: {
1031
1126
  oauth_required: true,
1032
- resource_metadata: `${this.httpTransport.getServerUrl()}/.well-known/oauth-protected-resource/mcp`,
1127
+ resource_metadata: this.httpTransport.getOAuthProtectedResourceUrl(profileId),
1033
1128
  scope: 'api'
1034
1129
  }
1035
1130
  }
@@ -1039,7 +1134,11 @@ export class MCPServer {
1039
1134
  }
1040
1135
  // Handle tools/list
1041
1136
  if (req.method === 'tools/list') {
1042
- const tools = this.profile?.tools.map(toolDef => this.toolGenerator.generateTool(toolDef)) || [];
1137
+ const sessionFilter = this.getToolFilterForSession(sessionId, profileId);
1138
+ const allowedSet = sessionFilter?.allowedToolNames;
1139
+ const tools = this.profile?.tools
1140
+ .filter(toolDef => !allowedSet || allowedSet.has(toolDef.name))
1141
+ .map(toolDef => this.toolGenerator.generateTool(toolDef)) || [];
1043
1142
  return {
1044
1143
  jsonrpc: '2.0',
1045
1144
  id: req.id,
@@ -1058,6 +1157,204 @@ export class MCPServer {
1058
1157
  },
1059
1158
  };
1060
1159
  }
1160
+ applyGlobalToolFiltering() {
1161
+ if (!this.profile) {
1162
+ return;
1163
+ }
1164
+ // Initialize ToolFilterService if not already done
1165
+ if (!this.toolFilterService) {
1166
+ const validator = new RegexValidator();
1167
+ const compiler = new RegexCompiler(validator);
1168
+ const envParser = new EnvConfigParser(compiler);
1169
+ const headerParser = new HeaderConfigParser(compiler);
1170
+ // Create OperationDetector for category filtering
1171
+ const classifier = new OperationClassifier();
1172
+ const resolver = new OpenAPIOperationResolver(this.parser);
1173
+ const detector = new OperationDetector(classifier, resolver);
1174
+ this.toolFilterService = new ToolFilterService(envParser, headerParser, this.logger, detector);
1175
+ }
1176
+ const originalTools = this.profile.tools;
1177
+ const originalCount = originalTools.length;
1178
+ // Apply filtering using new service
1179
+ const filteredTools = this.toolFilterService.applyGlobalFilter(originalTools, process.env);
1180
+ const allowedCount = filteredTools.length;
1181
+ const removedCount = originalCount - allowedCount;
1182
+ // Early return if no filtering config present (service returned same tools)
1183
+ if (filteredTools === originalTools) {
1184
+ return;
1185
+ }
1186
+ // Validation: check if filter has no effect
1187
+ if (originalCount > 0 && allowedCount === originalCount && removedCount === 0) {
1188
+ throw new ConfigurationError(`Tool filter configuration has no effect. Original tool count: ${originalCount}, filtered: ${allowedCount}. Check MCP4_TOOL_FILTER_* patterns.`);
1189
+ }
1190
+ // Validation: check if all tools filtered
1191
+ if (originalCount > 0 && allowedCount === 0) {
1192
+ throw new ConfigurationError(`All tools filtered out (original: ${originalCount}). Check MCP4_TOOL_FILTER_* settings.`);
1193
+ }
1194
+ // Validate composite tools against filtered operations
1195
+ const resolver = this.buildToolFilterResolver();
1196
+ this.validateCompositeToolsAgainstFilteredOperations(originalTools, filteredTools, resolver);
1197
+ // Update profile
1198
+ this.profile.tools = filteredTools;
1199
+ // Record summary for metrics
1200
+ this.globalToolFilterSummary = {
1201
+ originalCount,
1202
+ allowedCount,
1203
+ removedCount,
1204
+ patternCounts: {
1205
+ // Note: counts not available from new service, using simplified version
1206
+ filtered: removedCount
1207
+ }
1208
+ };
1209
+ // Warn if high percentage filtered
1210
+ const warnThreshold = this.getToolFilterWarnThresholdPct();
1211
+ if (originalCount > 0) {
1212
+ const percentFiltered = (removedCount / originalCount) * 100;
1213
+ if (percentFiltered >= warnThreshold) {
1214
+ this.logger.warn('Tool filter removed high percentage of tools', {
1215
+ original: originalCount,
1216
+ surviving: allowedCount,
1217
+ threshold_pct: warnThreshold,
1218
+ removed_count: removedCount
1219
+ });
1220
+ }
1221
+ }
1222
+ if (this.httpTransport) {
1223
+ this.recordGlobalToolFilterMetrics();
1224
+ }
1225
+ }
1226
+ applySessionToolFiltering(sessionId, profileId) {
1227
+ if (!this.httpTransport || !this.profile) {
1228
+ return;
1229
+ }
1230
+ if (typeof this.httpTransport.getSessionToolFilterRequest !== 'function') {
1231
+ return;
1232
+ }
1233
+ const effectiveProfileId = profileId || this.getProfileIdValue();
1234
+ const request = this.httpTransport.getSessionToolFilterRequest(effectiveProfileId, sessionId);
1235
+ if (!request) {
1236
+ return;
1237
+ }
1238
+ const originalCount = this.profile.tools.length;
1239
+ const resolver = this.buildToolFilterResolver();
1240
+ const sessionFilter = applySessionToolFilter(this.profile.tools, request, resolver);
1241
+ const allowedCount = sessionFilter.allowedToolNames.size;
1242
+ if (allowedCount === originalCount) {
1243
+ throw new ValidationError(`X-Mcp4-Tools filter has no effect for this session. Available tools: ${originalCount}, after filter: ${allowedCount}. Check patterns.`);
1244
+ }
1245
+ if (originalCount > 0 && allowedCount === 0) {
1246
+ const sources = request.rawEntries.length > 0 ? request.rawEntries.join(', ') : 'none';
1247
+ throw new ValidationError(`X-Mcp4-Tools filtered out all tools (original: ${originalCount}). Removed by: ${sources}. Check session filter configuration.`);
1248
+ }
1249
+ this.httpTransport.setSessionToolFilter(effectiveProfileId, sessionId, sessionFilter);
1250
+ this.logger.info('Session tool filter applied', {
1251
+ sessionId,
1252
+ originalCount,
1253
+ allowedCount,
1254
+ patterns: request.rawEntries,
1255
+ });
1256
+ this.recordSessionToolFilterMetrics(sessionId, allowedCount, request);
1257
+ }
1258
+ buildToolFilterResolver() {
1259
+ return {
1260
+ getOperationById: (operationId) => this.parser.getOperation(operationId),
1261
+ getOperationForCall: (call) => {
1262
+ const [method, path] = call.split(' ');
1263
+ if (!method || !path) {
1264
+ return undefined;
1265
+ }
1266
+ const pathInfo = this.parser.getPath(path);
1267
+ return pathInfo?.operations[method.toLowerCase()];
1268
+ },
1269
+ };
1270
+ }
1271
+ validateCompositeToolsAgainstFilteredOperations(originalTools, allowedTools, resolver) {
1272
+ const operationToTools = new Map();
1273
+ for (const tool of originalTools) {
1274
+ if (!tool.operations) {
1275
+ continue;
1276
+ }
1277
+ for (const operationId of Object.values(tool.operations)) {
1278
+ if (typeof operationId !== 'string') {
1279
+ continue;
1280
+ }
1281
+ const names = operationToTools.get(operationId) ?? [];
1282
+ names.push(tool.name);
1283
+ operationToTools.set(operationId, names);
1284
+ }
1285
+ }
1286
+ const allowedOperationIds = new Set();
1287
+ for (const tool of allowedTools) {
1288
+ if (!tool.operations) {
1289
+ continue;
1290
+ }
1291
+ for (const operationId of Object.values(tool.operations)) {
1292
+ if (typeof operationId !== 'string') {
1293
+ continue;
1294
+ }
1295
+ allowedOperationIds.add(operationId);
1296
+ }
1297
+ }
1298
+ for (const tool of allowedTools) {
1299
+ if (!tool.composite || !tool.steps) {
1300
+ continue;
1301
+ }
1302
+ for (const step of tool.steps) {
1303
+ const operation = resolver.getOperationForCall(step.call);
1304
+ if (!operation) {
1305
+ continue;
1306
+ }
1307
+ if (allowedOperationIds.has(operation.operationId)) {
1308
+ continue;
1309
+ }
1310
+ const removedTools = operationToTools.get(operation.operationId);
1311
+ if (!removedTools || removedTools.length === 0) {
1312
+ continue;
1313
+ }
1314
+ const removedList = removedTools.join(', ');
1315
+ throw new ConfigurationError(`Composite tool '${tool.name}' step '${step.call}' calls filtered tool '${removedList}'. ` +
1316
+ `Add '${removedList}' to filter or include _allow_list or _allow_read if it is a list or read operation.`);
1317
+ }
1318
+ }
1319
+ }
1320
+ getToolFilterWarnThresholdPct() {
1321
+ const raw = process.env.MCP4_TOOL_FILTER_WARN_THRESHOLD_PCT;
1322
+ if (raw === undefined) {
1323
+ return 90;
1324
+ }
1325
+ const parsed = Number(raw);
1326
+ if (Number.isNaN(parsed) || parsed <= 0) {
1327
+ throw new ConfigurationError(`Invalid MCP4_TOOL_FILTER_WARN_THRESHOLD_PCT: expected positive number, got '${raw}'.`);
1328
+ }
1329
+ return parsed;
1330
+ }
1331
+ recordGlobalToolFilterMetrics() {
1332
+ if (!this.httpTransport || !this.globalToolFilterSummary) {
1333
+ return;
1334
+ }
1335
+ if (typeof this.httpTransport.recordGlobalToolFilterMetrics !== 'function') {
1336
+ return;
1337
+ }
1338
+ this.httpTransport.recordGlobalToolFilterMetrics(this.globalToolFilterSummary);
1339
+ }
1340
+ recordSessionToolFilterMetrics(sessionId, allowedCount, request) {
1341
+ if (!this.httpTransport) {
1342
+ return;
1343
+ }
1344
+ if (typeof this.httpTransport.recordSessionToolFilterMetrics !== 'function') {
1345
+ return;
1346
+ }
1347
+ this.httpTransport.recordSessionToolFilterMetrics(sessionId, allowedCount, request);
1348
+ }
1349
+ recordToolFilterRejection(toolName, source) {
1350
+ if (!this.httpTransport) {
1351
+ return;
1352
+ }
1353
+ if (typeof this.httpTransport.recordToolFilterRejection !== 'function') {
1354
+ return;
1355
+ }
1356
+ this.httpTransport.recordToolFilterRejection(toolName, source);
1357
+ }
1061
1358
  /**
1062
1359
  * Stop the MCP server gracefully
1063
1360
  *