mcp4openapi 0.2.7 → 0.3.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 (317) hide show
  1. package/README.md +147 -63
  2. package/dist/scripts/validate-profile.js +3 -3
  3. package/dist/scripts/validate-profile.js.map +1 -1
  4. package/dist/src/{oauth-provider.d.ts → auth/oauth-provider.d.ts} +7 -2
  5. package/dist/src/auth/oauth-provider.d.ts.map +1 -0
  6. package/dist/src/{oauth-provider.js → auth/oauth-provider.js} +30 -2
  7. package/dist/src/auth/oauth-provider.js.map +1 -0
  8. package/dist/src/core/cli-config.d.ts +9 -0
  9. package/dist/src/core/cli-config.d.ts.map +1 -0
  10. package/dist/src/core/cli-config.js +124 -0
  11. package/dist/src/core/cli-config.js.map +1 -0
  12. package/dist/src/{constants.d.ts → core/constants.d.ts} +1 -0
  13. package/dist/src/core/constants.d.ts.map +1 -0
  14. package/dist/src/{constants.js → core/constants.js} +1 -0
  15. package/dist/src/core/constants.js.map +1 -0
  16. package/dist/src/{errors.d.ts → core/errors.d.ts} +6 -0
  17. package/dist/src/core/errors.d.ts.map +1 -0
  18. package/dist/src/{errors.js → core/errors.js} +15 -6
  19. package/dist/src/core/errors.js.map +1 -0
  20. package/dist/src/core/filtering.d.ts +19 -0
  21. package/dist/src/core/filtering.d.ts.map +1 -0
  22. package/dist/src/core/filtering.js +292 -0
  23. package/dist/src/core/filtering.js.map +1 -0
  24. package/dist/src/core/index.d.ts +26 -0
  25. package/dist/src/core/index.d.ts.map +1 -0
  26. package/dist/src/core/index.js +275 -0
  27. package/dist/src/core/index.js.map +1 -0
  28. package/dist/src/core/lib.d.ts +8 -0
  29. package/dist/src/core/lib.d.ts.map +1 -0
  30. package/dist/src/core/lib.js +7 -0
  31. package/dist/src/core/lib.js.map +1 -0
  32. package/dist/src/{logger.d.ts → core/logger.d.ts} +6 -13
  33. package/dist/src/core/logger.d.ts.map +1 -0
  34. package/dist/src/core/logger.js +197 -0
  35. package/dist/src/core/logger.js.map +1 -0
  36. package/dist/src/{metrics.d.ts → core/metrics.d.ts} +11 -0
  37. package/dist/src/core/metrics.d.ts.map +1 -0
  38. package/dist/src/{metrics.js → core/metrics.js} +61 -0
  39. package/dist/src/core/metrics.js.map +1 -0
  40. package/dist/src/core/naming-warnings.d.ts.map +1 -0
  41. package/dist/src/{naming-warnings.js → core/naming-warnings.js} +6 -6
  42. package/dist/src/core/naming-warnings.js.map +1 -0
  43. package/dist/src/core/naming.d.ts.map +1 -0
  44. package/dist/src/core/naming.js.map +1 -0
  45. package/dist/src/generated-schemas.d.ts +281 -79
  46. package/dist/src/generated-schemas.d.ts.map +1 -1
  47. package/dist/src/generated-schemas.js +17 -3
  48. package/dist/src/generated-schemas.js.map +1 -1
  49. package/dist/src/index.d.ts +1 -6
  50. package/dist/src/index.d.ts.map +1 -1
  51. package/dist/src/index.js +1 -156
  52. package/dist/src/index.js.map +1 -1
  53. package/dist/src/lib.d.ts +1 -7
  54. package/dist/src/lib.d.ts.map +1 -1
  55. package/dist/src/lib.js +1 -6
  56. package/dist/src/lib.js.map +1 -1
  57. package/dist/src/mcp/mcp-server-manager.d.ts +20 -0
  58. package/dist/src/mcp/mcp-server-manager.d.ts.map +1 -0
  59. package/dist/src/mcp/mcp-server-manager.js +38 -0
  60. package/dist/src/mcp/mcp-server-manager.js.map +1 -0
  61. package/dist/src/{mcp-server.d.ts → mcp/mcp-server.d.ts} +43 -3
  62. package/dist/src/mcp/mcp-server.d.ts.map +1 -0
  63. package/dist/src/{mcp-server.js → mcp/mcp-server.js} +639 -123
  64. package/dist/src/mcp/mcp-server.js.map +1 -0
  65. package/dist/src/{openapi-parser.d.ts → openapi/openapi-parser.d.ts} +1 -1
  66. package/dist/src/openapi/openapi-parser.d.ts.map +1 -0
  67. package/dist/src/{openapi-parser.js → openapi/openapi-parser.js} +2 -2
  68. package/dist/src/openapi/openapi-parser.js.map +1 -0
  69. package/dist/src/{profile-loader.d.ts → profile/profile-loader.d.ts} +3 -2
  70. package/dist/src/profile/profile-loader.d.ts.map +1 -0
  71. package/dist/src/{profile-loader.js → profile/profile-loader.js} +17 -6
  72. package/dist/src/profile/profile-loader.js.map +1 -0
  73. package/dist/src/profile/profile-registry.d.ts +18 -0
  74. package/dist/src/profile/profile-registry.d.ts.map +1 -0
  75. package/dist/src/profile/profile-registry.js +26 -0
  76. package/dist/src/profile/profile-registry.js.map +1 -0
  77. package/dist/src/profile/profile-resolver.d.ts +25 -0
  78. package/dist/src/profile/profile-resolver.d.ts.map +1 -0
  79. package/dist/src/profile/profile-resolver.js +204 -0
  80. package/dist/src/profile/profile-resolver.js.map +1 -0
  81. package/dist/src/profile/startup-profile.d.ts +17 -0
  82. package/dist/src/profile/startup-profile.d.ts.map +1 -0
  83. package/dist/src/profile/startup-profile.js +30 -0
  84. package/dist/src/profile/startup-profile.js.map +1 -0
  85. package/dist/src/profile/startup-validation.d.ts +11 -0
  86. package/dist/src/profile/startup-validation.d.ts.map +1 -0
  87. package/dist/src/profile/startup-validation.js +21 -0
  88. package/dist/src/profile/startup-validation.js.map +1 -0
  89. package/dist/src/testing/dynamic-mock-server.d.ts +24 -0
  90. package/dist/src/testing/dynamic-mock-server.d.ts.map +1 -0
  91. package/dist/src/testing/dynamic-mock-server.js +138 -0
  92. package/dist/src/testing/dynamic-mock-server.js.map +1 -0
  93. package/dist/src/testing/listen-support.d.ts +3 -0
  94. package/dist/src/testing/listen-support.d.ts.map +1 -0
  95. package/dist/src/testing/listen-support.js +50 -0
  96. package/dist/src/testing/listen-support.js.map +1 -0
  97. package/dist/src/testing/request-assertions.d.ts +5 -0
  98. package/dist/src/testing/request-assertions.d.ts.map +1 -0
  99. package/dist/src/testing/request-assertions.js +165 -0
  100. package/dist/src/testing/request-assertions.js.map +1 -0
  101. package/dist/src/testing/template-utils.d.ts +10 -0
  102. package/dist/src/testing/template-utils.d.ts.map +1 -0
  103. package/dist/src/testing/template-utils.js +72 -0
  104. package/dist/src/testing/template-utils.js.map +1 -0
  105. package/dist/src/testing/test-http-utils.d.ts +1 -1
  106. package/dist/src/testing/test-http-utils.d.ts.map +1 -1
  107. package/dist/src/testing/test-http-utils.js +1 -1
  108. package/dist/src/testing/test-http-utils.js.map +1 -1
  109. package/dist/src/testing/test-loader.d.ts +6 -0
  110. package/dist/src/testing/test-loader.d.ts.map +1 -0
  111. package/dist/src/testing/test-loader.js +212 -0
  112. package/dist/src/testing/test-loader.js.map +1 -0
  113. package/dist/src/testing/test-schema.d.ts +1270 -0
  114. package/dist/src/testing/test-schema.d.ts.map +1 -0
  115. package/dist/src/testing/test-schema.js +76 -0
  116. package/dist/src/testing/test-schema.js.map +1 -0
  117. package/dist/src/tool-filter/compat.d.ts +49 -0
  118. package/dist/src/tool-filter/compat.d.ts.map +1 -0
  119. package/dist/src/tool-filter/compat.js +72 -0
  120. package/dist/src/tool-filter/compat.js.map +1 -0
  121. package/dist/src/tool-filter/config/env-config-parser.d.ts +38 -0
  122. package/dist/src/tool-filter/config/env-config-parser.d.ts.map +1 -0
  123. package/dist/src/tool-filter/config/env-config-parser.js +103 -0
  124. package/dist/src/tool-filter/config/env-config-parser.js.map +1 -0
  125. package/dist/src/tool-filter/config/header-config-parser.d.ts +37 -0
  126. package/dist/src/tool-filter/config/header-config-parser.d.ts.map +1 -0
  127. package/dist/src/tool-filter/config/header-config-parser.js +118 -0
  128. package/dist/src/tool-filter/config/header-config-parser.js.map +1 -0
  129. package/dist/src/tool-filter/errors.d.ts +18 -0
  130. package/dist/src/tool-filter/errors.d.ts.map +1 -0
  131. package/dist/src/tool-filter/errors.js +21 -0
  132. package/dist/src/tool-filter/errors.js.map +1 -0
  133. package/dist/src/tool-filter/filter/filter-engine.d.ts +45 -0
  134. package/dist/src/tool-filter/filter/filter-engine.d.ts.map +1 -0
  135. package/dist/src/tool-filter/filter/filter-engine.js +94 -0
  136. package/dist/src/tool-filter/filter/filter-engine.js.map +1 -0
  137. package/dist/src/tool-filter/filter/filter-rules.d.ts +44 -0
  138. package/dist/src/tool-filter/filter/filter-rules.d.ts.map +1 -0
  139. package/dist/src/tool-filter/filter/filter-rules.js +72 -0
  140. package/dist/src/tool-filter/filter/filter-rules.js.map +1 -0
  141. package/dist/src/tool-filter/filter/global-tool-filter.d.ts +40 -0
  142. package/dist/src/tool-filter/filter/global-tool-filter.d.ts.map +1 -0
  143. package/dist/src/tool-filter/filter/global-tool-filter.js +92 -0
  144. package/dist/src/tool-filter/filter/global-tool-filter.js.map +1 -0
  145. package/dist/src/tool-filter/filter/session-tool-filter.d.ts +29 -0
  146. package/dist/src/tool-filter/filter/session-tool-filter.d.ts.map +1 -0
  147. package/dist/src/tool-filter/filter/session-tool-filter.js +69 -0
  148. package/dist/src/tool-filter/filter/session-tool-filter.js.map +1 -0
  149. package/dist/src/tool-filter/index.d.ts +25 -0
  150. package/dist/src/tool-filter/index.d.ts.map +1 -0
  151. package/dist/src/tool-filter/index.js +30 -0
  152. package/dist/src/tool-filter/index.js.map +1 -0
  153. package/dist/src/tool-filter/integration/tool-filter-service.d.ts +44 -0
  154. package/dist/src/tool-filter/integration/tool-filter-service.d.ts.map +1 -0
  155. package/dist/src/tool-filter/integration/tool-filter-service.js +68 -0
  156. package/dist/src/tool-filter/integration/tool-filter-service.js.map +1 -0
  157. package/dist/src/tool-filter/operation/operation-classifier.d.ts +20 -0
  158. package/dist/src/tool-filter/operation/operation-classifier.d.ts.map +1 -0
  159. package/dist/src/tool-filter/operation/operation-classifier.js +26 -0
  160. package/dist/src/tool-filter/operation/operation-classifier.js.map +1 -0
  161. package/dist/src/tool-filter/operation/operation-detector.d.ts +30 -0
  162. package/dist/src/tool-filter/operation/operation-detector.d.ts.map +1 -0
  163. package/dist/src/tool-filter/operation/operation-detector.js +96 -0
  164. package/dist/src/tool-filter/operation/operation-detector.js.map +1 -0
  165. package/dist/src/tool-filter/operation/operation-resolver.d.ts +22 -0
  166. package/dist/src/tool-filter/operation/operation-resolver.d.ts.map +1 -0
  167. package/dist/src/tool-filter/operation/operation-resolver.js +32 -0
  168. package/dist/src/tool-filter/operation/operation-resolver.js.map +1 -0
  169. package/dist/src/tool-filter/regex/regex-compiler.d.ts +22 -0
  170. package/dist/src/tool-filter/regex/regex-compiler.d.ts.map +1 -0
  171. package/dist/src/tool-filter/regex/regex-compiler.js +56 -0
  172. package/dist/src/tool-filter/regex/regex-compiler.js.map +1 -0
  173. package/dist/src/tool-filter/regex/regex-validator.d.ts +24 -0
  174. package/dist/src/tool-filter/regex/regex-validator.d.ts.map +1 -0
  175. package/dist/src/tool-filter/regex/regex-validator.js +58 -0
  176. package/dist/src/tool-filter/regex/regex-validator.js.map +1 -0
  177. package/dist/src/tool-filter/types.d.ts +92 -0
  178. package/dist/src/tool-filter/types.d.ts.map +1 -0
  179. package/dist/src/tool-filter/types.js +5 -0
  180. package/dist/src/tool-filter/types.js.map +1 -0
  181. package/dist/src/tool-filter/utils.d.ts +11 -0
  182. package/dist/src/tool-filter/utils.d.ts.map +1 -0
  183. package/dist/src/tool-filter/utils.js +13 -0
  184. package/dist/src/tool-filter/utils.js.map +1 -0
  185. package/dist/src/{composite-executor.d.ts → tooling/composite-executor.d.ts} +3 -3
  186. package/dist/src/tooling/composite-executor.d.ts.map +1 -0
  187. package/dist/src/{composite-executor.js → tooling/composite-executor.js} +1 -1
  188. package/dist/src/tooling/composite-executor.js.map +1 -0
  189. package/dist/src/{dag-executor.d.ts → tooling/dag-executor.d.ts} +1 -1
  190. package/dist/src/tooling/dag-executor.d.ts.map +1 -0
  191. package/dist/src/tooling/dag-executor.js.map +1 -0
  192. package/dist/src/{proxy-executor.d.ts → tooling/proxy-executor.d.ts} +19 -4
  193. package/dist/src/tooling/proxy-executor.d.ts.map +1 -0
  194. package/dist/src/tooling/proxy-executor.js +497 -0
  195. package/dist/src/tooling/proxy-executor.js.map +1 -0
  196. package/dist/src/{tool-generator.d.ts → tooling/tool-generator.d.ts} +4 -3
  197. package/dist/src/tooling/tool-generator.d.ts.map +1 -0
  198. package/dist/src/{tool-generator.js → tooling/tool-generator.js} +23 -7
  199. package/dist/src/tooling/tool-generator.js.map +1 -0
  200. package/dist/src/{http-client-factory.d.ts → transport/http-client-factory.d.ts} +4 -1
  201. package/dist/src/transport/http-client-factory.d.ts.map +1 -0
  202. package/dist/src/{http-client-factory.js → transport/http-client-factory.js} +13 -3
  203. package/dist/src/transport/http-client-factory.js.map +1 -0
  204. package/dist/src/transport/http-transport-config.d.ts +6 -0
  205. package/dist/src/transport/http-transport-config.d.ts.map +1 -0
  206. package/dist/src/transport/http-transport-config.js +62 -0
  207. package/dist/src/transport/http-transport-config.js.map +1 -0
  208. package/dist/src/{http-transport.d.ts → transport/http-transport.d.ts} +72 -14
  209. package/dist/src/transport/http-transport.d.ts.map +1 -0
  210. package/dist/src/transport/http-transport.js +2522 -0
  211. package/dist/src/transport/http-transport.js.map +1 -0
  212. package/dist/src/{interceptors.d.ts → transport/interceptors.d.ts} +6 -2
  213. package/dist/src/transport/interceptors.d.ts.map +1 -0
  214. package/dist/src/{interceptors.js → transport/interceptors.js} +77 -46
  215. package/dist/src/transport/interceptors.js.map +1 -0
  216. package/dist/src/types/http-transport.d.ts +25 -0
  217. package/dist/src/types/http-transport.d.ts.map +1 -1
  218. package/dist/src/types/profile.d.ts +31 -1
  219. package/dist/src/types/profile.d.ts.map +1 -1
  220. package/dist/src/validation/argument-normalizer.d.ts +6 -0
  221. package/dist/src/validation/argument-normalizer.d.ts.map +1 -0
  222. package/dist/src/validation/argument-normalizer.js +70 -0
  223. package/dist/src/validation/argument-normalizer.js.map +1 -0
  224. package/dist/src/validation/jsonrpc-validator.d.ts.map +1 -0
  225. package/dist/src/validation/jsonrpc-validator.js.map +1 -0
  226. package/dist/src/{schema-validator.d.ts → validation/schema-validator.d.ts} +2 -2
  227. package/dist/src/validation/schema-validator.d.ts.map +1 -0
  228. package/dist/src/validation/schema-validator.js.map +1 -0
  229. package/dist/src/validation/validation-utils.d.ts.map +1 -0
  230. package/dist/src/validation/validation-utils.js.map +1 -0
  231. package/package.json +9 -3
  232. package/profile-schema.json +75 -3
  233. package/profiles/gitlab/developer-profile-oauth.json +1520 -0
  234. package/profiles/gitlab/developer-profile-oauth.test.json +3432 -0
  235. package/profiles/gitlab/openapi.yaml +6891 -0
  236. package/profiles/n8n/openapi.yaml +2441 -0
  237. package/profiles/n8n/profile-optimized.json +965 -0
  238. package/profiles/n8n/profile-optimized.test.json +1078 -0
  239. package/profiles/n8n/profile.json +1033 -0
  240. package/profiles/n8n/profile.test.json +983 -0
  241. package/profiles/n8n-nodes/openapi.yaml +24 -0
  242. package/profiles/n8n-nodes/profile-nodes.json +44 -0
  243. package/profiles/n8n-nodes/profile-nodes.test.json +91 -0
  244. package/profiles/semgrep/openapi.yaml +4706 -0
  245. package/profiles/semgrep/profile.json +692 -0
  246. package/profiles/semgrep/profile.test.json +471 -0
  247. package/profiles/youtrack/openapi.json +16976 -0
  248. package/profiles/youtrack/profile.json +608 -0
  249. package/profiles/youtrack/profile.test.json +1926 -0
  250. package/dist/src/composite-executor.d.ts.map +0 -1
  251. package/dist/src/composite-executor.js.map +0 -1
  252. package/dist/src/constants.d.ts.map +0 -1
  253. package/dist/src/constants.js.map +0 -1
  254. package/dist/src/dag-executor.d.ts.map +0 -1
  255. package/dist/src/dag-executor.js.map +0 -1
  256. package/dist/src/errors.d.ts.map +0 -1
  257. package/dist/src/errors.js.map +0 -1
  258. package/dist/src/http-client-factory.d.ts.map +0 -1
  259. package/dist/src/http-client-factory.js.map +0 -1
  260. package/dist/src/http-transport.d.ts.map +0 -1
  261. package/dist/src/http-transport.js +0 -1826
  262. package/dist/src/http-transport.js.map +0 -1
  263. package/dist/src/interceptors.d.ts.map +0 -1
  264. package/dist/src/interceptors.js.map +0 -1
  265. package/dist/src/jsonrpc-validator.d.ts.map +0 -1
  266. package/dist/src/jsonrpc-validator.js.map +0 -1
  267. package/dist/src/logger.d.ts.map +0 -1
  268. package/dist/src/logger.js +0 -177
  269. package/dist/src/logger.js.map +0 -1
  270. package/dist/src/mcp-server.d.ts.map +0 -1
  271. package/dist/src/mcp-server.js.map +0 -1
  272. package/dist/src/metrics.d.ts.map +0 -1
  273. package/dist/src/metrics.js.map +0 -1
  274. package/dist/src/naming-warnings.d.ts.map +0 -1
  275. package/dist/src/naming-warnings.js.map +0 -1
  276. package/dist/src/naming.d.ts.map +0 -1
  277. package/dist/src/naming.js.map +0 -1
  278. package/dist/src/oauth-provider.d.ts.map +0 -1
  279. package/dist/src/oauth-provider.js.map +0 -1
  280. package/dist/src/openapi-parser.d.ts.map +0 -1
  281. package/dist/src/openapi-parser.js.map +0 -1
  282. package/dist/src/profile-loader.d.ts.map +0 -1
  283. package/dist/src/profile-loader.js.map +0 -1
  284. package/dist/src/proxy-executor.d.ts.map +0 -1
  285. package/dist/src/proxy-executor.js +0 -240
  286. package/dist/src/proxy-executor.js.map +0 -1
  287. package/dist/src/schema-validator.d.ts.map +0 -1
  288. package/dist/src/schema-validator.js.map +0 -1
  289. package/dist/src/testing/fixtures.d.ts +0 -684
  290. package/dist/src/testing/fixtures.d.ts.map +0 -1
  291. package/dist/src/testing/fixtures.js +0 -528
  292. package/dist/src/testing/fixtures.js.map +0 -1
  293. package/dist/src/testing/mock-gitlab-server.d.ts +0 -43
  294. package/dist/src/testing/mock-gitlab-server.d.ts.map +0 -1
  295. package/dist/src/testing/mock-gitlab-server.js +0 -1026
  296. package/dist/src/testing/mock-gitlab-server.js.map +0 -1
  297. package/dist/src/testing/mock-semgrep-server.d.ts +0 -32
  298. package/dist/src/testing/mock-semgrep-server.d.ts.map +0 -1
  299. package/dist/src/testing/mock-semgrep-server.js +0 -213
  300. package/dist/src/testing/mock-semgrep-server.js.map +0 -1
  301. package/dist/src/testing/mock-youtrack-server.d.ts +0 -11
  302. package/dist/src/testing/mock-youtrack-server.d.ts.map +0 -1
  303. package/dist/src/testing/mock-youtrack-server.js +0 -138
  304. package/dist/src/testing/mock-youtrack-server.js.map +0 -1
  305. package/dist/src/tool-generator.d.ts.map +0 -1
  306. package/dist/src/tool-generator.js.map +0 -1
  307. package/dist/src/validation-utils.d.ts.map +0 -1
  308. package/dist/src/validation-utils.js.map +0 -1
  309. /package/dist/src/{naming-warnings.d.ts → core/naming-warnings.d.ts} +0 -0
  310. /package/dist/src/{naming.d.ts → core/naming.d.ts} +0 -0
  311. /package/dist/src/{naming.js → core/naming.js} +0 -0
  312. /package/dist/src/{dag-executor.js → tooling/dag-executor.js} +0 -0
  313. /package/dist/src/{jsonrpc-validator.d.ts → validation/jsonrpc-validator.d.ts} +0 -0
  314. /package/dist/src/{jsonrpc-validator.js → validation/jsonrpc-validator.js} +0 -0
  315. /package/dist/src/{schema-validator.js → validation/schema-validator.js} +0 -0
  316. /package/dist/src/{validation-utils.d.ts → validation/validation-utils.d.ts} +0 -0
  317. /package/dist/src/{validation-utils.js → validation/validation-utils.js} +0 -0
