mcp4openapi 0.2.8 → 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 (310) 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/{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 -1
  33. package/dist/src/core/logger.d.ts.map +1 -0
  34. package/dist/src/{logger.js → core/logger.js} +30 -2
  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/core/naming-warnings.js.map +1 -0
  42. package/dist/src/core/naming.d.ts.map +1 -0
  43. package/dist/src/core/naming.js.map +1 -0
  44. package/dist/src/generated-schemas.d.ts +245 -79
  45. package/dist/src/generated-schemas.d.ts.map +1 -1
  46. package/dist/src/generated-schemas.js +14 -2
  47. package/dist/src/generated-schemas.js.map +1 -1
  48. package/dist/src/index.d.ts +1 -6
  49. package/dist/src/index.d.ts.map +1 -1
  50. package/dist/src/index.js +1 -170
  51. package/dist/src/index.js.map +1 -1
  52. package/dist/src/lib.d.ts +1 -7
  53. package/dist/src/lib.d.ts.map +1 -1
  54. package/dist/src/lib.js +1 -6
  55. package/dist/src/lib.js.map +1 -1
  56. package/dist/src/mcp/mcp-server-manager.d.ts +20 -0
  57. package/dist/src/mcp/mcp-server-manager.d.ts.map +1 -0
  58. package/dist/src/mcp/mcp-server-manager.js +38 -0
  59. package/dist/src/mcp/mcp-server-manager.js.map +1 -0
  60. package/dist/src/{mcp-server.d.ts → mcp/mcp-server.d.ts} +31 -1
  61. package/dist/src/mcp/mcp-server.d.ts.map +1 -0
  62. package/dist/src/{mcp-server.js → mcp/mcp-server.js} +547 -146
  63. package/dist/src/mcp/mcp-server.js.map +1 -0
  64. package/dist/src/{openapi-parser.d.ts → openapi/openapi-parser.d.ts} +1 -1
  65. package/dist/src/openapi/openapi-parser.d.ts.map +1 -0
  66. package/dist/src/{openapi-parser.js → openapi/openapi-parser.js} +2 -2
  67. package/dist/src/openapi/openapi-parser.js.map +1 -0
  68. package/dist/src/{profile-loader.d.ts → profile/profile-loader.d.ts} +3 -2
  69. package/dist/src/profile/profile-loader.d.ts.map +1 -0
  70. package/dist/src/{profile-loader.js → profile/profile-loader.js} +17 -6
  71. package/dist/src/profile/profile-loader.js.map +1 -0
  72. package/dist/src/profile/profile-registry.d.ts +18 -0
  73. package/dist/src/profile/profile-registry.d.ts.map +1 -0
  74. package/dist/src/profile/profile-registry.js +26 -0
  75. package/dist/src/profile/profile-registry.js.map +1 -0
  76. package/dist/src/profile/profile-resolver.d.ts +25 -0
  77. package/dist/src/profile/profile-resolver.d.ts.map +1 -0
  78. package/dist/src/profile/profile-resolver.js +204 -0
  79. package/dist/src/profile/profile-resolver.js.map +1 -0
  80. package/dist/src/profile/startup-profile.d.ts +17 -0
  81. package/dist/src/profile/startup-profile.d.ts.map +1 -0
  82. package/dist/src/profile/startup-profile.js +30 -0
  83. package/dist/src/profile/startup-profile.js.map +1 -0
  84. package/dist/src/profile/startup-validation.d.ts +11 -0
  85. package/dist/src/profile/startup-validation.d.ts.map +1 -0
  86. package/dist/src/profile/startup-validation.js +21 -0
  87. package/dist/src/profile/startup-validation.js.map +1 -0
  88. package/dist/src/testing/dynamic-mock-server.d.ts +24 -0
  89. package/dist/src/testing/dynamic-mock-server.d.ts.map +1 -0
  90. package/dist/src/testing/dynamic-mock-server.js +138 -0
  91. package/dist/src/testing/dynamic-mock-server.js.map +1 -0
  92. package/dist/src/testing/request-assertions.d.ts +5 -0
  93. package/dist/src/testing/request-assertions.d.ts.map +1 -0
  94. package/dist/src/testing/request-assertions.js +165 -0
  95. package/dist/src/testing/request-assertions.js.map +1 -0
  96. package/dist/src/testing/template-utils.d.ts +10 -0
  97. package/dist/src/testing/template-utils.d.ts.map +1 -0
  98. package/dist/src/testing/template-utils.js +72 -0
  99. package/dist/src/testing/template-utils.js.map +1 -0
  100. package/dist/src/testing/test-http-utils.d.ts +1 -1
  101. package/dist/src/testing/test-http-utils.d.ts.map +1 -1
  102. package/dist/src/testing/test-http-utils.js +1 -1
  103. package/dist/src/testing/test-http-utils.js.map +1 -1
  104. package/dist/src/testing/test-loader.d.ts +6 -0
  105. package/dist/src/testing/test-loader.d.ts.map +1 -0
  106. package/dist/src/testing/test-loader.js +212 -0
  107. package/dist/src/testing/test-loader.js.map +1 -0
  108. package/dist/src/testing/test-schema.d.ts +1270 -0
  109. package/dist/src/testing/test-schema.d.ts.map +1 -0
  110. package/dist/src/testing/test-schema.js +76 -0
  111. package/dist/src/testing/test-schema.js.map +1 -0
  112. package/dist/src/tool-filter/compat.d.ts +49 -0
  113. package/dist/src/tool-filter/compat.d.ts.map +1 -0
  114. package/dist/src/tool-filter/compat.js +72 -0
  115. package/dist/src/tool-filter/compat.js.map +1 -0
  116. package/dist/src/tool-filter/config/env-config-parser.d.ts +38 -0
  117. package/dist/src/tool-filter/config/env-config-parser.d.ts.map +1 -0
  118. package/dist/src/tool-filter/config/env-config-parser.js +103 -0
  119. package/dist/src/tool-filter/config/env-config-parser.js.map +1 -0
  120. package/dist/src/tool-filter/config/header-config-parser.d.ts +37 -0
  121. package/dist/src/tool-filter/config/header-config-parser.d.ts.map +1 -0
  122. package/dist/src/tool-filter/config/header-config-parser.js +118 -0
  123. package/dist/src/tool-filter/config/header-config-parser.js.map +1 -0
  124. package/dist/src/tool-filter/errors.d.ts +18 -0
  125. package/dist/src/tool-filter/errors.d.ts.map +1 -0
  126. package/dist/src/tool-filter/errors.js +21 -0
  127. package/dist/src/tool-filter/errors.js.map +1 -0
  128. package/dist/src/tool-filter/filter/filter-engine.d.ts +45 -0
  129. package/dist/src/tool-filter/filter/filter-engine.d.ts.map +1 -0
  130. package/dist/src/tool-filter/filter/filter-engine.js +94 -0
  131. package/dist/src/tool-filter/filter/filter-engine.js.map +1 -0
  132. package/dist/src/tool-filter/filter/filter-rules.d.ts +44 -0
  133. package/dist/src/tool-filter/filter/filter-rules.d.ts.map +1 -0
  134. package/dist/src/tool-filter/filter/filter-rules.js +72 -0
  135. package/dist/src/tool-filter/filter/filter-rules.js.map +1 -0
  136. package/dist/src/tool-filter/filter/global-tool-filter.d.ts +40 -0
  137. package/dist/src/tool-filter/filter/global-tool-filter.d.ts.map +1 -0
  138. package/dist/src/tool-filter/filter/global-tool-filter.js +92 -0
  139. package/dist/src/tool-filter/filter/global-tool-filter.js.map +1 -0
  140. package/dist/src/tool-filter/filter/session-tool-filter.d.ts +29 -0
  141. package/dist/src/tool-filter/filter/session-tool-filter.d.ts.map +1 -0
  142. package/dist/src/tool-filter/filter/session-tool-filter.js +69 -0
  143. package/dist/src/tool-filter/filter/session-tool-filter.js.map +1 -0
  144. package/dist/src/tool-filter/index.d.ts +25 -0
  145. package/dist/src/tool-filter/index.d.ts.map +1 -0
  146. package/dist/src/tool-filter/index.js +30 -0
  147. package/dist/src/tool-filter/index.js.map +1 -0
  148. package/dist/src/tool-filter/integration/tool-filter-service.d.ts +44 -0
  149. package/dist/src/tool-filter/integration/tool-filter-service.d.ts.map +1 -0
  150. package/dist/src/tool-filter/integration/tool-filter-service.js +68 -0
  151. package/dist/src/tool-filter/integration/tool-filter-service.js.map +1 -0
  152. package/dist/src/tool-filter/operation/operation-classifier.d.ts +20 -0
  153. package/dist/src/tool-filter/operation/operation-classifier.d.ts.map +1 -0
  154. package/dist/src/tool-filter/operation/operation-classifier.js +26 -0
  155. package/dist/src/tool-filter/operation/operation-classifier.js.map +1 -0
  156. package/dist/src/tool-filter/operation/operation-detector.d.ts +30 -0
  157. package/dist/src/tool-filter/operation/operation-detector.d.ts.map +1 -0
  158. package/dist/src/tool-filter/operation/operation-detector.js +96 -0
  159. package/dist/src/tool-filter/operation/operation-detector.js.map +1 -0
  160. package/dist/src/tool-filter/operation/operation-resolver.d.ts +22 -0
  161. package/dist/src/tool-filter/operation/operation-resolver.d.ts.map +1 -0
  162. package/dist/src/tool-filter/operation/operation-resolver.js +32 -0
  163. package/dist/src/tool-filter/operation/operation-resolver.js.map +1 -0
  164. package/dist/src/tool-filter/regex/regex-compiler.d.ts +22 -0
  165. package/dist/src/tool-filter/regex/regex-compiler.d.ts.map +1 -0
  166. package/dist/src/tool-filter/regex/regex-compiler.js +56 -0
  167. package/dist/src/tool-filter/regex/regex-compiler.js.map +1 -0
  168. package/dist/src/tool-filter/regex/regex-validator.d.ts +24 -0
  169. package/dist/src/tool-filter/regex/regex-validator.d.ts.map +1 -0
  170. package/dist/src/tool-filter/regex/regex-validator.js +58 -0
  171. package/dist/src/tool-filter/regex/regex-validator.js.map +1 -0
  172. package/dist/src/tool-filter/types.d.ts +92 -0
  173. package/dist/src/tool-filter/types.d.ts.map +1 -0
  174. package/dist/src/tool-filter/types.js +5 -0
  175. package/dist/src/tool-filter/types.js.map +1 -0
  176. package/dist/src/tool-filter/utils.d.ts +11 -0
  177. package/dist/src/tool-filter/utils.d.ts.map +1 -0
  178. package/dist/src/tool-filter/utils.js +13 -0
  179. package/dist/src/tool-filter/utils.js.map +1 -0
  180. package/dist/src/{composite-executor.d.ts → tooling/composite-executor.d.ts} +3 -3
  181. package/dist/src/tooling/composite-executor.d.ts.map +1 -0
  182. package/dist/src/{composite-executor.js → tooling/composite-executor.js} +1 -1
  183. package/dist/src/tooling/composite-executor.js.map +1 -0
  184. package/dist/src/{dag-executor.d.ts → tooling/dag-executor.d.ts} +1 -1
  185. package/dist/src/tooling/dag-executor.d.ts.map +1 -0
  186. package/dist/src/tooling/dag-executor.js.map +1 -0
  187. package/dist/src/{proxy-executor.d.ts → tooling/proxy-executor.d.ts} +2 -2
  188. package/dist/src/tooling/proxy-executor.d.ts.map +1 -0
  189. package/dist/src/{proxy-executor.js → tooling/proxy-executor.js} +8 -1
  190. package/dist/src/tooling/proxy-executor.js.map +1 -0
  191. package/dist/src/{tool-generator.d.ts → tooling/tool-generator.d.ts} +4 -3
  192. package/dist/src/tooling/tool-generator.d.ts.map +1 -0
  193. package/dist/src/{tool-generator.js → tooling/tool-generator.js} +23 -7
  194. package/dist/src/tooling/tool-generator.js.map +1 -0
  195. package/dist/src/{http-client-factory.d.ts → transport/http-client-factory.d.ts} +4 -1
  196. package/dist/src/transport/http-client-factory.d.ts.map +1 -0
  197. package/dist/src/{http-client-factory.js → transport/http-client-factory.js} +13 -3
  198. package/dist/src/transport/http-client-factory.js.map +1 -0
  199. package/dist/src/transport/http-transport-config.d.ts +6 -0
  200. package/dist/src/transport/http-transport-config.d.ts.map +1 -0
  201. package/dist/src/transport/http-transport-config.js +62 -0
  202. package/dist/src/transport/http-transport-config.js.map +1 -0
  203. package/dist/src/{http-transport.d.ts → transport/http-transport.d.ts} +72 -14
  204. package/dist/src/transport/http-transport.d.ts.map +1 -0
  205. package/dist/src/{http-transport.js → transport/http-transport.js} +1166 -493
  206. package/dist/src/transport/http-transport.js.map +1 -0
  207. package/dist/src/{interceptors.d.ts → transport/interceptors.d.ts} +6 -2
  208. package/dist/src/transport/interceptors.d.ts.map +1 -0
  209. package/dist/src/{interceptors.js → transport/interceptors.js} +72 -41
  210. package/dist/src/transport/interceptors.js.map +1 -0
  211. package/dist/src/types/http-transport.d.ts +25 -0
  212. package/dist/src/types/http-transport.d.ts.map +1 -1
  213. package/dist/src/types/profile.d.ts +13 -1
  214. package/dist/src/types/profile.d.ts.map +1 -1
  215. package/dist/src/validation/argument-normalizer.d.ts +6 -0
  216. package/dist/src/validation/argument-normalizer.d.ts.map +1 -0
  217. package/dist/src/validation/argument-normalizer.js +70 -0
  218. package/dist/src/validation/argument-normalizer.js.map +1 -0
  219. package/dist/src/validation/jsonrpc-validator.d.ts.map +1 -0
  220. package/dist/src/validation/jsonrpc-validator.js.map +1 -0
  221. package/dist/src/{schema-validator.d.ts → validation/schema-validator.d.ts} +2 -2
  222. package/dist/src/validation/schema-validator.d.ts.map +1 -0
  223. package/dist/src/validation/schema-validator.js.map +1 -0
  224. package/dist/src/validation/validation-utils.d.ts.map +1 -0
  225. package/dist/src/validation/validation-utils.js.map +1 -0
  226. package/package.json +9 -3
  227. package/profile-schema.json +63 -3
  228. package/profiles/gitlab/developer-profile-oauth.json +1520 -0
  229. package/profiles/gitlab/developer-profile-oauth.test.json +3432 -0
  230. package/profiles/gitlab/openapi.yaml +6891 -0
  231. package/profiles/n8n/openapi.yaml +2441 -0
  232. package/profiles/n8n/profile-optimized.json +965 -0
  233. package/profiles/n8n/profile-optimized.test.json +1078 -0
  234. package/profiles/n8n/profile.json +1033 -0
  235. package/profiles/n8n/profile.test.json +983 -0
  236. package/profiles/n8n-nodes/openapi.yaml +24 -0
  237. package/profiles/n8n-nodes/profile-nodes.json +44 -0
  238. package/profiles/n8n-nodes/profile-nodes.test.json +91 -0
  239. package/profiles/semgrep/openapi.yaml +4706 -0
  240. package/profiles/semgrep/profile.json +692 -0
  241. package/profiles/semgrep/profile.test.json +471 -0
  242. package/profiles/youtrack/openapi.json +16976 -0
  243. package/profiles/youtrack/profile.json +608 -0
  244. package/profiles/youtrack/profile.test.json +1926 -0
  245. package/dist/src/composite-executor.d.ts.map +0 -1
  246. package/dist/src/composite-executor.js.map +0 -1
  247. package/dist/src/constants.d.ts.map +0 -1
  248. package/dist/src/constants.js.map +0 -1
  249. package/dist/src/dag-executor.d.ts.map +0 -1
  250. package/dist/src/dag-executor.js.map +0 -1
  251. package/dist/src/errors.d.ts.map +0 -1
  252. package/dist/src/errors.js.map +0 -1
  253. package/dist/src/http-client-factory.d.ts.map +0 -1
  254. package/dist/src/http-client-factory.js.map +0 -1
  255. package/dist/src/http-transport.d.ts.map +0 -1
  256. package/dist/src/http-transport.js.map +0 -1
  257. package/dist/src/interceptors.d.ts.map +0 -1
  258. package/dist/src/interceptors.js.map +0 -1
  259. package/dist/src/jsonrpc-validator.d.ts.map +0 -1
  260. package/dist/src/jsonrpc-validator.js.map +0 -1
  261. package/dist/src/logger.d.ts.map +0 -1
  262. package/dist/src/logger.js.map +0 -1
  263. package/dist/src/mcp-server.d.ts.map +0 -1
  264. package/dist/src/mcp-server.js.map +0 -1
  265. package/dist/src/metrics.d.ts.map +0 -1
  266. package/dist/src/metrics.js.map +0 -1
  267. package/dist/src/naming-warnings.d.ts.map +0 -1
  268. package/dist/src/naming-warnings.js.map +0 -1
  269. package/dist/src/naming.d.ts.map +0 -1
  270. package/dist/src/naming.js.map +0 -1
  271. package/dist/src/oauth-provider.d.ts.map +0 -1
  272. package/dist/src/oauth-provider.js.map +0 -1
  273. package/dist/src/openapi-parser.d.ts.map +0 -1
  274. package/dist/src/openapi-parser.js.map +0 -1
  275. package/dist/src/profile-loader.d.ts.map +0 -1
  276. package/dist/src/profile-loader.js.map +0 -1
  277. package/dist/src/proxy-executor.d.ts.map +0 -1
  278. package/dist/src/proxy-executor.js.map +0 -1
  279. package/dist/src/schema-validator.d.ts.map +0 -1
  280. package/dist/src/schema-validator.js.map +0 -1
  281. package/dist/src/testing/fixtures.d.ts +0 -684
  282. package/dist/src/testing/fixtures.d.ts.map +0 -1
  283. package/dist/src/testing/fixtures.js +0 -528
  284. package/dist/src/testing/fixtures.js.map +0 -1
  285. package/dist/src/testing/mock-gitlab-server.d.ts +0 -43
  286. package/dist/src/testing/mock-gitlab-server.d.ts.map +0 -1
  287. package/dist/src/testing/mock-gitlab-server.js +0 -1026
  288. package/dist/src/testing/mock-gitlab-server.js.map +0 -1
  289. package/dist/src/testing/mock-semgrep-server.d.ts +0 -32
  290. package/dist/src/testing/mock-semgrep-server.d.ts.map +0 -1
  291. package/dist/src/testing/mock-semgrep-server.js +0 -213
  292. package/dist/src/testing/mock-semgrep-server.js.map +0 -1
  293. package/dist/src/testing/mock-youtrack-server.d.ts +0 -11
  294. package/dist/src/testing/mock-youtrack-server.d.ts.map +0 -1
  295. package/dist/src/testing/mock-youtrack-server.js +0 -152
  296. package/dist/src/testing/mock-youtrack-server.js.map +0 -1
  297. package/dist/src/tool-generator.d.ts.map +0 -1
  298. package/dist/src/tool-generator.js.map +0 -1
  299. package/dist/src/validation-utils.d.ts.map +0 -1
  300. package/dist/src/validation-utils.js.map +0 -1
  301. /package/dist/src/{naming-warnings.d.ts → core/naming-warnings.d.ts} +0 -0
  302. /package/dist/src/{naming-warnings.js → core/naming-warnings.js} +0 -0
  303. /package/dist/src/{naming.d.ts → core/naming.d.ts} +0 -0
  304. /package/dist/src/{naming.js → core/naming.js} +0 -0
  305. /package/dist/src/{dag-executor.js → tooling/dag-executor.js} +0 -0
  306. /package/dist/src/{jsonrpc-validator.d.ts → validation/jsonrpc-validator.d.ts} +0 -0
  307. /package/dist/src/{jsonrpc-validator.js → validation/jsonrpc-validator.js} +0 -0
  308. /package/dist/src/{schema-validator.js → validation/schema-validator.js} +0 -0
  309. /package/dist/src/{validation-utils.d.ts → validation/validation-utils.d.ts} +0 -0
  310. /package/dist/src/{validation-utils.js → validation/validation-utils.js} +0 -0
@@ -7,21 +7,41 @@
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';
23
- import { isSafePropertyName } from './validation-utils.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';
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
  *
@@ -46,22 +66,17 @@ export class MCPServer {
46
66
  return root;
47
67
  }
48
68
  mergeFieldSelector(target, selector) {
49
- const baseName = selector.split('(')[0].trim();
69
+ const parsed = this.parseFieldSelector(selector);
70
+ const baseName = parsed.baseName;
50
71
  if (!baseName)
51
72
  return;
52
73
  if (!isSafePropertyName(baseName))
53
74
  return;
54
- const openParen = selector.indexOf('(');
55
- if (openParen === -1) {
56
- target[baseName] = true;
57
- return;
58
- }
59
- const closeParen = selector.lastIndexOf(')');
60
- if (closeParen === -1 || closeParen <= openParen) {
75
+ if (!parsed.inner) {
61
76
  target[baseName] = true;
62
77
  return;
63
78
  }
64
- const inner = selector.slice(openParen + 1, closeParen).trim();
79
+ const inner = parsed.inner;
65
80
  const subSelectors = this.splitTopLevel(inner);
66
81
  const subTree = Object.create(null);
67
82
  for (const sub of subSelectors) {
@@ -76,6 +91,59 @@ export class MCPServer {
76
91
  }
77
92
  this.mergeSelectionTrees(existing, subTree);
78
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
+ }
79
147
  mergeSelectionTrees(target, incoming) {
80
148
  for (const [key, val] of Object.entries(incoming)) {
81
149
  if (!isSafePropertyName(key))
@@ -96,12 +164,31 @@ export class MCPServer {
96
164
  const result = [];
97
165
  let depth = 0;
98
166
  let current = '';
167
+ let inQuote = false;
168
+ let escaped = false;
99
169
  for (const ch of input) {
100
- if (ch === '(')
101
- depth += 1;
102
- if (ch === ')')
103
- depth = Math.max(0, depth - 1);
104
- if (ch === ',' && depth === 0) {
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) {
105
192
  const trimmed = current.trim();
106
193
  if (trimmed)
107
194
  result.push(trimmed);
@@ -177,6 +264,9 @@ export class MCPServer {
177
264
  if (error instanceof OperationNotFoundError) {
178
265
  return `Operation not found: ${error.message} (correlation ID: ${correlationId})`;
179
266
  }
267
+ if (error instanceof ResourceNotFoundError) {
268
+ return `${error.message} (correlation ID: ${correlationId})`;
269
+ }
180
270
  // Configuration errors - safe to show (helps admin fix setup)
181
271
  if (error instanceof ConfigurationError) {
182
272
  return `Configuration error: ${error.message} (correlation ID: ${correlationId})`;
@@ -223,6 +313,7 @@ export class MCPServer {
223
313
  // Check if we should warn about long names
224
314
  this.checkToolNameLengths();
225
315
  }
316
+ this.applyGlobalToolFiltering();
226
317
  // Re-create logger with auth config for token redaction
227
318
  const authConfigs = this.getAuthConfigs();
228
319
  if (authConfigs.length > 0) {
@@ -239,8 +330,8 @@ export class MCPServer {
239
330
  const envAuthConfig = this.getEnvBackedAuthConfig();
240
331
  const envVarName = envAuthConfig?.value_from_env;
241
332
  const envToken = envVarName ? process.env[envVarName] : undefined;
242
- if (envAuthConfig && envToken) {
243
- // 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
244
335
  const httpClient = this.httpClientFactory.createGlobalClient({
245
336
  profile: this.profile,
246
337
  baseUrl,
@@ -346,6 +437,59 @@ export class MCPServer {
346
437
  const oauthConfig = configs.find(c => c.type === 'oauth');
347
438
  return oauthConfig?.oauth_config;
348
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
+ }
349
493
  /**
350
494
  * Extract hostnames from origin patterns for OAuth redirect validation
351
495
  * e.g., "http://localhost:*,https://app.example.com" -> ["localhost", "app.example.com"]
@@ -385,7 +529,7 @@ export class MCPServer {
385
529
  /**
386
530
  * Get or create HTTP client for session
387
531
  */
388
- async getHttpClientForSession(sessionId) {
532
+ async getHttpClientForSession(sessionId, profileId) {
389
533
  if (!sessionId) {
390
534
  // Fallback to global client for stdio transport
391
535
  if (!this.httpClientFactory.hasGlobalClient()) {
@@ -408,7 +552,7 @@ export class MCPServer {
408
552
  throw new ConfigurationError('Profile not initialized. Call initialize() first.');
409
553
  }
410
554
  // Get auth token from session (ensures token is valid/refreshed)
411
- const authToken = await this.getAuthTokenFromSession(sessionId);
555
+ const authToken = await this.getAuthTokenFromSession(sessionId, profileId);
412
556
  // Create or get session client using factory
413
557
  return this.httpClientFactory.getOrCreateSessionClient(sessionId, {
414
558
  profile: this.profile,
@@ -420,7 +564,7 @@ export class MCPServer {
420
564
  * Get auth token from HTTP transport session
421
565
  * Ensures token is valid (refreshes if expired) before returning
422
566
  */
423
- async getAuthTokenFromSession(sessionId) {
567
+ async getAuthTokenFromSession(sessionId, profileId) {
424
568
  // Early return if sessionId is missing/empty
425
569
  // Prevents misleading warn logs with empty sessionId
426
570
  if (!sessionId) {
@@ -430,23 +574,24 @@ export class MCPServer {
430
574
  return undefined;
431
575
  }
432
576
  // Ensure token is valid (refresh if expired)
433
- const isValid = await this.httpTransport.ensureValidSessionToken(sessionId);
577
+ const effectiveProfileId = profileId || this.getProfileIdValue();
578
+ const isValid = await this.httpTransport.ensureValidSessionToken(effectiveProfileId, sessionId);
434
579
  if (!isValid) {
435
- this.logger.warn('Session token validation/refresh failed', { sessionId });
580
+ this.logger.warn('Session token validation/refresh failed', { profileId: effectiveProfileId, sessionId });
436
581
  // Still return token if available - let the API call fail with proper error
437
582
  }
438
583
  // Use public API instead of type casting
439
- return this.httpTransport.getSessionToken(sessionId);
584
+ return this.httpTransport.getSessionToken(effectiveProfileId, sessionId);
440
585
  }
441
586
  /**
442
587
  * Cleanup HTTP client for destroyed session
443
588
  *
444
589
  * Why: Prevent memory leak - sessions expire but cached clients stay forever
445
590
  */
446
- cleanupSessionClient(sessionId) {
591
+ cleanupSessionClient(profileId, sessionId) {
447
592
  const removed = this.httpClientFactory.cleanupSessionClient(sessionId);
448
593
  if (removed) {
449
- this.logger.info('Cleaned up session HTTP client', { sessionId });
594
+ this.logger.info('Cleaned up session HTTP client', { profileId, sessionId });
450
595
  }
451
596
  }
452
597
  /**
@@ -480,7 +625,8 @@ export class MCPServer {
480
625
  if (!toolDef) {
481
626
  throw new OperationNotFoundError(request.params.name);
482
627
  }
483
- const args = request.params.arguments || {};
628
+ const rawArgs = request.params.arguments || {};
629
+ const args = applyParameterDefaults(toolDef, rawArgs);
484
630
  // Validate arguments
485
631
  this.toolGenerator.validateArguments(toolDef, args);
486
632
  // Execute composite or simple tool
@@ -530,15 +676,16 @@ export class MCPServer {
530
676
  * Why separate: Simple tools map directly to single OpenAPI operation.
531
677
  * No result aggregation needed.
532
678
  */
533
- async executeSimpleTool(toolDef, args, sessionId) {
679
+ async executeSimpleTool(toolDef, args, sessionId, profileId) {
680
+ const normalizedArgs = normalizeArguments(toolDef, args);
534
681
  this.logger.debug('Executing simple tool', {
535
682
  toolName: toolDef.name,
536
- action: args['action'],
537
- resourceType: args['resource_type'],
683
+ action: normalizedArgs['action'],
684
+ resourceType: normalizedArgs['resource_type'],
538
685
  sessionId
539
686
  });
540
687
  // Get operation definition (can be string or ProxyDownloadOperation)
541
- const operationDef = this.toolGenerator.getOperationDefinition(toolDef, args);
688
+ const operationDef = this.toolGenerator.getOperationDefinition(toolDef, normalizedArgs);
542
689
  if (!operationDef) {
543
690
  throw new ValidationError(`Could not map tool action to operation`, {
544
691
  toolName: toolDef.name,
@@ -549,7 +696,7 @@ export class MCPServer {
549
696
  }
550
697
  // Check if this is a proxy download operation
551
698
  if (typeof operationDef === 'object' && operationDef.type === 'proxy_download') {
552
- return this.executeProxyDownload(operationDef, args, sessionId);
699
+ return this.executeProxyDownload(operationDef, normalizedArgs, sessionId, profileId);
553
700
  }
554
701
  // Regular string operation
555
702
  const operationId = operationDef;
@@ -558,9 +705,9 @@ export class MCPServer {
558
705
  throw new OperationNotFoundError(operationId);
559
706
  }
560
707
  // 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);
708
+ const path = this.resolvePath(operation.path, normalizedArgs);
709
+ const queryParams = this.extractQueryParams(operation, normalizedArgs);
710
+ const body = this.extractBody(operation, normalizedArgs, toolDef);
564
711
  this.logger.debug('Executing HTTP request', {
565
712
  operationId,
566
713
  method: operation.method,
@@ -579,9 +726,9 @@ export class MCPServer {
579
726
  }
580
727
  }
581
728
  // Execute with session-specific client
582
- const httpClient = await this.getHttpClientForSession(sessionId);
729
+ const httpClient = await this.getHttpClientForSession(sessionId, profileId);
583
730
  // Set fields parameter if response_fields are configured for this action AND enabled
584
- const action = args.action;
731
+ const action = normalizedArgs.action;
585
732
  if (toolDef.send_response_fields_as_param && toolDef.response_fields && action && toolDef.response_fields[action]) {
586
733
  const fields = toolDef.response_fields[action];
587
734
  queryParams.fields = fields.join(',');
@@ -594,7 +741,7 @@ export class MCPServer {
594
741
  // Apply response field filtering if configured
595
742
  let result = response.body;
596
743
  if (toolDef.response_fields) {
597
- const action = args.action;
744
+ const action = normalizedArgs.action;
598
745
  if (action && toolDef.response_fields[action]) {
599
746
  const fields = toolDef.response_fields[action];
600
747
  result = this.filterFields(result, fields);
@@ -608,7 +755,7 @@ export class MCPServer {
608
755
  * Why: Some APIs return authenticated URLs that LLMs cannot fetch directly.
609
756
  * This proxies the download through the MCP server.
610
757
  */
611
- async executeProxyDownload(operation, args, sessionId) {
758
+ async executeProxyDownload(operation, args, sessionId, profileId) {
612
759
  this.logger.debug('Executing proxy download', {
613
760
  metadataEndpoint: operation.metadata_endpoint,
614
761
  urlField: operation.url_field,
@@ -634,7 +781,7 @@ export class MCPServer {
634
781
  };
635
782
  }
636
783
  // Get auth credentials for download
637
- const httpClient = await this.getHttpClientForSession(sessionId);
784
+ const httpClient = await this.getHttpClientForSession(sessionId, profileId);
638
785
  const authCredentials = httpClient.getAuthCredentials();
639
786
  // Execute proxy download
640
787
  const proxyExecutor = new ProxyDownloadExecutor(httpClient, this.logger);
@@ -735,17 +882,47 @@ export class MCPServer {
735
882
  pathOrQuery.add(param.name);
736
883
  }
737
884
  }
738
- // 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;
739
887
  const bodySchemaProps = new Set();
740
888
  if (operation.requestBody?.content) {
741
- // Check all content types (typically application/json)
742
- for (const mediaType of Object.values(operation.requestBody.content)) {
743
- if (mediaType.schema?.properties) {
744
- for (const propName of Object.keys(mediaType.schema.properties)) {
745
- 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;
746
897
  }
747
898
  }
748
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;
749
926
  }
750
927
  const body = {};
751
928
  let hasBody = false;
@@ -780,76 +957,26 @@ export class MCPServer {
780
957
  * and resumability for reliable communication over HTTP.
781
958
  */
782
959
  async runHttp(host, port) {
783
- const { HttpTransport } = await import('./http-transport.js');
784
- // Get OAuth config from profile (supports multi-auth)
785
- const oauthConfig = this.getOAuthConfig();
786
- if (oauthConfig) {
960
+ const { HttpTransport } = await import('../transport/http-transport.js');
961
+ const profileContext = this.getHttpProfileContext();
962
+ if (profileContext.oauthConfig) {
787
963
  this.logger.info('OAuth authentication enabled for HTTP transport');
788
964
  }
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();
965
+ const baseConfig = buildHttpTransportBaseConfig(host, port);
797
966
  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),
967
+ ...baseConfig,
968
+ profileRoutingEnabled: false,
969
+ defaultProfileId: profileContext.profileId,
812
970
  // 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
- })(),
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,
853
980
  };
854
981
  // Warn if binding to non-localhost without explicit MCP4_ALLOWED_ORIGINS
855
982
  const isLocalhost = host === 'localhost' || host === '127.0.0.1' || host === '::1';
@@ -858,37 +985,62 @@ export class MCPServer {
858
985
  this.logger.warn('Binding to non-localhost with empty MCP4_ALLOWED_ORIGINS. Set MCP4_ALLOWED_ORIGINS or bind to localhost.');
859
986
  }
860
987
  this.httpTransport = new HttpTransport(config, this.logger);
988
+ const metricsCollector = this.httpTransport.getMetricsCollector?.() || null;
989
+ this.httpClientFactory.setMetricsCollector(metricsCollector);
990
+ this.recordGlobalToolFilterMetrics();
861
991
  // Set message handler to process JSON-RPC messages
862
- this.httpTransport.setMessageHandler(async (message, sessionId) => {
863
- return await this.handleJsonRpcMessage(message, sessionId);
992
+ this.httpTransport.setMessageHandler(async (message, sessionId, profileId) => {
993
+ return await this.handleJsonRpcMessage(message, sessionId, profileId);
864
994
  });
865
995
  // Register cleanup listener for session destruction (memory leak prevention)
866
- this.httpTransport.onSessionDestroyed((sessionId) => {
867
- this.cleanupSessionClient(sessionId);
996
+ this.httpTransport.onSessionDestroyed((profileId, sessionId) => {
997
+ this.cleanupSessionClient(profileId, sessionId);
868
998
  });
869
999
  await this.httpTransport.start();
870
1000
  this.logger.info('MCP server running on HTTP', { host, port });
871
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
+ }
872
1010
  /**
873
1011
  * Handle JSON-RPC message from HTTP transport
874
1012
  *
875
1013
  * Why: Unified message handling for both stdio and HTTP transports
876
1014
  */
877
- async handleJsonRpcMessage(message, sessionId) {
1015
+ async handleJsonRpcMessage(message, sessionId, profileId) {
878
1016
  // Handle initialize
879
1017
  if (isInitializeRequest(message)) {
880
- return this.handleInitialize(message, sessionId);
1018
+ return this.handleInitialize(message, sessionId, profileId);
881
1019
  }
882
1020
  // Handle tool calls
883
1021
  if (isToolCallRequest(message)) {
884
- return await this.handleToolCall(message, sessionId);
1022
+ return await this.handleToolCall(message, sessionId, profileId);
885
1023
  }
886
1024
  // Handle other JSON-RPC requests
887
1025
  // (tools/list, prompts/list, etc.)
888
- 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);
889
1030
  }
890
- handleInitialize(message, sessionId) {
1031
+ handleInitialize(message, sessionId, profileId) {
891
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
+ }
892
1044
  const result = {
893
1045
  protocolVersion: '2025-03-26',
894
1046
  serverInfo: {
@@ -911,14 +1063,14 @@ export class MCPServer {
911
1063
  result,
912
1064
  };
913
1065
  }
914
- async handleToolCall(message, sessionId) {
1066
+ async handleToolCall(message, sessionId, profileId) {
915
1067
  const req = message;
916
1068
  const params = req.params;
917
1069
  const toolName = params.name;
918
- const args = params.arguments;
1070
+ const rawArgs = params.arguments || {};
919
1071
  // Check OAuth authentication for tool operations
920
- if (this.httpTransport && this.httpTransport.hasOAuthProvider()) {
921
- const authToken = await this.getAuthTokenFromSession(sessionId || '');
1072
+ if (this.httpTransport && this.httpTransport.hasOAuthProvider(profileId)) {
1073
+ const authToken = await this.getAuthTokenFromSession(sessionId || '', profileId);
922
1074
  if (!authToken) {
923
1075
  // Return OAuth required error with WWW-Authenticate header
924
1076
  // This should trigger the OAuth flow in the client
@@ -930,7 +1082,7 @@ export class MCPServer {
930
1082
  message: 'Authentication required. Please authorize via OAuth.',
931
1083
  data: {
932
1084
  oauth_required: true,
933
- resource_metadata: `${this.httpTransport.getServerUrl()}/.well-known/oauth-protected-resource/mcp`,
1085
+ resource_metadata: this.httpTransport.getOAuthProtectedResourceUrl(profileId),
934
1086
  scope: 'api'
935
1087
  }
936
1088
  }
@@ -938,16 +1090,36 @@ export class MCPServer {
938
1090
  return errorResponse;
939
1091
  }
940
1092
  }
1093
+ let args = rawArgs;
941
1094
  try {
942
1095
  // Find tool definition
943
1096
  const toolDef = this.profile?.tools.find(t => t.name === toolName);
944
1097
  if (!toolDef) {
945
- 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
+ });
946
1118
  }
947
1119
  // Execute tool (reuse existing execution logic)
948
1120
  let result;
949
1121
  if (toolDef.composite && toolDef.steps) {
950
- const httpClient = await this.getHttpClientForSession(sessionId);
1122
+ const httpClient = await this.getHttpClientForSession(sessionId, profileId);
951
1123
  const compositeResult = await this.compositeExecutor.execute(toolDef.steps, args, toolDef.partial_results || false, httpClient);
952
1124
  result = {
953
1125
  data: compositeResult.data,
@@ -958,7 +1130,7 @@ export class MCPServer {
958
1130
  };
959
1131
  }
960
1132
  else {
961
- result = await this.executeSimpleTool(toolDef, args, sessionId);
1133
+ result = await this.executeSimpleTool(toolDef, args, sessionId, profileId);
962
1134
  }
963
1135
  return {
964
1136
  jsonrpc: '2.0',
@@ -1003,6 +1175,9 @@ export class MCPServer {
1003
1175
  else if (error instanceof OperationNotFoundError) {
1004
1176
  errorCode = -32601; // Method not found
1005
1177
  }
1178
+ else if (error instanceof ResourceNotFoundError) {
1179
+ errorCode = -32601; // Method not found
1180
+ }
1006
1181
  return {
1007
1182
  jsonrpc: '2.0',
1008
1183
  id: req.id,
@@ -1013,11 +1188,35 @@ export class MCPServer {
1013
1188
  };
1014
1189
  }
1015
1190
  }
1016
- 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) {
1017
1216
  const req = message;
1018
1217
  // Check OAuth authentication for other operations (like tools/list)
1019
- if (this.httpTransport && this.httpTransport.hasOAuthProvider()) {
1020
- const authToken = await this.getAuthTokenFromSession(sessionId || '');
1218
+ if (this.httpTransport && this.httpTransport.hasOAuthProvider(profileId)) {
1219
+ const authToken = await this.getAuthTokenFromSession(sessionId || '', profileId);
1021
1220
  if (!authToken) {
1022
1221
  // Return OAuth required error with WWW-Authenticate header
1023
1222
  // This should trigger the OAuth flow in the client
@@ -1029,7 +1228,7 @@ export class MCPServer {
1029
1228
  message: 'Authentication required. Please authorize via OAuth.',
1030
1229
  data: {
1031
1230
  oauth_required: true,
1032
- resource_metadata: `${this.httpTransport.getServerUrl()}/.well-known/oauth-protected-resource/mcp`,
1231
+ resource_metadata: this.httpTransport.getOAuthProtectedResourceUrl(profileId),
1033
1232
  scope: 'api'
1034
1233
  }
1035
1234
  }
@@ -1039,7 +1238,11 @@ export class MCPServer {
1039
1238
  }
1040
1239
  // Handle tools/list
1041
1240
  if (req.method === 'tools/list') {
1042
- 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)) || [];
1043
1246
  return {
1044
1247
  jsonrpc: '2.0',
1045
1248
  id: req.id,
@@ -1058,6 +1261,204 @@ export class MCPServer {
1058
1261
  },
1059
1262
  };
1060
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
+ }
1061
1462
  /**
1062
1463
  * Stop the MCP server gracefully
1063
1464
  *