@@ -7,37 +7,221 @@
7
7
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
8
8
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
9
9
  import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
10
- import { OpenAPIParser } from './openapi-parser.js';
11
- import { ProfileLoader } from './profile-loader.js';
12
- import { ToolGenerator } from './tool-generator.js';
13
- import { CompositeExecutor } from './composite-executor.js';
14
- 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';
17
- import { HttpClientFactory } from './http-client-factory.js';
18
- import { SchemaValidator } from './schema-validator.js';
19
- import { ConsoleLogger, JsonLogger } from './logger.js';
20
- import { isInitializeRequest, isToolCallRequest } from './jsonrpc-validator.js';
21
- import { generateNameWarnings } from './naming-warnings.js';
22
- import { NamingStrategy } from './naming.js';
10
+ import { OpenAPIParser } from '../openapi/openapi-parser.js';
11
+ import { ProfileLoader } from '../profile/profile-loader.js';
12
+ import { ToolGenerator } from '../tooling/tool-generator.js';
13
+ import { applyParameterDefaults, normalizeArguments } from '../validation/argument-normalizer.js';
14
+ import { CompositeExecutor } from '../tooling/composite-executor.js';
15
+ import { ProxyDownloadExecutor } from '../tooling/proxy-executor.js';
16
+ import { enforceFiltering, parseFilteringHeader } from '../core/filtering.js';
17
+ import { ConfigurationError, OperationNotFoundError, ResourceNotFoundError, ValidationError, AuthenticationError, AuthorizationError, RateLimitError, NetworkError, generateCorrelationId } from '../core/errors.js';
18
+ import { OAUTH_RATE_LIMIT } from '../core/constants.js';
19
+ import { HttpClientFactory } from '../transport/http-client-factory.js';
20
+ import { SchemaValidator } from '../validation/schema-validator.js';
21
+ import { ConsoleLogger, JsonLogger } from '../core/logger.js';
22
+ import { isInitializeRequest, isToolCallRequest } from '../validation/jsonrpc-validator.js';
23
+ import { generateNameWarnings } from '../core/naming-warnings.js';
24
+ import { NamingStrategy } from '../core/naming.js';
25
+ import { isSafePropertyName } from '../validation/validation-utils.js';
26
+ import { ToolFilterService, EnvConfigParser, HeaderConfigParser, RegexCompiler, RegexValidator, OperationClassifier, OpenAPIOperationResolver, OperationDetector, applySessionToolFilter, } from '../tool-filter/index.js';
27
+ import { buildHttpTransportBaseConfig } from '../transport/http-transport-config.js';
23
28
  export class MCPServer {
24
29
  /**
25
- * Filter response object to include only specified fields
26
- * Supports nested objects but keeps first level of arrays
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
+ }
45
+ /**
46
+ * Filter response payload to include only specified fields.
47
+ *
48
+ * Supports YouTrack-style field selectors like:
49
+ * - "author(id,login)"
50
+ * - "comments(id,text,author(id,login))"
51
+ *
52
+ * Recurses into nested objects and arrays when subfields are specified.
27
53
  */
28
54
  filterFields(data, fields) {
55
+ const selection = this.parseFieldSelection(fields);
56
+ return this.applyFieldSelection(data, selection);
57
+ }
58
+ parseFieldSelection(fields) {
59
+ const root = Object.create(null);
60
+ for (const field of fields) {
61
+ const trimmed = field.trim();
62
+ if (!trimmed)
63
+ continue;
64
+ this.mergeFieldSelector(root, trimmed);
65
+ }
66
+ return root;
67
+ }
68
+ mergeFieldSelector(target, selector) {
69
+ const parsed = this.parseFieldSelector(selector);
70
+ const baseName = parsed.baseName;
71
+ if (!baseName)
72
+ return;
73
+ if (!isSafePropertyName(baseName))
74
+ return;
75
+ if (!parsed.inner) {
76
+ target[baseName] = true;
77
+ return;
78
+ }
79
+ const inner = parsed.inner;
80
+ const subSelectors = this.splitTopLevel(inner);
81
+ const subTree = Object.create(null);
82
+ for (const sub of subSelectors) {
83
+ this.mergeFieldSelector(subTree, sub);
84
+ }
85
+ const existing = target[baseName];
86
+ if (existing === true)
87
+ return;
88
+ if (!existing) {
89
+ target[baseName] = subTree;
90
+ return;
91
+ }
92
+ this.mergeSelectionTrees(existing, subTree);
93
+ }
94
+ parseFieldSelector(selector) {
95
+ const trimmed = selector.trim();
96
+ if (!trimmed)
97
+ return { baseName: '' };
98
+ if (trimmed.startsWith('"')) {
99
+ const parsedQuoted = this.parseQuotedBase(trimmed);
100
+ if (parsedQuoted) {
101
+ const { baseName, rest } = parsedQuoted;
102
+ const remaining = rest.trim();
103
+ if (!remaining) {
104
+ return { baseName };
105
+ }
106
+ if (remaining.startsWith('(') && remaining.endsWith(')')) {
107
+ const inner = remaining.slice(1, -1).trim();
108
+ return inner ? { baseName, inner } : { baseName };
109
+ }
110
+ return { baseName };
111
+ }
112
+ }
113
+ const openParen = trimmed.indexOf('(');
114
+ if (openParen === -1) {
115
+ return { baseName: trimmed };
116
+ }
117
+ const closeParen = trimmed.lastIndexOf(')');
118
+ if (closeParen === -1 || closeParen <= openParen) {
119
+ return { baseName: trimmed.slice(0, openParen).trim() };
120
+ }
121
+ const baseName = trimmed.slice(0, openParen).trim();
122
+ const inner = trimmed.slice(openParen + 1, closeParen).trim();
123
+ return inner ? { baseName, inner } : { baseName };
124
+ }
125
+ parseQuotedBase(input) {
126
+ let escaped = false;
127
+ let base = '';
128
+ for (let i = 1; i < input.length; i += 1) {
129
+ const ch = input[i];
130
+ if (escaped) {
131
+ base += ch;
132
+ escaped = false;
133
+ continue;
134
+ }
135
+ if (ch === '\\') {
136
+ escaped = true;
137
+ continue;
138
+ }
139
+ if (ch === '"') {
140
+ const rest = input.slice(i + 1);
141
+ return { baseName: base, rest };
142
+ }
143
+ base += ch;
144
+ }
145
+ return undefined;
146
+ }
147
+ mergeSelectionTrees(target, incoming) {
148
+ for (const [key, val] of Object.entries(incoming)) {
149
+ if (!isSafePropertyName(key))
150
+ continue;
151
+ const existing = target[key];
152
+ if (!existing) {
153
+ target[key] = val;
154
+ continue;
155
+ }
156
+ if (existing === true || val === true) {
157
+ target[key] = true;
158
+ continue;
159
+ }
160
+ this.mergeSelectionTrees(existing, val);
161
+ }
162
+ }
163
+ splitTopLevel(input) {
164
+ const result = [];
165
+ let depth = 0;
166
+ let current = '';
167
+ let inQuote = false;
168
+ let escaped = false;
169
+ for (const ch of input) {
170
+ if (escaped) {
171
+ current += ch;
172
+ escaped = false;
173
+ continue;
174
+ }
175
+ if (ch === '\\' && inQuote) {
176
+ current += ch;
177
+ escaped = true;
178
+ continue;
179
+ }
180
+ if (ch === '"') {
181
+ inQuote = !inQuote;
182
+ current += ch;
183
+ continue;
184
+ }
185
+ if (!inQuote) {
186
+ if (ch === '(')
187
+ depth += 1;
188
+ if (ch === ')')
189
+ depth = Math.max(0, depth - 1);
190
+ }
191
+ if (!inQuote && ch === ',' && depth === 0) {
192
+ const trimmed = current.trim();
193
+ if (trimmed)
194
+ result.push(trimmed);
195
+ current = '';
196
+ continue;
197
+ }
198
+ current += ch;
199
+ }
200
+ const last = current.trim();
201
+ if (last)
202
+ result.push(last);
203
+ return result;
204
+ }
205
+ applyFieldSelection(data, selection) {
29
206
  if (!data || typeof data !== 'object') {
30
207
  return data;
31
208
  }
32
209
  if (Array.isArray(data)) {
33
- return data.map(item => this.filterFields(item, fields));
210
+ return data.map(item => this.applyFieldSelection(item, selection));
34
211
  }
35
- const filtered = {};
36
- for (const field of fields) {
37
- // Handle YouTrack-style fields: "author(id,login)" -> "author"
38
- const baseFieldName = field.split('(')[0];
39
- if (baseFieldName in data) {
40
- filtered[baseFieldName] = data[baseFieldName];
212
+ const obj = data;
213
+ const filtered = Object.create(null);
214
+ for (const [key, sel] of Object.entries(selection)) {
215
+ if (!isSafePropertyName(key))
216
+ continue;
217
+ if (!Object.prototype.hasOwnProperty.call(obj, key))
218
+ continue;
219
+ const value = obj[key];
220
+ if (sel === true) {
221
+ filtered[key] = value;
222
+ }
223
+ else {
224
+ filtered[key] = this.applyFieldSelection(value, sel);
41
225
  }
42
226
  }
43
227
  return filtered;
@@ -80,6 +264,9 @@ export class MCPServer {
80
264
  if (error instanceof OperationNotFoundError) {
81
265
  return `Operation not found: ${error.message} (correlation ID: ${correlationId})`;
82
266
  }
267
+ if (error instanceof ResourceNotFoundError) {
268
+ return `${error.message} (correlation ID: ${correlationId})`;
269
+ }
83
270
  // Configuration errors - safe to show (helps admin fix setup)
84
271
  if (error instanceof ConfigurationError) {
85
272
  return `Configuration error: ${error.message} (correlation ID: ${correlationId})`;
@@ -126,6 +313,7 @@ export class MCPServer {
126
313
  // Check if we should warn about long names
127
314
  this.checkToolNameLengths();
128
315
  }
316
+ this.applyGlobalToolFiltering();
129
317
  // Re-create logger with auth config for token redaction
130
318
  const authConfigs = this.getAuthConfigs();
131
319
  if (authConfigs.length > 0) {
@@ -142,8 +330,8 @@ export class MCPServer {
142
330
  const envAuthConfig = this.getEnvBackedAuthConfig();
143
331
  const envVarName = envAuthConfig?.value_from_env;
144
332
  const envToken = envVarName ? process.env[envVarName] : undefined;
145
- if (envAuthConfig && envToken) {
146
- // Token available in env - create global client (stdio transport)
333
+ if ((envAuthConfig && envToken) || authConfigs.length === 0) {
334
+ // Token available in env (stdio) or no auth required - create global client
147
335
  const httpClient = this.httpClientFactory.createGlobalClient({
148
336
  profile: this.profile,
149
337
  baseUrl,
@@ -249,6 +437,59 @@ export class MCPServer {
249
437
  const oauthConfig = configs.find(c => c.type === 'oauth');
250
438
  return oauthConfig?.oauth_config;
251
439
  }
440
+ buildOAuthConfigWithAllowedRedirectHosts(oauthConfig) {
441
+ if (!oauthConfig) {
442
+ return undefined;
443
+ }
444
+ return {
445
+ ...oauthConfig,
446
+ allowed_redirect_hosts: oauthConfig.allowed_redirect_hosts
447
+ || (process.env.MCP4_ALLOWED_ORIGINS
448
+ ? this.extractHostsFromOrigins(process.env.MCP4_ALLOWED_ORIGINS)
449
+ : undefined),
450
+ };
451
+ }
452
+ getProfileIdValue() {
453
+ if (!this.profile) {
454
+ throw new ConfigurationError('Profile not initialized. Call initialize() first.');
455
+ }
456
+ const profileId = this.profile.profile_id?.trim() || this.profile.profile_name;
457
+ if (!profileId) {
458
+ throw new ConfigurationError('Profile is missing profile_id and profile_name.');
459
+ }
460
+ return profileId;
461
+ }
462
+ getOAuthRateLimitConfig() {
463
+ const authConfigs = this.getAuthConfigs();
464
+ const oauthAuthConfig = authConfigs.find(c => c.type === 'oauth');
465
+ const oauthRateLimit = oauthAuthConfig?.oauth_rate_limit;
466
+ const max = oauthRateLimit?.max_requests
467
+ || parseInt(process.env.MCP4_OAUTH_RATE_LIMIT_MAX || String(OAUTH_RATE_LIMIT.MAX_REQUESTS), 10);
468
+ const windowMs = oauthRateLimit?.window_ms
469
+ || parseInt(process.env.MCP4_OAUTH_RATE_LIMIT_WINDOW_MS || String(OAUTH_RATE_LIMIT.WINDOW_MS), 10);
470
+ return { max, windowMs };
471
+ }
472
+ getHttpProfileContext() {
473
+ if (!this.profile) {
474
+ throw new ConfigurationError('Profile not initialized. Call initialize() first.');
475
+ }
476
+ const authConfigs = this.getAuthConfigs();
477
+ const baseUrl = this.getBaseUrl();
478
+ const oauthConfig = this.buildOAuthConfigWithAllowedRedirectHosts(this.getOAuthConfig());
479
+ const resourceMetadata = this.parser.getResourceMetadata();
480
+ const oauthRateLimit = this.getOAuthRateLimitConfig();
481
+ return {
482
+ profileId: this.getProfileIdValue(),
483
+ oauthConfig,
484
+ authConfigs,
485
+ baseUrl,
486
+ rateLimitOAuthMax: oauthRateLimit.max,
487
+ rateLimitOAuthWindowMs: oauthRateLimit.windowMs,
488
+ resourceName: this.profile.resource_name || resourceMetadata.name || 'MCP Server',
489
+ resourceDocumentation: this.profile.resource_documentation || resourceMetadata.documentation,
490
+ parser: this.parser,
491
+ };
492
+ }
252
493
  /**
253
494
  * Extract hostnames from origin patterns for OAuth redirect validation
254
495
  * e.g., "http://localhost:*,https://app.example.com" -> ["localhost", "app.example.com"]
@@ -288,7 +529,7 @@ export class MCPServer {
288
529
  /**
289
530
  * Get or create HTTP client for session
290
531
  */
291
- async getHttpClientForSession(sessionId) {
532
+ async getHttpClientForSession(sessionId, profileId) {
292
533
  if (!sessionId) {
293
534
  // Fallback to global client for stdio transport
294
535
  if (!this.httpClientFactory.hasGlobalClient()) {
@@ -311,7 +552,7 @@ export class MCPServer {
311
552
  throw new ConfigurationError('Profile not initialized. Call initialize() first.');
312
553
  }
313
554
  // Get auth token from session (ensures token is valid/refreshed)
314
- const authToken = await this.getAuthTokenFromSession(sessionId);
555
+ const authToken = await this.getAuthTokenFromSession(sessionId, profileId);
315
556
  // Create or get session client using factory
316
557
  return this.httpClientFactory.getOrCreateSessionClient(sessionId, {
317
558
  profile: this.profile,
@@ -323,7 +564,7 @@ export class MCPServer {
323
564
  * Get auth token from HTTP transport session
324
565
  * Ensures token is valid (refreshes if expired) before returning
325
566
  */
326
- async getAuthTokenFromSession(sessionId) {
567
+ async getAuthTokenFromSession(sessionId, profileId) {
327
568
  // Early return if sessionId is missing/empty
328
569
  // Prevents misleading warn logs with empty sessionId
329
570
  if (!sessionId) {
@@ -333,23 +574,24 @@ export class MCPServer {
333
574
  return undefined;
334
575
  }
335
576
  // Ensure token is valid (refresh if expired)
336
- const isValid = await this.httpTransport.ensureValidSessionToken(sessionId);
577
+ const effectiveProfileId = profileId || this.getProfileIdValue();
578
+ const isValid = await this.httpTransport.ensureValidSessionToken(effectiveProfileId, sessionId);
337
579
  if (!isValid) {
338
- this.logger.warn('Session token validation/refresh failed', { sessionId });
580
+ this.logger.warn('Session token validation/refresh failed', { profileId: effectiveProfileId, sessionId });
339
581
  // Still return token if available - let the API call fail with proper error
340
582
  }
341
583
  // Use public API instead of type casting
342
- return this.httpTransport.getSessionToken(sessionId);
584
+ return this.httpTransport.getSessionToken(effectiveProfileId, sessionId);
343
585
  }
344
586
  /**
345
587
  * Cleanup HTTP client for destroyed session
346
588
  *
347
589
  * Why: Prevent memory leak - sessions expire but cached clients stay forever
348
590
  */
349
- cleanupSessionClient(sessionId) {
591
+ cleanupSessionClient(profileId, sessionId) {
350
592
  const removed = this.httpClientFactory.cleanupSessionClient(sessionId);
351
593
  if (removed) {
352
- this.logger.info('Cleaned up session HTTP client', { sessionId });
594
+ this.logger.info('Cleaned up session HTTP client', { profileId, sessionId });
353
595
  }
354
596
  }
355
597
  /**
@@ -383,7 +625,8 @@ export class MCPServer {
383
625
  if (!toolDef) {
384
626
  throw new OperationNotFoundError(request.params.name);
385
627
  }
386
- const args = request.params.arguments || {};
628
+ const rawArgs = request.params.arguments || {};
629
+ const args = applyParameterDefaults(toolDef, rawArgs);
387
630
  // Validate arguments
388
631
  this.toolGenerator.validateArguments(toolDef, args);
389
632
  // Execute composite or simple tool
@@ -433,15 +676,16 @@ export class MCPServer {
433
676
  * Why separate: Simple tools map directly to single OpenAPI operation.
434
677
  * No result aggregation needed.
435
678
  */
436
- async executeSimpleTool(toolDef, args, sessionId) {
679
+ async executeSimpleTool(toolDef, args, sessionId, profileId) {
680
+ const normalizedArgs = normalizeArguments(toolDef, args);
437
681
  this.logger.debug('Executing simple tool', {
438
682
  toolName: toolDef.name,
439
- action: args['action'],
440
- resourceType: args['resource_type'],
683
+ action: normalizedArgs['action'],
684
+ resourceType: normalizedArgs['resource_type'],
441
685
  sessionId
442
686
  });
443
687
  // Get operation definition (can be string or ProxyDownloadOperation)
444
- const operationDef = this.toolGenerator.getOperationDefinition(toolDef, args);
688
+ const operationDef = this.toolGenerator.getOperationDefinition(toolDef, normalizedArgs);
445
689
  if (!operationDef) {
446
690
  throw new ValidationError(`Could not map tool action to operation`, {
447
691
  toolName: toolDef.name,
@@ -452,7 +696,7 @@ export class MCPServer {
452
696
  }
453
697
  // Check if this is a proxy download operation
454
698
  if (typeof operationDef === 'object' && operationDef.type === 'proxy_download') {
455
- return this.executeProxyDownload(operationDef, args, sessionId);
699
+ return this.executeProxyDownload(operationDef, normalizedArgs, sessionId, profileId);
456
700
  }
457
701
  // Regular string operation
458
702
  const operationId = operationDef;
@@ -461,9 +705,9 @@ export class MCPServer {
461
705
  throw new OperationNotFoundError(operationId);
462
706
  }
463
707
  // Build request
464
- const path = this.resolvePath(operation.path, args);
465
- const queryParams = this.extractQueryParams(operation, args);
466
- const body = this.extractBody(operation, args, toolDef);
708
+ const path = this.resolvePath(operation.path, normalizedArgs);
709
+ const queryParams = this.extractQueryParams(operation, normalizedArgs);
710
+ const body = this.extractBody(operation, normalizedArgs, toolDef);
467
711
  this.logger.debug('Executing HTTP request', {
468
712
  operationId,
469
713
  method: operation.method,
@@ -482,9 +726,9 @@ export class MCPServer {
482
726
  }
483
727
  }
484
728
  // Execute with session-specific client
485
- const httpClient = await this.getHttpClientForSession(sessionId);
729
+ const httpClient = await this.getHttpClientForSession(sessionId, profileId);
486
730
  // Set fields parameter if response_fields are configured for this action AND enabled
487
- const action = args.action;
731
+ const action = normalizedArgs.action;
488
732
  if (toolDef.send_response_fields_as_param && toolDef.response_fields && action && toolDef.response_fields[action]) {
489
733
  const fields = toolDef.response_fields[action];
490
734
  queryParams.fields = fields.join(',');
@@ -497,7 +741,7 @@ export class MCPServer {
497
741
  // Apply response field filtering if configured
498
742
  let result = response.body;
499
743
  if (toolDef.response_fields) {
500
- const action = args.action;
744
+ const action = normalizedArgs.action;
501
745
  if (action && toolDef.response_fields[action]) {
502
746
  const fields = toolDef.response_fields[action];
503
747
  result = this.filterFields(result, fields);
@@ -511,7 +755,7 @@ export class MCPServer {
511
755
  * Why: Some APIs return authenticated URLs that LLMs cannot fetch directly.
512
756
  * This proxies the download through the MCP server.
513
757
  */
514
- async executeProxyDownload(operation, args, sessionId) {
758
+ async executeProxyDownload(operation, args, sessionId, profileId) {
515
759
  this.logger.debug('Executing proxy download', {
516
760
  metadataEndpoint: operation.metadata_endpoint,
517
761
  urlField: operation.url_field,
@@ -537,10 +781,10 @@ export class MCPServer {
537
781
  };
538
782
  }
539
783
  // Get auth credentials for download
540
- const httpClient = await this.getHttpClientForSession(sessionId);
784
+ const httpClient = await this.getHttpClientForSession(sessionId, profileId);
541
785
  const authCredentials = httpClient.getAuthCredentials();
542
786
  // Execute proxy download
543
- const proxyExecutor = new ProxyDownloadExecutor(httpClient);
787
+ const proxyExecutor = new ProxyDownloadExecutor(httpClient, this.logger);
544
788
  const result = await proxyExecutor.execute(operation, { path: metadataPath, method: metadataMethod }, authCredentials, directDownloadRequest);
545
789
  this.logger.debug('Proxy download completed', {
546
790
  fileName: result.fileName,
@@ -638,17 +882,47 @@ export class MCPServer {
638
882
  pathOrQuery.add(param.name);
639
883
  }
640
884
  }
641
- // Get body schema properties to check if path/query params should also be in body
885
+ // Get body schema and properties to check if path/query params should also be in body
886
+ let bodySchema;
642
887
  const bodySchemaProps = new Set();
643
888
  if (operation.requestBody?.content) {
644
- // Check all content types (typically application/json)
645
- for (const mediaType of Object.values(operation.requestBody.content)) {
646
- if (mediaType.schema?.properties) {
647
- for (const propName of Object.keys(mediaType.schema.properties)) {
648
- bodySchemaProps.add(propName);
889
+ // Prefer application/json but accept any schema present
890
+ const jsonSchema = operation.requestBody.content['application/json']?.schema;
891
+ bodySchema = jsonSchema;
892
+ if (!bodySchema) {
893
+ for (const mediaType of Object.values(operation.requestBody.content)) {
894
+ if (mediaType.schema) {
895
+ bodySchema = mediaType.schema;
896
+ break;
649
897
  }
650
898
  }
651
899
  }
900
+ if (bodySchema?.type === 'object' && bodySchema.properties) {
901
+ for (const propName of Object.keys(bodySchema.properties)) {
902
+ bodySchemaProps.add(propName);
903
+ }
904
+ }
905
+ }
906
+ // Root array body support
907
+ if (bodySchema?.type === 'array') {
908
+ const explicit = args['body'] ?? args['items'];
909
+ if (explicit !== undefined) {
910
+ return explicit;
911
+ }
912
+ const arrayCandidates = [];
913
+ for (const [key, value] of Object.entries(args)) {
914
+ if (metadata.has(key))
915
+ continue;
916
+ if (pathOrQuery.has(key))
917
+ continue;
918
+ if (Array.isArray(value)) {
919
+ arrayCandidates.push(value);
920
+ }
921
+ }
922
+ if (arrayCandidates.length === 1) {
923
+ return arrayCandidates[0];
924
+ }
925
+ return undefined;
652
926
  }
653
927
  const body = {};
654
928
  let hasBody = false;
@@ -683,58 +957,26 @@ export class MCPServer {
683
957
  * and resumability for reliable communication over HTTP.
684
958
  */
685
959
  async runHttp(host, port) {
686
- const { HttpTransport } = await import('./http-transport.js');
687
- // Get OAuth config from profile (supports multi-auth)
688
- const oauthConfig = this.getOAuthConfig();
689
- if (oauthConfig) {
960
+ const { HttpTransport } = await import('../transport/http-transport.js');
961
+ const profileContext = this.getHttpProfileContext();
962
+ if (profileContext.oauthConfig) {
690
963
  this.logger.info('OAuth authentication enabled for HTTP transport');
691
964
  }
692
- // Get auth configs for token validation
693
- const authConfigs = this.getAuthConfigs();
694
- const baseUrl = this.getBaseUrl();
695
- // Extract OAuth rate limit from profile (if configured)
696
- const oauthAuthConfig = authConfigs.find(c => c.type === 'oauth');
697
- const oauthRateLimit = oauthAuthConfig?.oauth_rate_limit;
698
- // Extract resource metadata from OpenAPI spec or profile
699
- const resourceMetadata = this.parser.getResourceMetadata();
965
+ const baseConfig = buildHttpTransportBaseConfig(host, port);
700
966
  const config = {
701
- host,
702
- port,
703
- sessionTimeoutMs: parseInt(process.env.MCP4_SESSION_TIMEOUT_MS || String(TIMEOUTS.SESSION_TIMEOUT_MS), 10),
704
- heartbeatEnabled: process.env.MCP4_HEARTBEAT_ENABLED === 'true',
705
- heartbeatIntervalMs: parseInt(process.env.MCP4_HEARTBEAT_INTERVAL_MS || String(TIMEOUTS.HEARTBEAT_INTERVAL_MS), 10),
706
- metricsEnabled: process.env.MCP4_METRICS_ENABLED === 'true',
707
- metricsPath: process.env.MCP4_METRICS_PATH || '/metrics',
708
- allowedOrigins: process.env.MCP4_ALLOWED_ORIGINS
709
- ? process.env.MCP4_ALLOWED_ORIGINS.split(',').map(o => o.trim())
710
- : undefined,
711
- rateLimitEnabled: process.env.MCP4_HTTP_RATE_LIMIT_ENABLED !== 'false', // default: true
712
- rateLimitWindowMs: parseInt(process.env.MCP4_HTTP_RATE_LIMIT_WINDOW_MS || String(TIMEOUTS.RATE_LIMIT_WINDOW_MS), 10),
713
- rateLimitMaxRequests: parseInt(process.env.MCP4_HTTP_RATE_LIMIT_MAX_REQUESTS || '100', 10),
714
- rateLimitMetricsMax: parseInt(process.env.MCP4_HTTP_RATE_LIMIT_METRICS_MAX || '10', 10),
967
+ ...baseConfig,
968
+ profileRoutingEnabled: false,
969
+ defaultProfileId: profileContext.profileId,
715
970
  // OAuth rate limiting (priority: profile > env vars > defaults)
716
- rateLimitOAuthMax: oauthRateLimit?.max_requests
717
- || parseInt(process.env.MCP4_OAUTH_RATE_LIMIT_MAX || String(OAUTH_RATE_LIMIT.MAX_REQUESTS), 10),
718
- rateLimitOAuthWindowMs: oauthRateLimit?.window_ms
719
- || parseInt(process.env.MCP4_OAUTH_RATE_LIMIT_WINDOW_MS || String(OAUTH_RATE_LIMIT.WINDOW_MS), 10),
720
- maxTokenLength: process.env.MCP4_TOKEN_MAX_LENGTH
721
- ? parseInt(process.env.MCP4_TOKEN_MAX_LENGTH, 10)
722
- : undefined, // Uses default from http-transport.ts if undefined
723
- // Pass OAuth config with allowed_redirect_hosts derived from MCP4_ALLOWED_ORIGINS
724
- oauthConfig: oauthConfig ? {
725
- ...oauthConfig,
726
- allowed_redirect_hosts: oauthConfig.allowed_redirect_hosts
727
- || (process.env.MCP4_ALLOWED_ORIGINS
728
- ? this.extractHostsFromOrigins(process.env.MCP4_ALLOWED_ORIGINS)
729
- : undefined),
730
- } : undefined,
731
- baseUrl, // Pass base URL for token validation
732
- authConfigs, // Pass auth configs for token validation
733
- // OAuth resource metadata (priority: profile > OpenAPI > fallback)
734
- resourceName: this.profile?.resource_name || resourceMetadata.name || 'MCP Server',
735
- resourceDocumentation: this.profile?.resource_documentation || resourceMetadata.documentation,
736
- sslCertFile: process.env.MCP4_SSL_CERT_FILE,
737
- sslKeyFile: process.env.MCP4_SSL_KEY_FILE,
971
+ rateLimitOAuthMax: profileContext.rateLimitOAuthMax,
972
+ rateLimitOAuthWindowMs: profileContext.rateLimitOAuthWindowMs,
973
+ // OAuth config already merged with allowed_redirect_hosts
974
+ oauthConfig: profileContext.oauthConfig,
975
+ baseUrl: profileContext.baseUrl,
976
+ authConfigs: profileContext.authConfigs,
977
+ resourceName: profileContext.resourceName,
978
+ resourceDocumentation: profileContext.resourceDocumentation,
979
+ parser: profileContext.parser,
738
980
  };
739
981
  // Warn if binding to non-localhost without explicit MCP4_ALLOWED_ORIGINS
740
982
  const isLocalhost = host === 'localhost' || host === '127.0.0.1' || host === '::1';
@@ -743,37 +985,62 @@ export class MCPServer {
743
985
  this.logger.warn('Binding to non-localhost with empty MCP4_ALLOWED_ORIGINS. Set MCP4_ALLOWED_ORIGINS or bind to localhost.');
744
986
  }
745
987
  this.httpTransport = new HttpTransport(config, this.logger);
988
+ const metricsCollector = this.httpTransport.getMetricsCollector?.() || null;
989
+ this.httpClientFactory.setMetricsCollector(metricsCollector);
990
+ this.recordGlobalToolFilterMetrics();
746
991
  // Set message handler to process JSON-RPC messages
747
- this.httpTransport.setMessageHandler(async (message, sessionId) => {
748
- return await this.handleJsonRpcMessage(message, sessionId);
992
+ this.httpTransport.setMessageHandler(async (message, sessionId, profileId) => {
993
+ return await this.handleJsonRpcMessage(message, sessionId, profileId);
749
994
  });
750
995
  // Register cleanup listener for session destruction (memory leak prevention)
751
- this.httpTransport.onSessionDestroyed((sessionId) => {
752
- this.cleanupSessionClient(sessionId);
996
+ this.httpTransport.onSessionDestroyed((profileId, sessionId) => {
997
+ this.cleanupSessionClient(profileId, sessionId);
753
998
  });
754
999
  await this.httpTransport.start();
755
1000
  this.logger.info('MCP server running on HTTP', { host, port });
756
1001
  }
1002
+ attachHttpTransport(transport) {
1003
+ this.httpTransport = transport;
1004
+ const metricsCollector = this.httpTransport.getMetricsCollector?.() || null;
1005
+ this.httpClientFactory.setMetricsCollector(metricsCollector);
1006
+ }
1007
+ handleSessionDestroyed(profileId, sessionId) {
1008
+ this.cleanupSessionClient(profileId, sessionId);
1009
+ }
757
1010
  /**
758
1011
  * Handle JSON-RPC message from HTTP transport
759
1012
  *
760
1013
  * Why: Unified message handling for both stdio and HTTP transports
761
1014
  */
762
- async handleJsonRpcMessage(message, sessionId) {
1015
+ async handleJsonRpcMessage(message, sessionId, profileId) {
763
1016
  // Handle initialize
764
1017
  if (isInitializeRequest(message)) {
765
- return this.handleInitialize(message, sessionId);
1018
+ return this.handleInitialize(message, sessionId, profileId);
766
1019
  }
767
1020
  // Handle tool calls
768
1021
  if (isToolCallRequest(message)) {
769
- return await this.handleToolCall(message, sessionId);
1022
+ return await this.handleToolCall(message, sessionId, profileId);
770
1023
  }
771
1024
  // Handle other JSON-RPC requests
772
1025
  // (tools/list, prompts/list, etc.)
773
- return await this.handleOtherRequest(message, sessionId);
1026
+ return await this.handleOtherRequest(message, sessionId, profileId);
1027
+ }
1028
+ async handleHttpMessage(message, sessionId, profileId) {
1029
+ return this.handleJsonRpcMessage(message, sessionId, profileId);
774
1030
  }
775
- handleInitialize(message, sessionId) {
1031
+ handleInitialize(message, sessionId, profileId) {
776
1032
  const req = message;
1033
+ const params = req.params;
1034
+ if (!this.httpTransport && params?.filtering !== undefined) {
1035
+ if (typeof params.filtering !== 'string') {
1036
+ throw new ValidationError('Invalid X-Mcp4-Params header. Expected comma-separated key=value pairs.');
1037
+ }
1038
+ const parsed = parseFilteringHeader(params.filtering);
1039
+ this.stdioFiltering = parsed.filtering;
1040
+ }
1041
+ if (this.httpTransport && sessionId) {
1042
+ this.applySessionToolFiltering(sessionId, profileId);
1043
+ }
777
1044
  const result = {
778
1045
  protocolVersion: '2025-03-26',
779
1046
  serverInfo: {
@@ -796,14 +1063,14 @@ export class MCPServer {
796
1063
  result,
797
1064
  };
798
1065
  }
799
- async handleToolCall(message, sessionId) {
1066
+ async handleToolCall(message, sessionId, profileId) {
800
1067
  const req = message;
801
1068
  const params = req.params;
802
1069
  const toolName = params.name;
803
- const args = params.arguments;
1070
+ const rawArgs = params.arguments || {};
804
1071
  // Check OAuth authentication for tool operations
805
- if (this.httpTransport && this.httpTransport.hasOAuthProvider()) {
806
- const authToken = await this.getAuthTokenFromSession(sessionId || '');
1072
+ if (this.httpTransport && this.httpTransport.hasOAuthProvider(profileId)) {
1073
+ const authToken = await this.getAuthTokenFromSession(sessionId || '', profileId);
807
1074
  if (!authToken) {
808
1075
  // Return OAuth required error with WWW-Authenticate header
809
1076
  // This should trigger the OAuth flow in the client
@@ -815,7 +1082,7 @@ export class MCPServer {
815
1082
  message: 'Authentication required. Please authorize via OAuth.',
816
1083
  data: {
817
1084
  oauth_required: true,
818
- resource_metadata: `${this.httpTransport.getServerUrl()}/.well-known/oauth-protected-resource/mcp`,
1085
+ resource_metadata: this.httpTransport.getOAuthProtectedResourceUrl(profileId),
819
1086
  scope: 'api'
820
1087
  }
821
1088
  }
@@ -823,16 +1090,36 @@ export class MCPServer {
823
1090
  return errorResponse;
824
1091
  }
825
1092
  }
1093
+ let args = rawArgs;
826
1094
  try {
827
1095
  // Find tool definition
828
1096
  const toolDef = this.profile?.tools.find(t => t.name === toolName);
829
1097
  if (!toolDef) {
830
- throw new OperationNotFoundError(toolName);
1098
+ throw new ResourceNotFoundError(toolName, 'Tool');
1099
+ }
1100
+ args = applyParameterDefaults(toolDef, rawArgs);
1101
+ const toolFilter = this.getToolFilterForSession(sessionId, profileId);
1102
+ if (toolFilter && !toolFilter.allowedToolNames.has(toolName)) {
1103
+ this.recordToolFilterRejection(toolName, 'session');
1104
+ const reason = toolFilter.reasons.get(toolName)?.[0];
1105
+ const reasonSuffix = reason ? ` Blocked by: ${reason}.` : '';
1106
+ throw new AuthorizationError(`Tool '${toolName}' not allowed by X-Mcp4-Tools filter.${reasonSuffix}`);
1107
+ }
1108
+ const filtering = this.getFilteringForSession(sessionId, profileId);
1109
+ if (filtering) {
1110
+ const operation = this.getFilteringOperationInfo(toolDef, args);
1111
+ enforceFiltering({
1112
+ filtering,
1113
+ toolDef,
1114
+ args,
1115
+ parameterAliases: this.profile?.parameter_aliases,
1116
+ operation,
1117
+ });
831
1118
  }
832
1119
  // Execute tool (reuse existing execution logic)
833
1120
  let result;
834
1121
  if (toolDef.composite && toolDef.steps) {
835
- const httpClient = await this.getHttpClientForSession(sessionId);
1122
+ const httpClient = await this.getHttpClientForSession(sessionId, profileId);
836
1123
  const compositeResult = await this.compositeExecutor.execute(toolDef.steps, args, toolDef.partial_results || false, httpClient);
837
1124
  result = {
838
1125
  data: compositeResult.data,
@@ -843,7 +1130,7 @@ export class MCPServer {
843
1130
  };
844
1131
  }
845
1132
  else {
846
- result = await this.executeSimpleTool(toolDef, args, sessionId);
1133
+ result = await this.executeSimpleTool(toolDef, args, sessionId, profileId);
847
1134
  }
848
1135
  return {
849
1136
  jsonrpc: '2.0',
@@ -888,6 +1175,9 @@ export class MCPServer {
888
1175
  else if (error instanceof OperationNotFoundError) {
889
1176
  errorCode = -32601; // Method not found
890
1177
  }
1178
+ else if (error instanceof ResourceNotFoundError) {
1179
+ errorCode = -32601; // Method not found
1180
+ }
891
1181
  return {
892
1182
  jsonrpc: '2.0',
893
1183
  id: req.id,
@@ -898,11 +1188,35 @@ export class MCPServer {
898
1188
  };
899
1189
  }
900
1190
  }
901
- async handleOtherRequest(message, sessionId) {
1191
+ getFilteringForSession(sessionId, profileId) {
1192
+ if (this.httpTransport && sessionId) {
1193
+ const effectiveProfileId = profileId || this.getProfileIdValue();
1194
+ return this.httpTransport.getSessionFiltering(effectiveProfileId, sessionId);
1195
+ }
1196
+ return this.stdioFiltering;
1197
+ }
1198
+ getToolFilterForSession(sessionId, profileId) {
1199
+ if (this.httpTransport && sessionId && typeof this.httpTransport.getSessionToolFilter === 'function') {
1200
+ const effectiveProfileId = profileId || this.getProfileIdValue();
1201
+ return this.httpTransport.getSessionToolFilter(effectiveProfileId, sessionId);
1202
+ }
1203
+ return undefined;
1204
+ }
1205
+ getFilteringOperationInfo(toolDef, args) {
1206
+ if (toolDef.composite) {
1207
+ return undefined;
1208
+ }
1209
+ const operationId = this.toolGenerator.mapActionToOperation(toolDef, args);
1210
+ if (!operationId) {
1211
+ return undefined;
1212
+ }
1213
+ return this.parser.getOperation(operationId);
1214
+ }
1215
+ async handleOtherRequest(message, sessionId, profileId) {
902
1216
  const req = message;
903
1217
  // Check OAuth authentication for other operations (like tools/list)
904
- if (this.httpTransport && this.httpTransport.hasOAuthProvider()) {
905
- const authToken = await this.getAuthTokenFromSession(sessionId || '');
1218
+ if (this.httpTransport && this.httpTransport.hasOAuthProvider(profileId)) {
1219
+ const authToken = await this.getAuthTokenFromSession(sessionId || '', profileId);
906
1220
  if (!authToken) {
907
1221
  // Return OAuth required error with WWW-Authenticate header
908
1222
  // This should trigger the OAuth flow in the client
@@ -914,7 +1228,7 @@ export class MCPServer {
914
1228
  message: 'Authentication required. Please authorize via OAuth.',
915
1229
  data: {
916
1230
  oauth_required: true,
917
- resource_metadata: `${this.httpTransport.getServerUrl()}/.well-known/oauth-protected-resource/mcp`,
1231
+ resource_metadata: this.httpTransport.getOAuthProtectedResourceUrl(profileId),
918
1232
  scope: 'api'
919
1233
  }
920
1234
  }
@@ -924,7 +1238,11 @@ export class MCPServer {
924
1238
  }
925
1239
  // Handle tools/list
926
1240
  if (req.method === 'tools/list') {
927
- const tools = this.profile?.tools.map(toolDef => this.toolGenerator.generateTool(toolDef)) || [];
1241
+ const sessionFilter = this.getToolFilterForSession(sessionId, profileId);
1242
+ const allowedSet = sessionFilter?.allowedToolNames;
1243
+ const tools = this.profile?.tools
1244
+ .filter(toolDef => !allowedSet || allowedSet.has(toolDef.name))
1245
+ .map(toolDef => this.toolGenerator.generateTool(toolDef)) || [];
928
1246
  return {
929
1247
  jsonrpc: '2.0',
930
1248
  id: req.id,
@@ -943,6 +1261,204 @@ export class MCPServer {
943
1261
  },
944
1262
  };
945
1263
  }
1264
+ applyGlobalToolFiltering() {
1265
+ if (!this.profile) {
1266
+ return;
1267
+ }
1268
+ // Initialize ToolFilterService if not already done
1269
+ if (!this.toolFilterService) {
1270
+ const validator = new RegexValidator();
1271
+ const compiler = new RegexCompiler(validator);
1272
+ const envParser = new EnvConfigParser(compiler);
1273
+ const headerParser = new HeaderConfigParser(compiler);
1274
+ // Create OperationDetector for category filtering
1275
+ const classifier = new OperationClassifier();
1276
+ const resolver = new OpenAPIOperationResolver(this.parser);
1277
+ const detector = new OperationDetector(classifier, resolver);
1278
+ this.toolFilterService = new ToolFilterService(envParser, headerParser, this.logger, detector);
1279
+ }
1280
+ const originalTools = this.profile.tools;
1281
+ const originalCount = originalTools.length;
1282
+ // Apply filtering using new service
1283
+ const filteredTools = this.toolFilterService.applyGlobalFilter(originalTools, process.env);
1284
+ const allowedCount = filteredTools.length;
1285
+ const removedCount = originalCount - allowedCount;
1286
+ // Early return if no filtering config present (service returned same tools)
1287
+ if (filteredTools === originalTools) {
1288
+ return;
1289
+ }
1290
+ // Validation: check if filter has no effect
1291
+ if (originalCount > 0 && allowedCount === originalCount && removedCount === 0) {
1292
+ throw new ConfigurationError(`Tool filter configuration has no effect. Original tool count: ${originalCount}, filtered: ${allowedCount}. Check MCP4_TOOL_FILTER_* patterns.`);
1293
+ }
1294
+ // Validation: check if all tools filtered
1295
+ if (originalCount > 0 && allowedCount === 0) {
1296
+ throw new ConfigurationError(`All tools filtered out (original: ${originalCount}). Check MCP4_TOOL_FILTER_* settings.`);
1297
+ }
1298
+ // Validate composite tools against filtered operations
1299
+ const resolver = this.buildToolFilterResolver();
1300
+ this.validateCompositeToolsAgainstFilteredOperations(originalTools, filteredTools, resolver);
1301
+ // Update profile
1302
+ this.profile.tools = filteredTools;
1303
+ // Record summary for metrics
1304
+ this.globalToolFilterSummary = {
1305
+ originalCount,
1306
+ allowedCount,
1307
+ removedCount,
1308
+ patternCounts: {
1309
+ // Note: counts not available from new service, using simplified version
1310
+ filtered: removedCount
1311
+ }
1312
+ };
1313
+ // Warn if high percentage filtered
1314
+ const warnThreshold = this.getToolFilterWarnThresholdPct();
1315
+ if (originalCount > 0) {
1316
+ const percentFiltered = (removedCount / originalCount) * 100;
1317
+ if (percentFiltered >= warnThreshold) {
1318
+ this.logger.warn('Tool filter removed high percentage of tools', {
1319
+ original: originalCount,
1320
+ surviving: allowedCount,
1321
+ threshold_pct: warnThreshold,
1322
+ removed_count: removedCount
1323
+ });
1324
+ }
1325
+ }
1326
+ if (this.httpTransport) {
1327
+ this.recordGlobalToolFilterMetrics();
1328
+ }
1329
+ }
1330
+ applySessionToolFiltering(sessionId, profileId) {
1331
+ if (!this.httpTransport || !this.profile) {
1332
+ return;
1333
+ }
1334
+ if (typeof this.httpTransport.getSessionToolFilterRequest !== 'function') {
1335
+ return;
1336
+ }
1337
+ const effectiveProfileId = profileId || this.getProfileIdValue();
1338
+ const request = this.httpTransport.getSessionToolFilterRequest(effectiveProfileId, sessionId);
1339
+ if (!request) {
1340
+ return;
1341
+ }
1342
+ const originalCount = this.profile.tools.length;
1343
+ const resolver = this.buildToolFilterResolver();
1344
+ const sessionFilter = applySessionToolFilter(this.profile.tools, request, resolver);
1345
+ const allowedCount = sessionFilter.allowedToolNames.size;
1346
+ if (allowedCount === originalCount) {
1347
+ throw new ValidationError(`X-Mcp4-Tools filter has no effect for this session. Available tools: ${originalCount}, after filter: ${allowedCount}. Check patterns.`);
1348
+ }
1349
+ if (originalCount > 0 && allowedCount === 0) {
1350
+ const sources = request.rawEntries.length > 0 ? request.rawEntries.join(', ') : 'none';
1351
+ throw new ValidationError(`X-Mcp4-Tools filtered out all tools (original: ${originalCount}). Removed by: ${sources}. Check session filter configuration.`);
1352
+ }
1353
+ this.httpTransport.setSessionToolFilter(effectiveProfileId, sessionId, sessionFilter);
1354
+ this.logger.info('Session tool filter applied', {
1355
+ sessionId,
1356
+ originalCount,
1357
+ allowedCount,
1358
+ patterns: request.rawEntries,
1359
+ });
1360
+ this.recordSessionToolFilterMetrics(sessionId, allowedCount, request);
1361
+ }
1362
+ buildToolFilterResolver() {
1363
+ return {
1364
+ getOperationById: (operationId) => this.parser.getOperation(operationId),
1365
+ getOperationForCall: (call) => {
1366
+ const [method, path] = call.split(' ');
1367
+ if (!method || !path) {
1368
+ return undefined;
1369
+ }
1370
+ const pathInfo = this.parser.getPath(path);
1371
+ return pathInfo?.operations[method.toLowerCase()];
1372
+ },
1373
+ };
1374
+ }
1375
+ validateCompositeToolsAgainstFilteredOperations(originalTools, allowedTools, resolver) {
1376
+ const operationToTools = new Map();
1377
+ for (const tool of originalTools) {
1378
+ if (!tool.operations) {
1379
+ continue;
1380
+ }
1381
+ for (const operationId of Object.values(tool.operations)) {
1382
+ if (typeof operationId !== 'string') {
1383
+ continue;
1384
+ }
1385
+ const names = operationToTools.get(operationId) ?? [];
1386
+ names.push(tool.name);
1387
+ operationToTools.set(operationId, names);
1388
+ }
1389
+ }
1390
+ const allowedOperationIds = new Set();
1391
+ for (const tool of allowedTools) {
1392
+ if (!tool.operations) {
1393
+ continue;
1394
+ }
1395
+ for (const operationId of Object.values(tool.operations)) {
1396
+ if (typeof operationId !== 'string') {
1397
+ continue;
1398
+ }
1399
+ allowedOperationIds.add(operationId);
1400
+ }
1401
+ }
1402
+ for (const tool of allowedTools) {
1403
+ if (!tool.composite || !tool.steps) {
1404
+ continue;
1405
+ }
1406
+ for (const step of tool.steps) {
1407
+ const operation = resolver.getOperationForCall(step.call);
1408
+ if (!operation) {
1409
+ continue;
1410
+ }
1411
+ if (allowedOperationIds.has(operation.operationId)) {
1412
+ continue;
1413
+ }
1414
+ const removedTools = operationToTools.get(operation.operationId);
1415
+ if (!removedTools || removedTools.length === 0) {
1416
+ continue;
1417
+ }
1418
+ const removedList = removedTools.join(', ');
1419
+ throw new ConfigurationError(`Composite tool '${tool.name}' step '${step.call}' calls filtered tool '${removedList}'. ` +
1420
+ `Add '${removedList}' to filter or include _allow_list or _allow_read if it is a list or read operation.`);
1421
+ }
1422
+ }
1423
+ }
1424
+ getToolFilterWarnThresholdPct() {
1425
+ const raw = process.env.MCP4_TOOL_FILTER_WARN_THRESHOLD_PCT;
1426
+ if (raw === undefined) {
1427
+ return 90;
1428
+ }
1429
+ const parsed = Number(raw);
1430
+ if (Number.isNaN(parsed) || parsed <= 0) {
1431
+ throw new ConfigurationError(`Invalid MCP4_TOOL_FILTER_WARN_THRESHOLD_PCT: expected positive number, got '${raw}'.`);
1432
+ }
1433
+ return parsed;
1434
+ }
1435
+ recordGlobalToolFilterMetrics() {
1436
+ if (!this.httpTransport || !this.globalToolFilterSummary) {
1437
+ return;
1438
+ }
1439
+ if (typeof this.httpTransport.recordGlobalToolFilterMetrics !== 'function') {
1440
+ return;
1441
+ }
1442
+ this.httpTransport.recordGlobalToolFilterMetrics(this.globalToolFilterSummary);
1443
+ }
1444
+ recordSessionToolFilterMetrics(sessionId, allowedCount, request) {
1445
+ if (!this.httpTransport) {
1446
+ return;
1447
+ }
1448
+ if (typeof this.httpTransport.recordSessionToolFilterMetrics !== 'function') {
1449
+ return;
1450
+ }
1451
+ this.httpTransport.recordSessionToolFilterMetrics(sessionId, allowedCount, request);
1452
+ }
1453
+ recordToolFilterRejection(toolName, source) {
1454
+ if (!this.httpTransport) {
1455
+ return;
1456
+ }
1457
+ if (typeof this.httpTransport.recordToolFilterRejection !== 'function') {
1458
+ return;
1459
+ }
1460
+ this.httpTransport.recordToolFilterRejection(toolName, source);
1461
+ }
946
1462
  /**
947
1463
  * Stop the MCP server gracefully
948
1464
  *