@confluentinc/mcp-confluent 1.3.0 → 1.5.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 (429) hide show
  1. package/README.md +180 -78
  2. package/assets/oauth-templates/callback-success.html +80 -0
  3. package/assets/oauth-templates/styles.html +101 -40
  4. package/config.example.yaml +36 -7
  5. package/config.oauth.example.yaml +14 -1
  6. package/dist/config/env-config.d.ts +9 -3
  7. package/dist/config/env-config.d.ts.map +1 -1
  8. package/dist/config/env-config.js +20 -34
  9. package/dist/config/env-config.js.map +1 -1
  10. package/dist/config/models.d.ts +108 -13
  11. package/dist/config/models.d.ts.map +1 -1
  12. package/dist/config/models.js +54 -37
  13. package/dist/config/models.js.map +1 -1
  14. package/dist/confluent/base-client-manager.d.ts +36 -1
  15. package/dist/confluent/base-client-manager.d.ts.map +1 -1
  16. package/dist/confluent/base-client-manager.js +4 -0
  17. package/dist/confluent/base-client-manager.js.map +1 -1
  18. package/dist/confluent/client-manager.d.ts +14 -0
  19. package/dist/confluent/client-manager.d.ts.map +1 -1
  20. package/dist/confluent/direct-client-manager.d.ts +2 -2
  21. package/dist/confluent/direct-client-manager.d.ts.map +1 -1
  22. package/dist/confluent/direct-client-manager.js +25 -17
  23. package/dist/confluent/direct-client-manager.js.map +1 -1
  24. package/dist/confluent/node-deps.d.ts +1 -0
  25. package/dist/confluent/node-deps.d.ts.map +1 -1
  26. package/dist/confluent/node-deps.js +7 -1
  27. package/dist/confluent/node-deps.js.map +1 -1
  28. package/dist/confluent/oauth/auth0-config.d.ts +26 -4
  29. package/dist/confluent/oauth/auth0-config.d.ts.map +1 -1
  30. package/dist/confluent/oauth/auth0-config.js +59 -19
  31. package/dist/confluent/oauth/auth0-config.js.map +1 -1
  32. package/dist/confluent/oauth/callback-pages.d.ts +9 -1
  33. package/dist/confluent/oauth/callback-pages.d.ts.map +1 -1
  34. package/dist/confluent/oauth/callback-pages.js +14 -5
  35. package/dist/confluent/oauth/callback-pages.js.map +1 -1
  36. package/dist/confluent/oauth/pkce-login.d.ts +20 -1
  37. package/dist/confluent/oauth/pkce-login.d.ts.map +1 -1
  38. package/dist/confluent/oauth/pkce-login.js +118 -9
  39. package/dist/confluent/oauth/pkce-login.js.map +1 -1
  40. package/dist/confluent/oauth/types.d.ts +1 -2
  41. package/dist/confluent/oauth/types.d.ts.map +1 -1
  42. package/dist/confluent/oauth-client-manager.d.ts +7 -4
  43. package/dist/confluent/oauth-client-manager.d.ts.map +1 -1
  44. package/dist/confluent/oauth-client-manager.js +34 -12
  45. package/dist/confluent/oauth-client-manager.js.map +1 -1
  46. package/dist/confluent/oauth-resource-resolvers.d.ts +11 -0
  47. package/dist/confluent/oauth-resource-resolvers.d.ts.map +1 -1
  48. package/dist/confluent/oauth-resource-resolvers.js +24 -0
  49. package/dist/confluent/oauth-resource-resolvers.js.map +1 -1
  50. package/dist/confluent/schema-registry-helper.d.ts +119 -16
  51. package/dist/confluent/schema-registry-helper.d.ts.map +1 -1
  52. package/dist/confluent/schema-registry-helper.js +303 -49
  53. package/dist/confluent/schema-registry-helper.js.map +1 -1
  54. package/dist/confluent/telemetry.d.ts +7 -1
  55. package/dist/confluent/telemetry.d.ts.map +1 -1
  56. package/dist/confluent/telemetry.js +6 -0
  57. package/dist/confluent/telemetry.js.map +1 -1
  58. package/dist/confluent/tools/base-tools.d.ts +207 -21
  59. package/dist/confluent/tools/base-tools.d.ts.map +1 -1
  60. package/dist/confluent/tools/base-tools.js +259 -20
  61. package/dist/confluent/tools/base-tools.js.map +1 -1
  62. package/dist/confluent/tools/cluster-arg-resolvers.d.ts +21 -0
  63. package/dist/confluent/tools/cluster-arg-resolvers.d.ts.map +1 -1
  64. package/dist/confluent/tools/cluster-arg-resolvers.js +45 -0
  65. package/dist/confluent/tools/cluster-arg-resolvers.js.map +1 -1
  66. package/dist/confluent/tools/connection-predicates.d.ts +83 -12
  67. package/dist/confluent/tools/connection-predicates.d.ts.map +1 -1
  68. package/dist/confluent/tools/connection-predicates.js +85 -11
  69. package/dist/confluent/tools/connection-predicates.js.map +1 -1
  70. package/dist/confluent/tools/handlers/billing/list-billing-costs-handler.d.ts +2 -1
  71. package/dist/confluent/tools/handlers/billing/list-billing-costs-handler.d.ts.map +1 -1
  72. package/dist/confluent/tools/handlers/billing/list-billing-costs-handler.js +37 -4
  73. package/dist/confluent/tools/handlers/billing/list-billing-costs-handler.js.map +1 -1
  74. package/dist/confluent/tools/handlers/catalog/add-tags-to-topic.d.ts +3 -3
  75. package/dist/confluent/tools/handlers/catalog/add-tags-to-topic.d.ts.map +1 -1
  76. package/dist/confluent/tools/handlers/catalog/add-tags-to-topic.js +11 -6
  77. package/dist/confluent/tools/handlers/catalog/add-tags-to-topic.js.map +1 -1
  78. package/dist/confluent/tools/handlers/catalog/create-topic-tags.d.ts +3 -3
  79. package/dist/confluent/tools/handlers/catalog/create-topic-tags.d.ts.map +1 -1
  80. package/dist/confluent/tools/handlers/catalog/create-topic-tags.js +11 -6
  81. package/dist/confluent/tools/handlers/catalog/create-topic-tags.js.map +1 -1
  82. package/dist/confluent/tools/handlers/catalog/delete-tag.d.ts +3 -3
  83. package/dist/confluent/tools/handlers/catalog/delete-tag.d.ts.map +1 -1
  84. package/dist/confluent/tools/handlers/catalog/delete-tag.js +11 -6
  85. package/dist/confluent/tools/handlers/catalog/delete-tag.js.map +1 -1
  86. package/dist/confluent/tools/handlers/catalog/list-tags.d.ts +4 -4
  87. package/dist/confluent/tools/handlers/catalog/list-tags.d.ts.map +1 -1
  88. package/dist/confluent/tools/handlers/catalog/list-tags.js +16 -7
  89. package/dist/confluent/tools/handlers/catalog/list-tags.js.map +1 -1
  90. package/dist/confluent/tools/handlers/catalog/remove-tag-from-entity.d.ts +3 -3
  91. package/dist/confluent/tools/handlers/catalog/remove-tag-from-entity.d.ts.map +1 -1
  92. package/dist/confluent/tools/handlers/catalog/remove-tag-from-entity.js +11 -6
  93. package/dist/confluent/tools/handlers/catalog/remove-tag-from-entity.js.map +1 -1
  94. package/dist/confluent/tools/handlers/clusters/list-clusters-handler.d.ts +2 -1
  95. package/dist/confluent/tools/handlers/clusters/list-clusters-handler.d.ts.map +1 -1
  96. package/dist/confluent/tools/handlers/clusters/list-clusters-handler.js +3 -2
  97. package/dist/confluent/tools/handlers/clusters/list-clusters-handler.js.map +1 -1
  98. package/dist/confluent/tools/handlers/connect/connect-tool-handler.d.ts +22 -9
  99. package/dist/confluent/tools/handlers/connect/connect-tool-handler.d.ts.map +1 -1
  100. package/dist/confluent/tools/handlers/connect/connect-tool-handler.js +35 -11
  101. package/dist/confluent/tools/handlers/connect/connect-tool-handler.js.map +1 -1
  102. package/dist/confluent/tools/handlers/connect/create-connector-handler.d.ts.map +1 -1
  103. package/dist/confluent/tools/handlers/connect/create-connector-handler.js +1 -2
  104. package/dist/confluent/tools/handlers/connect/create-connector-handler.js.map +1 -1
  105. package/dist/confluent/tools/handlers/connect/delete-connector-handler.d.ts.map +1 -1
  106. package/dist/confluent/tools/handlers/connect/delete-connector-handler.js +1 -2
  107. package/dist/confluent/tools/handlers/connect/delete-connector-handler.js.map +1 -1
  108. package/dist/confluent/tools/handlers/connect/{read-connectors-handler.d.ts → get-connector-config-handler.d.ts} +2 -3
  109. package/dist/confluent/tools/handlers/connect/get-connector-config-handler.d.ts.map +1 -0
  110. package/dist/confluent/tools/handlers/connect/get-connector-config-handler.js +34 -0
  111. package/dist/confluent/tools/handlers/connect/get-connector-config-handler.js.map +1 -0
  112. package/dist/confluent/tools/handlers/connect/get-connector-error-recommendations-handler.d.ts +9 -0
  113. package/dist/confluent/tools/handlers/connect/get-connector-error-recommendations-handler.d.ts.map +1 -0
  114. package/dist/confluent/tools/handlers/connect/get-connector-error-recommendations-handler.js +65 -0
  115. package/dist/confluent/tools/handlers/connect/get-connector-error-recommendations-handler.js.map +1 -0
  116. package/dist/confluent/tools/handlers/connect/get-connector-error-summary-handler.d.ts +9 -0
  117. package/dist/confluent/tools/handlers/connect/get-connector-error-summary-handler.d.ts.map +1 -0
  118. package/dist/confluent/tools/handlers/connect/get-connector-error-summary-handler.js +130 -0
  119. package/dist/confluent/tools/handlers/connect/get-connector-error-summary-handler.js.map +1 -0
  120. package/dist/confluent/tools/handlers/connect/get-connector-logs-handler.d.ts +9 -0
  121. package/dist/confluent/tools/handlers/connect/get-connector-logs-handler.d.ts.map +1 -0
  122. package/dist/confluent/tools/handlers/connect/get-connector-logs-handler.js +269 -0
  123. package/dist/confluent/tools/handlers/connect/get-connector-logs-handler.js.map +1 -0
  124. package/dist/confluent/tools/handlers/connect/get-connector-offsets-handler.d.ts +9 -0
  125. package/dist/confluent/tools/handlers/connect/get-connector-offsets-handler.d.ts.map +1 -0
  126. package/dist/confluent/tools/handlers/connect/get-connector-offsets-handler.js +34 -0
  127. package/dist/confluent/tools/handlers/connect/get-connector-offsets-handler.js.map +1 -0
  128. package/dist/confluent/tools/handlers/connect/get-connector-status-handler.d.ts +9 -0
  129. package/dist/confluent/tools/handlers/connect/get-connector-status-handler.d.ts.map +1 -0
  130. package/dist/confluent/tools/handlers/connect/get-connector-status-handler.js +43 -0
  131. package/dist/confluent/tools/handlers/connect/get-connector-status-handler.js.map +1 -0
  132. package/dist/confluent/tools/handlers/connect/get-connector-tasks-handler.d.ts +9 -0
  133. package/dist/confluent/tools/handlers/connect/get-connector-tasks-handler.d.ts.map +1 -0
  134. package/dist/confluent/tools/handlers/connect/get-connector-tasks-handler.js +34 -0
  135. package/dist/confluent/tools/handlers/connect/get-connector-tasks-handler.js.map +1 -0
  136. package/dist/confluent/tools/handlers/connect/list-connectors-handler.d.ts.map +1 -1
  137. package/dist/confluent/tools/handlers/connect/list-connectors-handler.js +1 -2
  138. package/dist/confluent/tools/handlers/connect/list-connectors-handler.js.map +1 -1
  139. package/dist/confluent/tools/handlers/connect/pause-connector-handler.d.ts +9 -0
  140. package/dist/confluent/tools/handlers/connect/pause-connector-handler.d.ts.map +1 -0
  141. package/dist/confluent/tools/handlers/connect/pause-connector-handler.js +34 -0
  142. package/dist/confluent/tools/handlers/connect/pause-connector-handler.js.map +1 -0
  143. package/dist/confluent/tools/handlers/connect/restart-connector-handler.d.ts +9 -0
  144. package/dist/confluent/tools/handlers/connect/restart-connector-handler.d.ts.map +1 -0
  145. package/dist/confluent/tools/handlers/connect/restart-connector-handler.js +34 -0
  146. package/dist/confluent/tools/handlers/connect/restart-connector-handler.js.map +1 -0
  147. package/dist/confluent/tools/handlers/connect/resume-connector-handler.d.ts +9 -0
  148. package/dist/confluent/tools/handlers/connect/resume-connector-handler.d.ts.map +1 -0
  149. package/dist/confluent/tools/handlers/connect/resume-connector-handler.js +34 -0
  150. package/dist/confluent/tools/handlers/connect/resume-connector-handler.js.map +1 -0
  151. package/dist/confluent/tools/handlers/connect/update-connector-config-handler.d.ts +9 -0
  152. package/dist/confluent/tools/handlers/connect/update-connector-config-handler.d.ts.map +1 -0
  153. package/dist/confluent/tools/handlers/connect/update-connector-config-handler.js +43 -0
  154. package/dist/confluent/tools/handlers/connect/update-connector-config-handler.js.map +1 -0
  155. package/dist/confluent/tools/handlers/diagnostics/config-help-handler.d.ts +42 -0
  156. package/dist/confluent/tools/handlers/diagnostics/config-help-handler.d.ts.map +1 -0
  157. package/dist/confluent/tools/handlers/diagnostics/config-help-handler.js +297 -0
  158. package/dist/confluent/tools/handlers/diagnostics/config-help-handler.js.map +1 -0
  159. package/dist/confluent/tools/handlers/diagnostics/describe-configured-connection-handler.d.ts +31 -0
  160. package/dist/confluent/tools/handlers/diagnostics/describe-configured-connection-handler.d.ts.map +1 -0
  161. package/dist/confluent/tools/handlers/diagnostics/describe-configured-connection-handler.js +116 -0
  162. package/dist/confluent/tools/handlers/diagnostics/describe-configured-connection-handler.js.map +1 -0
  163. package/dist/confluent/tools/handlers/diagnostics/describe-fields.d.ts +52 -0
  164. package/dist/confluent/tools/handlers/diagnostics/describe-fields.d.ts.map +1 -0
  165. package/dist/confluent/tools/handlers/diagnostics/describe-fields.js +114 -0
  166. package/dist/confluent/tools/handlers/diagnostics/describe-fields.js.map +1 -0
  167. package/dist/confluent/tools/handlers/diagnostics/explain-disabled-tools-handler.d.ts +21 -43
  168. package/dist/confluent/tools/handlers/diagnostics/explain-disabled-tools-handler.d.ts.map +1 -1
  169. package/dist/confluent/tools/handlers/diagnostics/explain-disabled-tools-handler.js +105 -78
  170. package/dist/confluent/tools/handlers/diagnostics/explain-disabled-tools-handler.js.map +1 -1
  171. package/dist/confluent/tools/handlers/diagnostics/list-configured-connections-handler.d.ts +27 -0
  172. package/dist/confluent/tools/handlers/diagnostics/list-configured-connections-handler.d.ts.map +1 -0
  173. package/dist/confluent/tools/handlers/diagnostics/list-configured-connections-handler.js +93 -0
  174. package/dist/confluent/tools/handlers/diagnostics/list-configured-connections-handler.js.map +1 -0
  175. package/dist/confluent/tools/handlers/docs/get-product-doc-page-handler.d.ts +2 -1
  176. package/dist/confluent/tools/handlers/docs/get-product-doc-page-handler.d.ts.map +1 -1
  177. package/dist/confluent/tools/handlers/docs/get-product-doc-page-handler.js +2 -1
  178. package/dist/confluent/tools/handlers/docs/get-product-doc-page-handler.js.map +1 -1
  179. package/dist/confluent/tools/handlers/docs/search-product-docs-handler.d.ts +2 -1
  180. package/dist/confluent/tools/handlers/docs/search-product-docs-handler.d.ts.map +1 -1
  181. package/dist/confluent/tools/handlers/docs/search-product-docs-handler.js +2 -1
  182. package/dist/confluent/tools/handlers/docs/search-product-docs-handler.js.map +1 -1
  183. package/dist/confluent/tools/handlers/environments/list-environments-handler.d.ts +8 -7
  184. package/dist/confluent/tools/handlers/environments/list-environments-handler.d.ts.map +1 -1
  185. package/dist/confluent/tools/handlers/environments/list-environments-handler.js +8 -7
  186. package/dist/confluent/tools/handlers/environments/list-environments-handler.js.map +1 -1
  187. package/dist/confluent/tools/handlers/environments/read-environment-handler.d.ts +2 -1
  188. package/dist/confluent/tools/handlers/environments/read-environment-handler.d.ts.map +1 -1
  189. package/dist/confluent/tools/handlers/environments/read-environment-handler.js +3 -2
  190. package/dist/confluent/tools/handlers/environments/read-environment-handler.js.map +1 -1
  191. package/dist/confluent/tools/handlers/flink/catalog/catalog-resolver.d.ts +16 -2
  192. package/dist/confluent/tools/handlers/flink/catalog/catalog-resolver.d.ts.map +1 -1
  193. package/dist/confluent/tools/handlers/flink/catalog/catalog-resolver.js +4 -4
  194. package/dist/confluent/tools/handlers/flink/catalog/catalog-resolver.js.map +1 -1
  195. package/dist/confluent/tools/handlers/flink/catalog/describe-table-handler.d.ts.map +1 -1
  196. package/dist/confluent/tools/handlers/flink/catalog/describe-table-handler.js +43 -30
  197. package/dist/confluent/tools/handlers/flink/catalog/describe-table-handler.js.map +1 -1
  198. package/dist/confluent/tools/handlers/flink/catalog/get-table-info-handler.d.ts.map +1 -1
  199. package/dist/confluent/tools/handlers/flink/catalog/get-table-info-handler.js +43 -30
  200. package/dist/confluent/tools/handlers/flink/catalog/get-table-info-handler.js.map +1 -1
  201. package/dist/confluent/tools/handlers/flink/catalog/list-catalogs-handler.d.ts.map +1 -1
  202. package/dist/confluent/tools/handlers/flink/catalog/list-catalogs-handler.js +18 -11
  203. package/dist/confluent/tools/handlers/flink/catalog/list-catalogs-handler.js.map +1 -1
  204. package/dist/confluent/tools/handlers/flink/catalog/list-databases-handler.d.ts.map +1 -1
  205. package/dist/confluent/tools/handlers/flink/catalog/list-databases-handler.js +23 -16
  206. package/dist/confluent/tools/handlers/flink/catalog/list-databases-handler.js.map +1 -1
  207. package/dist/confluent/tools/handlers/flink/catalog/list-tables-handler.d.ts.map +1 -1
  208. package/dist/confluent/tools/handlers/flink/catalog/list-tables-handler.js +23 -16
  209. package/dist/confluent/tools/handlers/flink/catalog/list-tables-handler.js.map +1 -1
  210. package/dist/confluent/tools/handlers/flink/create-flink-statement-handler.d.ts.map +1 -1
  211. package/dist/confluent/tools/handlers/flink/create-flink-statement-handler.js +27 -22
  212. package/dist/confluent/tools/handlers/flink/create-flink-statement-handler.js.map +1 -1
  213. package/dist/confluent/tools/handlers/flink/delete-flink-statement-handler.d.ts.map +1 -1
  214. package/dist/confluent/tools/handlers/flink/delete-flink-statement-handler.js +20 -12
  215. package/dist/confluent/tools/handlers/flink/delete-flink-statement-handler.js.map +1 -1
  216. package/dist/confluent/tools/handlers/flink/diagnostics/check-health-handler.d.ts.map +1 -1
  217. package/dist/confluent/tools/handlers/flink/diagnostics/check-health-handler.js +20 -12
  218. package/dist/confluent/tools/handlers/flink/diagnostics/check-health-handler.js.map +1 -1
  219. package/dist/confluent/tools/handlers/flink/diagnostics/detect-issues-handler.d.ts.map +1 -1
  220. package/dist/confluent/tools/handlers/flink/diagnostics/detect-issues-handler.js +20 -18
  221. package/dist/confluent/tools/handlers/flink/diagnostics/detect-issues-handler.js.map +1 -1
  222. package/dist/confluent/tools/handlers/flink/diagnostics/query-profiler-handler.d.ts.map +1 -1
  223. package/dist/confluent/tools/handlers/flink/diagnostics/query-profiler-handler.js +24 -22
  224. package/dist/confluent/tools/handlers/flink/diagnostics/query-profiler-handler.js.map +1 -1
  225. package/dist/confluent/tools/handlers/flink/flink-sql-helper.d.ts +15 -1
  226. package/dist/confluent/tools/handlers/flink/flink-sql-helper.d.ts.map +1 -1
  227. package/dist/confluent/tools/handlers/flink/flink-sql-helper.js +133 -57
  228. package/dist/confluent/tools/handlers/flink/flink-sql-helper.js.map +1 -1
  229. package/dist/confluent/tools/handlers/flink/flink-tool-handler.d.ts +29 -7
  230. package/dist/confluent/tools/handlers/flink/flink-tool-handler.d.ts.map +1 -1
  231. package/dist/confluent/tools/handlers/flink/flink-tool-handler.js +38 -8
  232. package/dist/confluent/tools/handlers/flink/flink-tool-handler.js.map +1 -1
  233. package/dist/confluent/tools/handlers/flink/get-flink-exceptions-handler.d.ts.map +1 -1
  234. package/dist/confluent/tools/handlers/flink/get-flink-exceptions-handler.js +20 -12
  235. package/dist/confluent/tools/handlers/flink/get-flink-exceptions-handler.js.map +1 -1
  236. package/dist/confluent/tools/handlers/flink/{read-flink-statement-handler.d.ts → get-flink-statement-results-handler.d.ts} +2 -2
  237. package/dist/confluent/tools/handlers/flink/get-flink-statement-results-handler.d.ts.map +1 -0
  238. package/dist/confluent/tools/handlers/flink/{read-flink-statement-handler.js → get-flink-statement-results-handler.js} +30 -22
  239. package/dist/confluent/tools/handlers/flink/get-flink-statement-results-handler.js.map +1 -0
  240. package/dist/confluent/tools/handlers/flink/list-compute-pools-handler.d.ts +10 -0
  241. package/dist/confluent/tools/handlers/flink/list-compute-pools-handler.d.ts.map +1 -0
  242. package/dist/confluent/tools/handlers/flink/list-compute-pools-handler.js +118 -0
  243. package/dist/confluent/tools/handlers/flink/list-compute-pools-handler.js.map +1 -0
  244. package/dist/confluent/tools/handlers/flink/list-flink-statements-handler.d.ts.map +1 -1
  245. package/dist/confluent/tools/handlers/flink/list-flink-statements-handler.js +19 -18
  246. package/dist/confluent/tools/handlers/flink/list-flink-statements-handler.js.map +1 -1
  247. package/dist/confluent/tools/handlers/kafka/alter-topic-config.d.ts +2 -1
  248. package/dist/confluent/tools/handlers/kafka/alter-topic-config.d.ts.map +1 -1
  249. package/dist/confluent/tools/handlers/kafka/alter-topic-config.js +3 -2
  250. package/dist/confluent/tools/handlers/kafka/alter-topic-config.js.map +1 -1
  251. package/dist/confluent/tools/handlers/kafka/consume-kafka-messages-handler.d.ts +223 -28
  252. package/dist/confluent/tools/handlers/kafka/consume-kafka-messages-handler.d.ts.map +1 -1
  253. package/dist/confluent/tools/handlers/kafka/consume-kafka-messages-handler.js +832 -68
  254. package/dist/confluent/tools/handlers/kafka/consume-kafka-messages-handler.js.map +1 -1
  255. package/dist/confluent/tools/handlers/kafka/consumer-group-enums.d.ts +48 -0
  256. package/dist/confluent/tools/handlers/kafka/consumer-group-enums.d.ts.map +1 -0
  257. package/dist/confluent/tools/handlers/kafka/consumer-group-enums.js +84 -0
  258. package/dist/confluent/tools/handlers/kafka/consumer-group-enums.js.map +1 -0
  259. package/dist/confluent/tools/handlers/kafka/consumer-group-helpers.d.ts +54 -0
  260. package/dist/confluent/tools/handlers/kafka/consumer-group-helpers.d.ts.map +1 -0
  261. package/dist/confluent/tools/handlers/kafka/consumer-group-helpers.js +89 -0
  262. package/dist/confluent/tools/handlers/kafka/consumer-group-helpers.js.map +1 -0
  263. package/dist/confluent/tools/handlers/kafka/create-topics-handler.d.ts +2 -1
  264. package/dist/confluent/tools/handlers/kafka/create-topics-handler.d.ts.map +1 -1
  265. package/dist/confluent/tools/handlers/kafka/create-topics-handler.js +14 -4
  266. package/dist/confluent/tools/handlers/kafka/create-topics-handler.js.map +1 -1
  267. package/dist/confluent/tools/handlers/kafka/delete-topics-handler.d.ts +2 -1
  268. package/dist/confluent/tools/handlers/kafka/delete-topics-handler.d.ts.map +1 -1
  269. package/dist/confluent/tools/handlers/kafka/delete-topics-handler.js +3 -2
  270. package/dist/confluent/tools/handlers/kafka/delete-topics-handler.js.map +1 -1
  271. package/dist/confluent/tools/handlers/kafka/describe-consumer-group-handler.d.ts +64 -0
  272. package/dist/confluent/tools/handlers/kafka/describe-consumer-group-handler.d.ts.map +1 -0
  273. package/dist/confluent/tools/handlers/kafka/describe-consumer-group-handler.js +140 -0
  274. package/dist/confluent/tools/handlers/kafka/describe-consumer-group-handler.js.map +1 -0
  275. package/dist/confluent/tools/handlers/kafka/get-consumer-group-lag-handler.d.ts +73 -0
  276. package/dist/confluent/tools/handlers/kafka/get-consumer-group-lag-handler.d.ts.map +1 -0
  277. package/dist/confluent/tools/handlers/kafka/get-consumer-group-lag-handler.js +337 -0
  278. package/dist/confluent/tools/handlers/kafka/get-consumer-group-lag-handler.js.map +1 -0
  279. package/dist/confluent/tools/handlers/kafka/get-partition-offsets-handler.d.ts +43 -0
  280. package/dist/confluent/tools/handlers/kafka/get-partition-offsets-handler.d.ts.map +1 -0
  281. package/dist/confluent/tools/handlers/kafka/get-partition-offsets-handler.js +123 -0
  282. package/dist/confluent/tools/handlers/kafka/get-partition-offsets-handler.js.map +1 -0
  283. package/dist/confluent/tools/handlers/kafka/get-topic-config.d.ts +2 -1
  284. package/dist/confluent/tools/handlers/kafka/get-topic-config.d.ts.map +1 -1
  285. package/dist/confluent/tools/handlers/kafka/get-topic-config.js +3 -2
  286. package/dist/confluent/tools/handlers/kafka/get-topic-config.js.map +1 -1
  287. package/dist/confluent/tools/handlers/kafka/list-consumer-groups-handler.d.ts +45 -0
  288. package/dist/confluent/tools/handlers/kafka/list-consumer-groups-handler.d.ts.map +1 -0
  289. package/dist/confluent/tools/handlers/kafka/list-consumer-groups-handler.js +86 -0
  290. package/dist/confluent/tools/handlers/kafka/list-consumer-groups-handler.js.map +1 -0
  291. package/dist/confluent/tools/handlers/kafka/list-topics-handler.d.ts +2 -1
  292. package/dist/confluent/tools/handlers/kafka/list-topics-handler.d.ts.map +1 -1
  293. package/dist/confluent/tools/handlers/kafka/list-topics-handler.js +3 -2
  294. package/dist/confluent/tools/handlers/kafka/list-topics-handler.js.map +1 -1
  295. package/dist/confluent/tools/handlers/kafka/partition-watermarks.d.ts +54 -0
  296. package/dist/confluent/tools/handlers/kafka/partition-watermarks.d.ts.map +1 -0
  297. package/dist/confluent/tools/handlers/kafka/partition-watermarks.js +56 -0
  298. package/dist/confluent/tools/handlers/kafka/partition-watermarks.js.map +1 -0
  299. package/dist/confluent/tools/handlers/kafka/produce-kafka-message-handler.d.ts +13 -5
  300. package/dist/confluent/tools/handlers/kafka/produce-kafka-message-handler.d.ts.map +1 -1
  301. package/dist/confluent/tools/handlers/kafka/produce-kafka-message-handler.js +145 -47
  302. package/dist/confluent/tools/handlers/kafka/produce-kafka-message-handler.js.map +1 -1
  303. package/dist/confluent/tools/handlers/kafka/topic-metadata.d.ts +18 -0
  304. package/dist/confluent/tools/handlers/kafka/topic-metadata.d.ts.map +1 -0
  305. package/dist/confluent/tools/handlers/kafka/topic-metadata.js +18 -0
  306. package/dist/confluent/tools/handlers/kafka/topic-metadata.js.map +1 -0
  307. package/dist/confluent/tools/handlers/metrics/list-metrics-handler.d.ts +3 -3
  308. package/dist/confluent/tools/handlers/metrics/list-metrics-handler.d.ts.map +1 -1
  309. package/dist/confluent/tools/handlers/metrics/list-metrics-handler.js +5 -4
  310. package/dist/confluent/tools/handlers/metrics/list-metrics-handler.js.map +1 -1
  311. package/dist/confluent/tools/handlers/metrics/query-metrics-handler.d.ts +3 -3
  312. package/dist/confluent/tools/handlers/metrics/query-metrics-handler.d.ts.map +1 -1
  313. package/dist/confluent/tools/handlers/metrics/query-metrics-handler.js +8 -5
  314. package/dist/confluent/tools/handlers/metrics/query-metrics-handler.js.map +1 -1
  315. package/dist/confluent/tools/handlers/organizations/list-organizations-handler.d.ts +2 -1
  316. package/dist/confluent/tools/handlers/organizations/list-organizations-handler.d.ts.map +1 -1
  317. package/dist/confluent/tools/handlers/organizations/list-organizations-handler.js +4 -2
  318. package/dist/confluent/tools/handlers/organizations/list-organizations-handler.js.map +1 -1
  319. package/dist/confluent/tools/handlers/schema/create-schema-handler.d.ts +10 -0
  320. package/dist/confluent/tools/handlers/schema/create-schema-handler.d.ts.map +1 -0
  321. package/dist/confluent/tools/handlers/schema/create-schema-handler.js +76 -0
  322. package/dist/confluent/tools/handlers/schema/create-schema-handler.js.map +1 -0
  323. package/dist/confluent/tools/handlers/schema/delete-schema-handler.d.ts +2 -1
  324. package/dist/confluent/tools/handlers/schema/delete-schema-handler.d.ts.map +1 -1
  325. package/dist/confluent/tools/handlers/schema/delete-schema-handler.js +3 -2
  326. package/dist/confluent/tools/handlers/schema/delete-schema-handler.js.map +1 -1
  327. package/dist/confluent/tools/handlers/schema/list-schemas-handler.d.ts +2 -1
  328. package/dist/confluent/tools/handlers/schema/list-schemas-handler.d.ts.map +1 -1
  329. package/dist/confluent/tools/handlers/schema/list-schemas-handler.js +3 -2
  330. package/dist/confluent/tools/handlers/schema/list-schemas-handler.js.map +1 -1
  331. package/dist/confluent/tools/handlers/search/search-topic-by-tag-handler.d.ts +3 -3
  332. package/dist/confluent/tools/handlers/search/search-topic-by-tag-handler.d.ts.map +1 -1
  333. package/dist/confluent/tools/handlers/search/search-topic-by-tag-handler.js +11 -6
  334. package/dist/confluent/tools/handlers/search/search-topic-by-tag-handler.js.map +1 -1
  335. package/dist/confluent/tools/handlers/search/search-topics-by-name-handler.d.ts +3 -3
  336. package/dist/confluent/tools/handlers/search/search-topics-by-name-handler.d.ts.map +1 -1
  337. package/dist/confluent/tools/handlers/search/search-topics-by-name-handler.js +11 -6
  338. package/dist/confluent/tools/handlers/search/search-topics-by-name-handler.js.map +1 -1
  339. package/dist/confluent/tools/handlers/tableflow/catalog/create-tableflow-catalog-integration-handler.d.ts.map +1 -1
  340. package/dist/confluent/tools/handlers/tableflow/catalog/create-tableflow-catalog-integration-handler.js +2 -2
  341. package/dist/confluent/tools/handlers/tableflow/catalog/create-tableflow-catalog-integration-handler.js.map +1 -1
  342. package/dist/confluent/tools/handlers/tableflow/catalog/delete-tableflow-catalog-integration-handler.d.ts.map +1 -1
  343. package/dist/confluent/tools/handlers/tableflow/catalog/delete-tableflow-catalog-integration-handler.js +2 -2
  344. package/dist/confluent/tools/handlers/tableflow/catalog/delete-tableflow-catalog-integration-handler.js.map +1 -1
  345. package/dist/confluent/tools/handlers/tableflow/catalog/list-tableflow-catalog-integrations-handler.d.ts.map +1 -1
  346. package/dist/confluent/tools/handlers/tableflow/catalog/list-tableflow-catalog-integrations-handler.js +2 -2
  347. package/dist/confluent/tools/handlers/tableflow/catalog/list-tableflow-catalog-integrations-handler.js.map +1 -1
  348. package/dist/confluent/tools/handlers/tableflow/catalog/read-tableflow-catalog-integration-handler.d.ts.map +1 -1
  349. package/dist/confluent/tools/handlers/tableflow/catalog/read-tableflow-catalog-integration-handler.js +2 -2
  350. package/dist/confluent/tools/handlers/tableflow/catalog/read-tableflow-catalog-integration-handler.js.map +1 -1
  351. package/dist/confluent/tools/handlers/tableflow/catalog/update-tableflow-catalog-integration-handler.d.ts.map +1 -1
  352. package/dist/confluent/tools/handlers/tableflow/catalog/update-tableflow-catalog-integration-handler.js +1 -1
  353. package/dist/confluent/tools/handlers/tableflow/catalog/update-tableflow-catalog-integration-handler.js.map +1 -1
  354. package/dist/confluent/tools/handlers/tableflow/list-tableflow-regions-handler.d.ts.map +1 -1
  355. package/dist/confluent/tools/handlers/tableflow/list-tableflow-regions-handler.js +2 -3
  356. package/dist/confluent/tools/handlers/tableflow/list-tableflow-regions-handler.js.map +1 -1
  357. package/dist/confluent/tools/handlers/tableflow/tableflow-tool-handler.d.ts +17 -11
  358. package/dist/confluent/tools/handlers/tableflow/tableflow-tool-handler.d.ts.map +1 -1
  359. package/dist/confluent/tools/handlers/tableflow/tableflow-tool-handler.js +19 -15
  360. package/dist/confluent/tools/handlers/tableflow/tableflow-tool-handler.js.map +1 -1
  361. package/dist/confluent/tools/handlers/tableflow/topic/create-tableflow-topic-handler.d.ts.map +1 -1
  362. package/dist/confluent/tools/handlers/tableflow/topic/create-tableflow-topic-handler.js +2 -2
  363. package/dist/confluent/tools/handlers/tableflow/topic/create-tableflow-topic-handler.js.map +1 -1
  364. package/dist/confluent/tools/handlers/tableflow/topic/delete-tableflow-topic-handler.d.ts.map +1 -1
  365. package/dist/confluent/tools/handlers/tableflow/topic/delete-tableflow-topic-handler.js +2 -2
  366. package/dist/confluent/tools/handlers/tableflow/topic/delete-tableflow-topic-handler.js.map +1 -1
  367. package/dist/confluent/tools/handlers/tableflow/topic/list-tableflow-topics-handler.d.ts.map +1 -1
  368. package/dist/confluent/tools/handlers/tableflow/topic/list-tableflow-topics-handler.js +2 -2
  369. package/dist/confluent/tools/handlers/tableflow/topic/list-tableflow-topics-handler.js.map +1 -1
  370. package/dist/confluent/tools/handlers/tableflow/topic/read-tableflow-topic-handler.d.ts.map +1 -1
  371. package/dist/confluent/tools/handlers/tableflow/topic/read-tableflow-topic-handler.js +2 -2
  372. package/dist/confluent/tools/handlers/tableflow/topic/read-tableflow-topic-handler.js.map +1 -1
  373. package/dist/confluent/tools/handlers/tableflow/topic/update-tableflow-topic-handler.d.ts.map +1 -1
  374. package/dist/confluent/tools/handlers/tableflow/topic/update-tableflow-topic-handler.js +1 -1
  375. package/dist/confluent/tools/handlers/tableflow/topic/update-tableflow-topic-handler.js.map +1 -1
  376. package/dist/confluent/tools/tool-availability.d.ts +84 -36
  377. package/dist/confluent/tools/tool-availability.d.ts.map +1 -1
  378. package/dist/confluent/tools/tool-availability.js +106 -65
  379. package/dist/confluent/tools/tool-availability.js.map +1 -1
  380. package/dist/confluent/tools/tool-metadata-handler.d.ts +51 -0
  381. package/dist/confluent/tools/tool-metadata-handler.d.ts.map +1 -0
  382. package/dist/confluent/tools/tool-metadata-handler.js +61 -0
  383. package/dist/confluent/tools/tool-metadata-handler.js.map +1 -0
  384. package/dist/confluent/tools/tool-name.d.ts +22 -3
  385. package/dist/confluent/tools/tool-name.d.ts.map +1 -1
  386. package/dist/confluent/tools/tool-name.js +21 -2
  387. package/dist/confluent/tools/tool-name.js.map +1 -1
  388. package/dist/confluent/tools/tool-registry.d.ts +1 -2
  389. package/dist/confluent/tools/tool-registry.d.ts.map +1 -1
  390. package/dist/confluent/tools/tool-registry.js +60 -7
  391. package/dist/confluent/tools/tool-registry.js.map +1 -1
  392. package/dist/env-schema.d.ts +1 -1
  393. package/dist/env-schema.d.ts.map +1 -1
  394. package/dist/env-schema.js +10 -12
  395. package/dist/env-schema.js.map +1 -1
  396. package/dist/env.d.ts +1 -1
  397. package/dist/index.d.ts +54 -64
  398. package/dist/index.d.ts.map +1 -1
  399. package/dist/index.js +78 -301
  400. package/dist/index.js.map +1 -1
  401. package/dist/lazy.d.ts +4 -2
  402. package/dist/lazy.d.ts.map +1 -1
  403. package/dist/lazy.js +27 -12
  404. package/dist/lazy.js.map +1 -1
  405. package/dist/mcp/server.d.ts.map +1 -1
  406. package/dist/mcp/server.js +9 -5
  407. package/dist/mcp/server.js.map +1 -1
  408. package/dist/preflight.d.ts +33 -0
  409. package/dist/preflight.d.ts.map +1 -0
  410. package/dist/preflight.js +68 -0
  411. package/dist/preflight.js.map +1 -0
  412. package/dist/server-main.d.ts +98 -0
  413. package/dist/server-main.d.ts.map +1 -0
  414. package/dist/server-main.js +383 -0
  415. package/dist/server-main.js.map +1 -0
  416. package/dist/server-runtime.d.ts +20 -5
  417. package/dist/server-runtime.d.ts.map +1 -1
  418. package/dist/server-runtime.js +45 -28
  419. package/dist/server-runtime.js.map +1 -1
  420. package/dist/utils/quote-join-ids.d.ts +9 -0
  421. package/dist/utils/quote-join-ids.d.ts.map +1 -0
  422. package/dist/utils/quote-join-ids.js +11 -0
  423. package/dist/utils/quote-join-ids.js.map +1 -0
  424. package/package.json +33 -34
  425. package/dist/confluent/tools/handlers/connect/read-connectors-handler.d.ts.map +0 -1
  426. package/dist/confluent/tools/handlers/connect/read-connectors-handler.js +0 -54
  427. package/dist/confluent/tools/handlers/connect/read-connectors-handler.js.map +0 -1
  428. package/dist/confluent/tools/handlers/flink/read-flink-statement-handler.d.ts.map +0 -1
  429. package/dist/confluent/tools/handlers/flink/read-flink-statement-handler.js.map +0 -1
@@ -1,29 +1,127 @@
1
- import { SerdeType } from "@confluentinc/schemaregistry";
2
- import { deserializeMessage, getLatestSchemaIfExists, } from "../../../../confluent/schema-registry-helper.js";
3
- import { BaseToolHandler, READ_ONLY, } from "../../../../confluent/tools/base-tools.js";
4
- import { formatKafkaError, resolveKafkaClusterArgs, } from "../../../../confluent/tools/cluster-arg-resolvers.js";
5
- import { kafkaBootstrapOrOAuth } from "../../../../confluent/tools/connection-predicates.js";
1
+ import { KEY_SCHEMA_ID_HEADER, SerdeType, VALUE_SCHEMA_ID_HEADER, } from "@confluentinc/schemaregistry";
2
+ import { nodeCrypto } from "../../../../confluent/node-deps.js";
3
+ import * as schemaRegistryHelper from "../../../../confluent/schema-registry-helper.js";
4
+ import { BaseToolHandler, READ_ONLY, ToolCategory, } from "../../../../confluent/tools/base-tools.js";
5
+ import { disposeIfOAuth, formatKafkaError, resolveKafkaClusterArgs, } from "../../../../confluent/tools/cluster-arg-resolvers.js";
6
+ import { hasSchemaRegistryOrOAuth, kafkaBootstrapOrOAuth, } from "../../../../confluent/tools/connection-predicates.js";
7
+ import { createWatermarkCache, } from "../../../../confluent/tools/handlers/kafka/partition-watermarks.js";
8
+ import { normalizeFetchTopicMetadataResponse } from "../../../../confluent/tools/handlers/kafka/topic-metadata.js";
6
9
  import { ToolName } from "../../../../confluent/tools/tool-name.js";
7
10
  import { logger } from "../../../../logger.js";
8
11
  import { z } from "zod";
9
- const messageOptions = z.object({
10
- useSchemaRegistry: z
12
+ const schemaRegistryOptions = z
13
+ .object({
14
+ disableSchemaRegistry: z
11
15
  .boolean()
12
16
  .optional()
13
17
  .default(false)
14
- .describe("Whether to use schema registry for deserialization. If false, messages will be returned as raw."),
18
+ .describe("Set to true to skip Schema Registry decoding and return raw UTF-8 bytes. " +
19
+ "When false (the default), messages are auto-decoded via the registered " +
20
+ "AVRO / JSON / PROTOBUF schema if the connection has Schema Registry " +
21
+ "configured and a schema exists for the subject; otherwise left as raw bytes."),
15
22
  subject: z
16
23
  .string()
17
24
  .optional()
18
- .describe("Schema registry subject. Defaults to 'topicName-value' or 'topicName-key'."),
25
+ .describe("Schema registry subject. Defaults to '<topic>-value' or '<topic>-key'."),
26
+ })
27
+ // The optional() + default() wrapping lives here on the shared
28
+ // schema so `valueFormat` and `keyFormat` declarations downstream
29
+ // are bare references — one place to change if the omit-by-default
30
+ // contract ever evolves.
31
+ .optional()
32
+ .default({ disableSchemaRegistry: false });
33
+ /**
34
+ * Per-entry "where to start consuming" tagged union. Collapses the prior
35
+ * `offset` and `timestamp` peer fields plus the top-level `offsetReset`
36
+ * knob into a single per-topic position control with five arms:
37
+ *
38
+ * - `"earliest"` — begin at the partition low watermark (entire retained
39
+ * history). This is the default when `start` is omitted.
40
+ * - `"latest"` — begin at the partition high watermark (only
41
+ * newly-produced messages).
42
+ * - `{ offset: "..." }` — begin at an absolute partition offset
43
+ * (digit-only string; requires `partition`).
44
+ * - `{ timestamp: ... }` — begin at the broker-resolved offset for the
45
+ * supplied time (ISO 8601 preferred; ms-since-epoch also accepted).
46
+ * - `{ tail: N }` — begin at `max(low, high - N)` on the partition, so
47
+ * the consumer returns up to the last N already-written messages
48
+ * without waiting for new traffic (requires `partition`).
49
+ *
50
+ * The object arms are `strictObject` so `{offset, timestamp}`,
51
+ * `{tail, offset}`, etc. together are a union miss rather than a silent
52
+ * strip of one key.
53
+ */
54
+ const startOption = z
55
+ .union([
56
+ z.literal("earliest"),
57
+ z.literal("latest"),
58
+ z.strictObject({
59
+ offset: z
60
+ .string()
61
+ .regex(/^\d+$/, "offset must be a non-negative integer (digit-only string)")
62
+ .describe("Absolute starting offset within the partition, as a digit-only string."),
63
+ }),
64
+ z.strictObject({
65
+ timestamp: z
66
+ .union([
67
+ z.iso.datetime({ offset: true }),
68
+ z.number().int().nonnegative(),
69
+ ])
70
+ .describe('ISO 8601 timestamp (e.g. "2026-05-14T17:00:00Z" or ' +
71
+ '"2026-05-14T13:00:00-04:00") or ms-since-epoch number. The ' +
72
+ "broker resolves this to a per-partition offset."),
73
+ }),
74
+ z.strictObject({
75
+ tail: z
76
+ .number()
77
+ .int()
78
+ .positive()
79
+ .describe("Consume the most recent N already-written messages from this partition. " +
80
+ "REQUIRES `partition` to be set on the same topic entry — there " +
81
+ "is no implicit partition default; consumption is " +
82
+ "single-partition only. N must be a positive integer. The " +
83
+ "consumer seeks to max(lowWatermark, highWatermark - N) and " +
84
+ "returns immediately with whatever is already there — it does " +
85
+ "not block waiting for new writes."),
86
+ }),
87
+ ])
88
+ .describe("Where to begin consuming this topic. Use 'earliest' for the entire " +
89
+ "retained history; 'latest' for " +
90
+ "newly-produced messages only; {offset: 'N'} for an absolute " +
91
+ "partition offset (requires `partition`); {timestamp: '...'} " +
92
+ "to seek to the broker-resolved offset for a point in time; or " +
93
+ "{tail: N} for the last N already-written messages on the " +
94
+ "partition (requires `partition`, returns without waiting for new " +
95
+ "traffic).");
96
+ /**
97
+ * Per-topic consume options. `name` is the only required field. `partition`
98
+ * optionally restricts to one partition; `start` optionally picks the
99
+ * starting position. Both are independent — `partition` answers WHICH
100
+ * partition(s), `start` answers WHERE in them to begin.
101
+ */
102
+ const topicConsumeOptions = z.object({
103
+ name: z.string().min(1).describe("Kafka topic name to consume from."),
104
+ partition: z
105
+ .number()
106
+ .int()
107
+ .nonnegative()
108
+ .optional()
109
+ .describe("Optional. Restrict consumption to this partition (0-indexed). " +
110
+ "Other partitions in the topic are paused after assignment. " +
111
+ "Omit to consume all partitions of the topic."),
112
+ start: startOption.optional().default("earliest"),
19
113
  });
20
- const valueOptions = z.object({}).extend(messageOptions.shape);
21
- const keyOptions = z.object({}).extend(messageOptions.shape);
22
114
  export const consumeKafkaMessagesArgs = z.object({
23
- topicNames: z
24
- .array(z.string())
115
+ topics: z
116
+ .array(topicConsumeOptions)
25
117
  .nonempty()
26
- .describe("Names of the Kafka topics to consume from."),
118
+ .describe("Topics to consume from. Each entry is an object with at least " +
119
+ "`name` (the Kafka topic name). Example simple call: " +
120
+ '[{name: "orders"}]. The `partition` and `start` fields let ' +
121
+ "callers restrict to a specific partition and/or pick where in " +
122
+ "the topic to begin consuming (e.g. " +
123
+ '[{name: "orders", partition: 0, start: {offset: "42"}}], ' +
124
+ '[{name: "orders", start: {timestamp: "2026-05-14T17:00:00Z"}}]).'),
27
125
  maxMessages: z
28
126
  .number()
29
127
  .int()
@@ -38,8 +136,10 @@ export const consumeKafkaMessagesArgs = z.object({
38
136
  .optional()
39
137
  .default(10000)
40
138
  .describe("Maximum time in milliseconds to wait for messages before stopping."),
41
- value: valueOptions,
42
- key: keyOptions.optional(),
139
+ valueFormat: schemaRegistryOptions.describe("VALUE format. Default: auto-decode via Schema Registry when configured. " +
140
+ "Set `disableSchemaRegistry: true` for raw UTF-8."),
141
+ keyFormat: schemaRegistryOptions.describe("KEY format. Default: auto-decode via Schema Registry when configured. " +
142
+ "Set `disableSchemaRegistry: true` for raw UTF-8."),
43
143
  cluster_id: z
44
144
  .string()
45
145
  .optional()
@@ -49,6 +149,526 @@ export const consumeKafkaMessagesArgs = z.object({
49
149
  .optional()
50
150
  .describe("Confluent Cloud environment ID (env-...) that owns the cluster. Discover via list-environments."),
51
151
  });
152
+ /**
153
+ * Format a Kafka message's `message.timestamp` (a string of ms-since-epoch
154
+ * per kafkajs) into an ISO 8601 UTC string. Returns `"(no timestamp)"` for
155
+ * undefined or Kafka's `-1` sentinel (message format pre-0.10.0 or
156
+ * timestamp unset by the producer). Exported for direct unit-test coverage
157
+ * of the four branches plus the empty-string edge case (`Number("")` is
158
+ * `0`, which is finite, so an empty timestamp string formats as epoch).
159
+ */
160
+ export function formatMessageTimestamp(ts) {
161
+ if (ts === undefined || ts === "-1")
162
+ return "(no timestamp)";
163
+ const ms = Number(ts);
164
+ if (!Number.isFinite(ms))
165
+ return ts;
166
+ return new Date(ms).toISOString();
167
+ }
168
+ const SCHEMA_ID_HEADER_KEYS = new Set([
169
+ VALUE_SCHEMA_ID_HEADER,
170
+ KEY_SCHEMA_ID_HEADER,
171
+ ]);
172
+ /**
173
+ * Render a record header for the echoed consume response, preserving
174
+ * multiplicity: a repeated-key header (surfaced by the client as an array)
175
+ * maps element-wise to a `string[]`, while a single-occurrence header stays a
176
+ * scalar `string`. Joining the array via `Array.prototype.toString` would
177
+ * collapse `["a", "b"]` to the lossy `"a,b"` — indistinguishable from a
178
+ * single value literally containing a comma (#597).
179
+ */
180
+ function echoHeaderValue(key, value) {
181
+ if (Array.isArray(value)) {
182
+ return value.map((element) => echoSingleHeaderValue(key, element));
183
+ }
184
+ return echoSingleHeaderValue(key, value);
185
+ }
186
+ /**
187
+ * Stringify one header occurrence. The schema-id headers
188
+ * (__value_schema_id / __key_schema_id) carry the schema GUID as raw bytes;
189
+ * decode them to the canonical GUID string so callers see the same schema
190
+ * identifier the CCloud UI and VS Code extension surface. Every other
191
+ * value — and any schema-id header whose bytes don't decode to a GUID — is
192
+ * stringified as-is.
193
+ */
194
+ function echoSingleHeaderValue(key, value) {
195
+ if (SCHEMA_ID_HEADER_KEYS.has(key) && Buffer.isBuffer(value)) {
196
+ const guid = schemaRegistryHelper.decodeSchemaGuidHeader(value);
197
+ if (guid !== null) {
198
+ return guid;
199
+ }
200
+ }
201
+ return value?.toString() || "";
202
+ }
203
+ /**
204
+ * Collapse the parsed `start` union into the internal tagged form.
205
+ * ISO 8601 timestamps normalize to ms-since-epoch at this boundary so
206
+ * downstream code only sees numbers. Exported so the timestamp-arm's
207
+ * string-vs-number branch can be unit-tested directly (the handler-level
208
+ * tests only exercise the string path).
209
+ */
210
+ export function normalizeStart(start) {
211
+ if (start === "earliest")
212
+ return { kind: "earliest" };
213
+ if (start === "latest")
214
+ return { kind: "latest" };
215
+ if ("offset" in start)
216
+ return { kind: "offset", value: start.offset };
217
+ if ("tail" in start)
218
+ return { kind: "tail", count: start.tail };
219
+ return {
220
+ kind: "timestamp",
221
+ ms: typeof start.timestamp === "number"
222
+ ? start.timestamp
223
+ : Date.parse(start.timestamp),
224
+ };
225
+ }
226
+ /**
227
+ * Normalize the parsed tool args into a uniform list of per-topic targets.
228
+ */
229
+ function normalizeTopicTargets(parsed) {
230
+ return parsed.topics.map((entry) => ({
231
+ name: entry.name,
232
+ partition: entry.partition,
233
+ start: normalizeStart(entry.start),
234
+ }));
235
+ }
236
+ /**
237
+ * Pick the consumer's `auto.offset.reset` value from the call. The
238
+ * consumer-wide setting governs any partition that doesn't get an
239
+ * explicit seek — so we want to align it with the call's intent and
240
+ * avoid emitting watermark seeks unnecessarily.
241
+ *
242
+ * Rule: if every direction-only entry (`start: "earliest"` or
243
+ * `start: "latest"`) agrees on `"earliest"`, use `"earliest"`; otherwise
244
+ * use `"latest"`. The choice of `"latest"` as the mixed-direction
245
+ * tiebreaker matches librdkafka's own `auto.offset.reset` default and
246
+ * keeps the explicit-seek work localized to the `"earliest"` minority
247
+ * (which gets per-partition low-watermark seeks during preflight); the
248
+ * `"latest"` majority inherits the consumer-wide default and needs no
249
+ * extra admin round-trips.
250
+ */
251
+ function deriveConsumerOffsetReset(targets) {
252
+ const directions = targets
253
+ .map((t) => t.start.kind === "earliest" || t.start.kind === "latest"
254
+ ? t.start.kind
255
+ : null)
256
+ .filter((d) => d !== null);
257
+ if (directions.length > 0 && directions.every((d) => d === "earliest")) {
258
+ return "earliest";
259
+ }
260
+ return "latest";
261
+ }
262
+ /**
263
+ * Returns true when any target requires the admin pre-flight + seek/pause
264
+ * dance. Bare-name-only calls (no partition restrictions, no explicit
265
+ * offset/timestamp/tail seeks, and a `start` whose direction matches the
266
+ * consumer's chosen reset) skip the whole dance — the consumer's
267
+ * `auto.offset.reset` handles them naturally.
268
+ */
269
+ function planNeedsPreflight(targets, consumerOffsetReset) {
270
+ return targets.some((t) => t.partition !== undefined ||
271
+ t.start.kind === "offset" ||
272
+ t.start.kind === "timestamp" ||
273
+ t.start.kind === "tail" ||
274
+ (t.start.kind === "earliest" && consumerOffsetReset === "latest"));
275
+ }
276
+ /**
277
+ * Reject any target whose `start` arm is partition-scoped but supplies
278
+ * no `partition`. Both `{offset}` and `{tail}` need partition context
279
+ * (different partitions have different offset spaces and different
280
+ * message counts), so picking a default is footgun-prone; rejecting at
281
+ * the boundary is louder.
282
+ */
283
+ function guardScopedStartRequiresPartition(targets) {
284
+ for (const t of targets) {
285
+ if (t.start.kind === "offset" && t.partition === undefined) {
286
+ throw new Error(`Topic "${t.name}" has an explicit offset (${t.start.value}) but no partition. ` +
287
+ `Absolute offsets are partition-scoped — different partitions have different offset spaces. ` +
288
+ `Either also supply a partition for this entry, or use a timestamp (which resolves per-partition).`);
289
+ }
290
+ if (t.start.kind === "tail" && t.partition === undefined) {
291
+ throw new Error(`Topic "${t.name}" requested tail of ${t.start.count} messages but no partition. ` +
292
+ `Tail is partition-scoped — different partitions have different message counts, and ` +
293
+ `cross-partition freshness ordering is not defined for this tool. ` +
294
+ `Supply a partition for this entry.`);
295
+ }
296
+ }
297
+ }
298
+ /**
299
+ * Group targets by topic name preserving entry order. Used downstream
300
+ * for both the all-or-nothing partition-mode check and the keep-set
301
+ * derivation; computing it once means subsequent iterations stay O(N).
302
+ */
303
+ /**
304
+ * Fetch partition counts for the supplied topics via
305
+ * `admin.fetchTopicMetadata`. Throws if any requested topic returned no
306
+ * metadata (typically: the topic doesn't exist on this cluster). The
307
+ * upstream type/runtime mismatch on the response shape is handled by
308
+ * {@link normalizeFetchTopicMetadataResponse}.
309
+ */
310
+ async function fetchPartitionCounts(admin, topicNames) {
311
+ const topicMetadata = normalizeFetchTopicMetadataResponse(await admin.fetchTopicMetadata({ topics: topicNames }));
312
+ const counts = new Map();
313
+ for (const t of topicMetadata) {
314
+ counts.set(t.name, t.partitions.length);
315
+ }
316
+ for (const name of topicNames) {
317
+ if (!counts.has(name)) {
318
+ throw new Error(`Topic "${name}" returned no partition metadata (does it exist on this cluster?).`);
319
+ }
320
+ }
321
+ return counts;
322
+ }
323
+ /**
324
+ * Reject any target whose `partition` index is out of range for its
325
+ * topic. The error message cites the actual partition count so the
326
+ * caller can correct without guessing.
327
+ */
328
+ function validateRequestedPartitions(targets, numPartitionsByTopic) {
329
+ for (const t of targets) {
330
+ if (t.partition !== undefined) {
331
+ const numParts = numPartitionsByTopic.get(t.name);
332
+ if (t.partition >= numParts) {
333
+ throw new Error(`Topic "${t.name}" has ${numParts} partition(s) (0..${numParts - 1}); ` +
334
+ `requested partition ${t.partition} is out of range.`);
335
+ }
336
+ }
337
+ }
338
+ }
339
+ /**
340
+ * Validate per-topic consistency and derive the keep-partitions map in
341
+ * a single pass over `byTopic`. Three rejections fire here, each a
342
+ * shape of "starting position is ambiguous" the consumer can't honor:
343
+ *
344
+ * - **Mixed mode**: a topic with some entries restricting to a partition
345
+ * and others not — does the bare entry coexist with or override the
346
+ * partitioned one? Reject loudly.
347
+ * - **Duplicate unrestricted**: a topic with multiple entries that all
348
+ * omit `partition`. Two "every partition" entries with conflicting
349
+ * `start` directions silently let one win the seek/reset race; reject.
350
+ * - **Duplicate (topic, partition)**: the same partition index referenced
351
+ * twice within one topic — same ambiguity at finer granularity.
352
+ *
353
+ * Topics whose entries are all unrestricted are omitted from the
354
+ * returned map (downstream: "keep every assigned partition for that
355
+ * topic"). For topics that restrict, the keep-set is exactly the
356
+ * requested partition indices.
357
+ */
358
+ function validateAndBuildKeepPartitions(byTopic) {
359
+ const keepPartitions = new Map();
360
+ for (const [topic, list] of byTopic) {
361
+ const explicit = list
362
+ .map((t) => t.partition)
363
+ .filter((p) => p !== undefined);
364
+ const allPartitioned = explicit.length === list.length;
365
+ const nonePartitioned = explicit.length === 0;
366
+ if (!allPartitioned && !nonePartitioned) {
367
+ throw new Error(`Topic "${topic}" mixes entries with explicit partitions and entries without one. ` +
368
+ `Pick one mode per topic: either every entry restricts to a partition, or none do.`);
369
+ }
370
+ if (nonePartitioned) {
371
+ if (list.length > 1) {
372
+ throw new Error(`Topic "${topic}" has ${list.length} entries without partition restrictions; ` +
373
+ `specify each topic at most once at the unrestricted level (or restrict each entry to a distinct partition).`);
374
+ }
375
+ continue;
376
+ }
377
+ const seen = new Set();
378
+ for (const p of explicit) {
379
+ if (seen.has(p)) {
380
+ throw new Error(`Topic "${topic}" has multiple entries for partition ${p}; ` +
381
+ `specify each (topic, partition) pair at most once.`);
382
+ }
383
+ seen.add(p);
384
+ }
385
+ keepPartitions.set(topic, seen);
386
+ }
387
+ return keepPartitions;
388
+ }
389
+ /**
390
+ * Validate an explicit `start: {offset}` against the partition's
391
+ * `[low, high)` watermarks and return the seek target. Throws if the
392
+ * partition has no offset metadata or the requested offset is out of
393
+ * range.
394
+ */
395
+ async function resolveExplicitOffsetSeek(topic, partition, offset, getWatermarks) {
396
+ const offsets = await getWatermarks(topic);
397
+ const partOffsets = offsets.find((o) => o.partition === partition);
398
+ if (!partOffsets) {
399
+ throw new Error(`Topic "${topic}" partition ${partition} returned no offset metadata.`);
400
+ }
401
+ const low = BigInt(partOffsets.low);
402
+ const high = BigInt(partOffsets.high);
403
+ const target = BigInt(offset);
404
+ if (target < low || target >= high) {
405
+ throw new Error(`Topic "${topic}" partition ${partition} offset ${offset} is out of range ` +
406
+ `[low=${partOffsets.low}, high=${partOffsets.high}). ` +
407
+ `An empty partition has low === high; pick an offset already on the partition.`);
408
+ }
409
+ return { topic, partition, offset };
410
+ }
411
+ /**
412
+ * Resolve a `start: {tail: N}` to a single seek target on the requested
413
+ * partition. Computes `target = max(low, high - N)` using `BigInt`
414
+ * (Kafka offsets are int64; `N` may legitimately be large).
415
+ *
416
+ * Empty-partition contract (`low === high`): the seek target collapses
417
+ * to `high` (`max(high, high - N) === high`), which parks the consumer
418
+ * at `OFFSET_END`. No records flow, the orchestrator's `timeoutMs`
419
+ * budget fires, and the call returns an empty success response — the
420
+ * issue's "tail on empty partition returns zero, no error" path. The
421
+ * `BigInt` subtraction stays well-defined for huge `N` because the
422
+ * arithmetic compares signed values and `max` clamps back into the
423
+ * `[low, high)` range before serializing.
424
+ */
425
+ async function resolveTailSeek(topic, partition, count, getWatermarks) {
426
+ const offsets = await getWatermarks(topic);
427
+ const partOffsets = offsets.find((o) => o.partition === partition);
428
+ if (!partOffsets) {
429
+ throw new Error(`Topic "${topic}" partition ${partition} returned no offset metadata.`);
430
+ }
431
+ const low = BigInt(partOffsets.low);
432
+ const high = BigInt(partOffsets.high);
433
+ const candidate = high - BigInt(count);
434
+ // Manual clamp instead of Math.max: Math.max rejects BigInt operands
435
+ // (TypeError: Cannot convert a BigInt value to a number), and the
436
+ // BigInt path is load-bearing because Kafka offsets are int64.
437
+ const target = candidate < low ? low : candidate;
438
+ return { topic, partition, offset: target.toString() };
439
+ }
440
+ /**
441
+ * Resolve a `start: {timestamp}` to per-partition offsets via
442
+ * `admin.fetchTopicOffsetsByTimestamp` AND filter out the binding's
443
+ * silent high-watermark substitutions.
444
+ *
445
+ * `@confluentinc/kafka-javascript`'s `fetchTopicOffsetsByTimestamp`
446
+ * silently substitutes the partition's high watermark when no message
447
+ * exists at or after the requested timestamp (see the method's
448
+ * docstring in node_modules/.../kafkajs/_admin.js). Seeking to a
449
+ * position == high watermark parks the consumer at OFFSET_END and we'd
450
+ * time out with zero messages with no signal as to why. The defense:
451
+ * cross-check each resolved offset against the partition's actual
452
+ * watermarks, skip silent partitions, and emit a diagnostic log so
453
+ * operators can distinguish the three resolution modes (silent /
454
+ * broker-low-fallback / real-index-hit).
455
+ */
456
+ async function resolveTimestampSeeks(admin, topic, restrictToPartition, timestampMs, getWatermarks) {
457
+ const resolved = await admin.fetchTopicOffsetsByTimestamp(topic, timestampMs);
458
+ const watermarks = await getWatermarks(topic);
459
+ const watermarkByPartition = new Map();
460
+ for (const w of watermarks) {
461
+ watermarkByPartition.set(w.partition, { low: w.low, high: w.high });
462
+ }
463
+ const candidates = restrictToPartition === undefined
464
+ ? resolved
465
+ : resolved.filter((r) => r.partition === restrictToPartition);
466
+ const active = [];
467
+ const silent = [];
468
+ for (const r of candidates) {
469
+ const wm = watermarkByPartition.get(r.partition);
470
+ // Diagnostic log so debugging timestamp-to-offset resolution
471
+ // doesn't require adding instrumentation later. Lets a reader
472
+ // distinguish three failure modes from one log inspection:
473
+ // - resolvedOffset == high → binding's silent substitution
474
+ // (already filtered below; the warn-level log fires too).
475
+ // - resolvedOffset == low → broker fell back to the earliest
476
+ // offset (e.g. requested timestamp is older than every
477
+ // indexed message, or tiered-storage index quirk).
478
+ // - resolvedOffset in (low, high) → real timestamp resolution.
479
+ logger.info({
480
+ topic,
481
+ partition: r.partition,
482
+ resolvedOffset: r.offset,
483
+ low: wm?.low,
484
+ high: wm?.high,
485
+ requestedMs: timestampMs,
486
+ requestedIso: new Date(timestampMs).toISOString(),
487
+ }, `Timestamp → offset resolution for ${topic} partition ${r.partition}`);
488
+ if (wm !== undefined && BigInt(r.offset) >= BigInt(wm.high)) {
489
+ silent.push(r.partition);
490
+ }
491
+ else {
492
+ active.push(r);
493
+ }
494
+ }
495
+ if (active.length === 0) {
496
+ const scopePhrase = restrictToPartition === undefined
497
+ ? "every partition"
498
+ : `partition ${restrictToPartition}`;
499
+ throw new Error(`Topic "${topic}" has no messages at or after timestamp ` +
500
+ `${new Date(timestampMs).toISOString()} ` +
501
+ `(${scopePhrase} has no record produced past that point). ` +
502
+ `Try an earlier timestamp, or use \`start: "latest"\` to consume ` +
503
+ `from the live position instead.`);
504
+ }
505
+ if (silent.length > 0) {
506
+ logger.warn({ topic, timestampMs, silentPartitions: silent }, `Skipping seek for partitions of "${topic}" with no record at or after the requested timestamp; they will idle at OFFSET_END.`);
507
+ }
508
+ return active.map((r) => ({
509
+ topic,
510
+ partition: r.partition,
511
+ offset: r.offset,
512
+ }));
513
+ }
514
+ /**
515
+ * Resolve a `start: "earliest"` entry that lost the direction-derivation
516
+ * race — i.e. another entry pushed the consumer-wide
517
+ * `auto.offset.reset` to `"latest"`. Returns explicit low-watermark
518
+ * seeks for every partition (or the single restricted partition) so
519
+ * this topic replays its history instead of inheriting the
520
+ * consumer-wide "latest" default.
521
+ */
522
+ async function resolveEarliestMinoritySeeks(topic, restrictToPartition, getWatermarks) {
523
+ const offsets = await getWatermarks(topic);
524
+ const partitionsToSeek = restrictToPartition === undefined
525
+ ? offsets
526
+ : offsets.filter((o) => o.partition === restrictToPartition);
527
+ return partitionsToSeek.map((p) => ({
528
+ topic,
529
+ partition: p.partition,
530
+ offset: p.low,
531
+ }));
532
+ }
533
+ async function buildPreflightPlan(admin, targets, consumerOffsetReset) {
534
+ guardScopedStartRequiresPartition(targets);
535
+ const byTopic = new Map();
536
+ for (const t of targets) {
537
+ const list = byTopic.get(t.name);
538
+ if (list)
539
+ list.push(t);
540
+ else
541
+ byTopic.set(t.name, [t]);
542
+ }
543
+ const topicNames = [...byTopic.keys()];
544
+ const numPartitionsByTopic = await fetchPartitionCounts(admin, topicNames);
545
+ validateRequestedPartitions(targets, numPartitionsByTopic);
546
+ const keepPartitions = validateAndBuildKeepPartitions(byTopic);
547
+ const getWatermarks = createWatermarkCache(admin);
548
+ const seeks = [];
549
+ for (const t of targets) {
550
+ if (t.start.kind === "offset" && t.partition !== undefined) {
551
+ seeks.push(await resolveExplicitOffsetSeek(t.name, t.partition, t.start.value, getWatermarks));
552
+ }
553
+ else if (t.start.kind === "timestamp") {
554
+ seeks.push(...(await resolveTimestampSeeks(admin, t.name, t.partition, t.start.ms, getWatermarks)));
555
+ }
556
+ else if (t.start.kind === "tail" && t.partition !== undefined) {
557
+ seeks.push(await resolveTailSeek(t.name, t.partition, t.start.count, getWatermarks));
558
+ }
559
+ else if (t.start.kind === "earliest" &&
560
+ consumerOffsetReset === "latest") {
561
+ seeks.push(...(await resolveEarliestMinoritySeeks(t.name, t.partition, getWatermarks)));
562
+ }
563
+ }
564
+ return { topicNames, keepPartitions, seeks };
565
+ }
566
+ /**
567
+ * Poll `consumer.assignment()` until it returns a non-empty array or the
568
+ * supplied deadline passes. Returns the assignment, or `null` if the
569
+ * deadline was hit first. The kafkajs-compat consumer only populates its
570
+ * assignment after `consumer.run()` triggers the first poll, so this is
571
+ * the well-defined seam to wait on before issuing per-partition seeks.
572
+ * Exported so the three branches (immediate-return, polled-populate,
573
+ * polled-timeout) can be exercised directly under fake timers.
574
+ */
575
+ export async function waitForAssignment(consumer, deadline) {
576
+ for (;;) {
577
+ const assignment = consumer.assignment();
578
+ if (assignment.length > 0) {
579
+ return assignment;
580
+ }
581
+ if (Date.now() >= deadline) {
582
+ return null;
583
+ }
584
+ await new Promise((r) => setTimeout(r, 50));
585
+ }
586
+ }
587
+ /**
588
+ * After the consumer assignment lands, pause any assigned partition that
589
+ * isn't in the keep-set. Seeks were stashed before `consumer.run()` (see
590
+ * the inline comment in the orchestrator) so the per-partition seek
591
+ * targets are already baked into the assignment by the time this runs.
592
+ */
593
+ async function applyPauseAfterAssignment(consumer, plan, assignment) {
594
+ const toPause = [];
595
+ for (const [topic, keepSet] of plan.keepPartitions) {
596
+ const pauseForTopic = assignment
597
+ .filter((a) => a.topic === topic && !keepSet.has(a.partition))
598
+ .map((a) => a.partition);
599
+ if (pauseForTopic.length > 0) {
600
+ toPause.push({ topic, partitions: pauseForTopic });
601
+ }
602
+ }
603
+ if (toPause.length > 0) {
604
+ consumer.pause(toPause);
605
+ }
606
+ }
607
+ /**
608
+ * Wrap a promise so its successful resolution is ignored (never settles
609
+ * downstream) while its rejection still propagates. Used in the
610
+ * orchestrator's `Promise.race` to let "engine still running" branches
611
+ * (`consumer.run`, `applyPostAssignmentHook`) participate only on
612
+ * failure — successful completion of those promises shouldn't end the
613
+ * consume loop, but their errors must surface as the race's rejection.
614
+ */
615
+ function rejectOnly(p) {
616
+ return p.then(() => new Promise(() => { }));
617
+ }
618
+ /**
619
+ * Drive the post-`consumer.run()` pause step: poll for an assignment up
620
+ * to `deadlineMs`, then pause partitions outside the keep-set. Signals
621
+ * completion via callbacks rather than its return value because the
622
+ * orchestrator's `Promise.race` only cares about success (`onApplied`)
623
+ * vs. assignment-deadline-elapsed (`onAssignmentTimedOut`); internal
624
+ * pause failures propagate as a rejection so the race can surface them
625
+ * via `rejectOnly`.
626
+ */
627
+ export async function applyPostAssignmentHook(opts) {
628
+ const assignment = await waitForAssignment(opts.consumer, opts.deadlineMs);
629
+ if (!assignment) {
630
+ opts.onAssignmentTimedOut();
631
+ return;
632
+ }
633
+ await applyPauseAfterAssignment(opts.consumer, opts.plan, assignment);
634
+ opts.onApplied();
635
+ }
636
+ /**
637
+ * Build the `eachMessage` callback `consumer.run` invokes per record.
638
+ * Pulled out of the orchestrator so the synchronous gates and the
639
+ * "did we hit `maxMessages`?" signal are individually testable.
640
+ *
641
+ * The synchronous boolean/predicate getters (`isAccepting`,
642
+ * `isPreflightApplied`, `shouldKeepDuringPrePause`) intentionally stay
643
+ * synchronous — `await`ing a Deferred in their place would change
644
+ * behavior, because pre-pause deliveries from librdkafka's fetch buffer
645
+ * must be filtered on arrival rather than queued through.
646
+ *
647
+ * Pre-pause gate semantics: before the post-assignment pause step has
648
+ * run, drop a delivery **only** when its `(topic, partition)` is one
649
+ * we'd pause anyway (i.e. outside the topic's keep-set). Keep-set
650
+ * deliveries arriving during the brief window between assignment-landing
651
+ * and pause-being-applied are real records the caller asked for; the
652
+ * pre-run-stashed seeks have already positioned the consumer, so
653
+ * librdkafka can hand them to `eachMessage` immediately. Dropping them
654
+ * would be silent data loss. After `preflightApplied` flips, the gate
655
+ * doesn't fire (pause has already kept the unwanted partitions silent).
656
+ */
657
+ export function createEachMessageHandler(opts) {
658
+ return async ({ topic, partition, message }) => {
659
+ if (!opts.state.isAccepting())
660
+ return;
661
+ if (!opts.state.isPreflightApplied() &&
662
+ !opts.state.shouldKeepDuringPrePause(topic, partition)) {
663
+ return;
664
+ }
665
+ const processed = await opts.processMessage(topic, partition, message);
666
+ opts.state.consumedMessages.push(processed);
667
+ if (opts.state.consumedMessages.length >= opts.maxMessages) {
668
+ opts.onMaxReached();
669
+ }
670
+ };
671
+ }
52
672
  /**
53
673
  * Handler for consuming messages from Kafka topics with support for Schema Registry deserialization.
54
674
  * This handler allows consuming messages from one or more topics with configurable message limits and timeouts.
@@ -61,44 +681,51 @@ export class ConsumeKafkaMessagesHandler extends BaseToolHandler {
61
681
  * @param partition - The partition the message was consumed from
62
682
  * @param message - The raw Kafka message
63
683
  * @param registry - Optional Schema Registry client for deserialization
64
- * @param valueOptions - Options for value deserialization
65
- * @param keyOptions - Optional options for key deserialization
684
+ * @param valueOptions - Options for value-side deserialization
685
+ * @param keyOptions - Options for key-side deserialization
66
686
  * @returns A processed message with deserialized key and value
67
687
  */
68
688
  async processMessage(topic, partition, message, registry, valueOptions, keyOptions) {
69
689
  let processedKey = message.key?.toString();
70
690
  let processedValue = message.value?.toString();
71
691
  const deserializeWithOptions = async (buffer, options, serdeType) => {
72
- if (!options.useSchemaRegistry || !registry) {
73
- return buffer?.toString();
692
+ // A null/undefined payload (Kafka tombstone, or an absent key/value)
693
+ // can't be decoded — short-circuit before any SR lookup so the
694
+ // deserializer is never handed a non-Buffer and no spurious error is
695
+ // logged on the inevitable failure.
696
+ if (buffer == null) {
697
+ return undefined;
698
+ }
699
+ if (options.disableSchemaRegistry || !registry) {
700
+ return buffer.toString();
74
701
  }
75
702
  const subject = options.subject ||
76
703
  `${topic}-${serdeType === SerdeType.KEY ? "key" : "value"}`;
77
- const schema = await getLatestSchemaIfExists(registry, subject);
704
+ const schema = await schemaRegistryHelper.getLatestSchemaIfExists(registry, subject);
78
705
  if (!schema || !schema.schemaType) {
79
- return buffer?.toString();
706
+ return buffer.toString();
80
707
  }
81
708
  try {
82
- return await deserializeMessage(topic, buffer, schema.schemaType, registry, serdeType);
709
+ return await schemaRegistryHelper.deserializeMessage(topic, buffer, schema.schemaType, registry, serdeType, message.headers);
83
710
  }
84
711
  catch (err) {
85
712
  logger.error({ error: err, topic, schemaType: schema.schemaType, serdeType }, `Error deserializing message ${serdeType} for topic ${topic}`);
86
- return buffer?.toString();
713
+ return buffer.toString();
87
714
  }
88
715
  };
89
716
  processedValue = await deserializeWithOptions(message.value, valueOptions, SerdeType.VALUE);
90
- if (message.key && keyOptions) {
717
+ if (message.key) {
91
718
  processedKey = await deserializeWithOptions(message.key, keyOptions, SerdeType.KEY);
92
719
  }
93
720
  return {
94
721
  key: processedKey,
95
722
  value: processedValue,
96
- timestamp: message.timestamp,
723
+ timestamp: formatMessageTimestamp(message.timestamp),
97
724
  offset: message.offset,
98
725
  headers: message.headers
99
726
  ? Object.fromEntries(Object.entries(message.headers).map(([key, value]) => [
100
727
  key,
101
- value?.toString() || "",
728
+ echoHeaderValue(key, value),
102
729
  ]))
103
730
  : undefined,
104
731
  topic,
@@ -106,57 +733,192 @@ export class ConsumeKafkaMessagesHandler extends BaseToolHandler {
106
733
  };
107
734
  }
108
735
  /**
109
- * Main handler for consuming messages from Kafka topics.
110
- * @param clientManager - The client manager for Kafka and registry clients
111
- * @param toolArguments - The arguments for the tool, including topics, message limits, and deserialization options
112
- * @param sessionId - Optional session ID for Kafka consumer
113
- * @returns A CallToolResult containing the consumed messages or error information
736
+ * Consume messages from one or more Kafka topics, honoring per-topic
737
+ * `partition` and `start` controls. Resolves when one of four exit
738
+ * conditions wins a `Promise.race`: `maxMessages` records have been
739
+ * collected, the `timeoutMs` budget elapses, `consumer.run()` rejects,
740
+ * or the post-assignment hook rejects. On the timeout path the
741
+ * partial set collected so far is returned (success-shaped response,
742
+ * not an error).
743
+ *
744
+ * @param runtime - The {@link ServerRuntime} (config + active client
745
+ * manager + OAuth holder). Supplied by the MCP server dispatcher;
746
+ * the handler resolves cluster/env/registry clients off of it.
747
+ * @param toolArguments - Raw tool arguments (`Record<string, unknown>`) as
748
+ * handed in by the MCP dispatcher. The handler parses them with
749
+ * `consumeKafkaMessagesArgs` to apply defaults; `resolveConnection` reads
750
+ * `connectionId` off this unparsed object (a handler-local `z.object`
751
+ * re-parse would strip the key it never declared).
752
+ * @returns A {@link CallToolResult}. Success path emits a text block
753
+ * summarizing the consumed messages; failures (build/preflight/run
754
+ * errors) surface via `createResponse(text, true)` with the
755
+ * formatted Kafka error.
114
756
  */
115
- async handle(runtime, toolArguments, sessionId) {
757
+ async handle(runtime, toolArguments) {
116
758
  const parsed = consumeKafkaMessagesArgs.parse(toolArguments);
117
- const { topicNames, maxMessages, timeoutMs, value, key } = parsed;
118
- const { connId, clientManager } = this.resolveSoleConnection(runtime);
759
+ const { maxMessages, timeoutMs, valueFormat, keyFormat } = parsed;
760
+ const { connId, conn, clientManager } = this.resolveConnection(runtime, toolArguments);
119
761
  const resolved = resolveKafkaClusterArgs(parsed, runtime, connId);
120
- const needsRegistry = (value && value.useSchemaRegistry) || (key && key.useSchemaRegistry);
762
+ // Auto-decode when SR is reachable on this connection (OAuth always; direct
763
+ // requires a `schema_registry` block — single source of truth lives in the
764
+ // `hasSchemaRegistryOrOAuth` predicate) and the caller hasn't opted out on
765
+ // BOTH sides. Either side wanting decode triggers the SR fetch.
766
+ const userDisabled = valueFormat.disableSchemaRegistry && keyFormat.disableSchemaRegistry;
767
+ const srReachable = hasSchemaRegistryOrOAuth(conn).enabled;
121
768
  let registry;
122
- if (needsRegistry) {
123
- registry = await clientManager.getSchemaRegistrySdkClient(resolved.envId);
769
+ if (!userDisabled && srReachable) {
770
+ try {
771
+ registry = await clientManager.getSchemaRegistrySdkClient(resolved.envId);
772
+ }
773
+ catch (error) {
774
+ // Graceful fallback: user didn't explicitly opt in, so an SR-transport
775
+ // failure shouldn't fail an otherwise-satisfiable consume. Per-message
776
+ // decode errors already fall back to raw inside processMessage.
777
+ logger.warn({ error, connId }, "Schema Registry client unavailable; consuming as raw bytes.");
778
+ }
779
+ }
780
+ const targets = normalizeTopicTargets(parsed);
781
+ const offsetReset = deriveConsumerOffsetReset(targets);
782
+ const needsPreflight = planNeedsPreflight(targets, offsetReset);
783
+ let plan;
784
+ if (needsPreflight) {
785
+ // `admin` is acquired INSIDE the try so a failed
786
+ // getKafkaAdminClient() (auth/network) becomes a tool-error
787
+ // response rather than propagating up unhandled. The finally
788
+ // guards against running disposeIfOAuth on an undefined admin
789
+ // (which would happen if the acquisition itself threw).
790
+ let admin;
791
+ try {
792
+ admin = await clientManager.getKafkaAdminClient(resolved.clusterId, resolved.envId);
793
+ plan = await buildPreflightPlan(admin, targets, offsetReset);
794
+ }
795
+ catch (error) {
796
+ return this.createResponse(`Failed to consume messages: ${formatKafkaError(error)}`, true);
797
+ }
798
+ finally {
799
+ if (admin !== undefined) {
800
+ await disposeIfOAuth(runtime, connId, admin);
801
+ }
802
+ }
803
+ }
804
+ else {
805
+ plan = {
806
+ topicNames: [...new Set(targets.map((t) => t.name))],
807
+ keepPartitions: new Map(),
808
+ seeks: [],
809
+ };
124
810
  }
125
811
  const consumedMessages = [];
126
- let timeoutReached = false;
812
+ // `accepting` is the synchronous gate that suppresses further
813
+ // processing once any terminal condition fires. `preflightApplied`
814
+ // is the synchronous gate that drops pre-pause fetch-buffer
815
+ // deliveries. Both stay booleans because eachMessage needs to check
816
+ // them on arrival — a `Promise` would require `await`, which would
817
+ // queue rather than drop the message.
818
+ let accepting = true;
819
+ let preflightApplied = !needsPreflight;
127
820
  let consumer;
128
821
  try {
129
- consumer = await clientManager.buildKafkaConsumer(resolved.clusterId, resolved.envId, sessionId);
822
+ consumer = await clientManager.buildKafkaConsumer({
823
+ clusterId: resolved.clusterId,
824
+ envId: resolved.envId,
825
+ // Per-invocation unique group id: each consume call becomes
826
+ // the sole member of its own Kafka consumer group, so two
827
+ // concurrent calls can't race for the same partition
828
+ // assignment via rebalance. (How the value reaches `group.id`
829
+ // depends on the manager: `DirectClientManager` appends it as
830
+ // a suffix to its base `mcp-confluent` group; `OAuthClientManager`
831
+ // uses it as the literal group id. Both yield a unique-per-call
832
+ // group, which is what the no-contention contract requires.)
833
+ groupId: nodeCrypto.randomUUID(),
834
+ offsetReset,
835
+ });
130
836
  await consumer.connect();
131
- await consumer.subscribe({ topics: topicNames });
132
- const consumePromise = new Promise((resolve, reject) => {
133
- if (!consumer) {
134
- reject(new Error("Consumer was unexpectedly undefined"));
135
- return;
136
- }
137
- consumer
138
- .run({
139
- eachMessage: async ({ topic, partition, message }) => {
140
- if (timeoutReached)
141
- return;
142
- const processed = await this.processMessage(topic, partition, message, registry, value, key);
143
- consumedMessages.push(processed);
144
- if (consumedMessages.length >= maxMessages) {
145
- timeoutReached = true;
146
- resolve();
147
- }
148
- },
149
- })
150
- .catch((error) => {
151
- reject(error);
837
+ await consumer.subscribe({ topics: plan.topicNames });
838
+ // Stash per-partition seeks on the consumer BEFORE `consumer.run()`
839
+ // is invoked. The kafkajs-compat library queues these as "pending
840
+ // seeks" via its `#addPendingOperation` → `#seekInternal` path; when
841
+ // the first rebalance fires inside `run()`, the assignment handler
842
+ // calls `#assignAsPerSeekedOffsets` and modifies the assignment to
843
+ // include the seeked offsets BEFORE calling `assignmentFn`, so the
844
+ // partition is assigned at the seek target atomically.
845
+ //
846
+ // This avoids the `ERR__STATE` race that the post-assignment seek
847
+ // path trips: even after `consumer.assignment()` returns the
848
+ // partition, the native librdkafka client can briefly reject seeks
849
+ // because its internal state hasn't fully transitioned from
850
+ // "rebalancing" to "ready." The pre-run path bypasses native seek
851
+ // calls entirely.
852
+ for (const s of plan.seeks) {
853
+ consumer.seek({
854
+ topic: s.topic,
855
+ partition: s.partition,
856
+ offset: s.offset,
152
857
  });
153
- setTimeout(() => {
154
- timeoutReached = true;
155
- resolve();
156
- }, timeoutMs);
858
+ }
859
+ // Named exit conditions. Each Deferred resolves only its own
860
+ // promise — the prior shared `timeoutReached` boolean was written
861
+ // by three sites with overlapping semantics; this splits them into
862
+ // independent signals the race observes.
863
+ const maxReached = Promise.withResolvers();
864
+ const timedOut = Promise.withResolvers();
865
+ const consumerActive = consumer;
866
+ const eachMessage = createEachMessageHandler({
867
+ state: {
868
+ consumedMessages,
869
+ isAccepting: () => accepting,
870
+ isPreflightApplied: () => preflightApplied,
871
+ // Pre-pause gate predicate: a (topic, partition) is "keep" if
872
+ // the topic has no partition restriction (keepPartitions omits
873
+ // it) or the partition is in the topic's keep-set. The
874
+ // orchestrator's `plan.keepPartitions` is built so absence of
875
+ // an entry means "no restriction"; we honor that contract here.
876
+ shouldKeepDuringPrePause: (topic, partition) => {
877
+ const keepSet = plan.keepPartitions.get(topic);
878
+ return keepSet === undefined || keepSet.has(partition);
879
+ },
880
+ },
881
+ maxMessages,
882
+ onMaxReached: () => {
883
+ accepting = false;
884
+ maxReached.resolve();
885
+ },
886
+ processMessage: (topic, partition, message) => this.processMessage(topic, partition, message, registry, valueFormat, keyFormat),
157
887
  });
158
- await consumePromise;
159
- return this.createResponse(`Consumed ${consumedMessages.length} messages from topics ${topicNames.join(", ")}.\nConsumed messages: ${JSON.stringify(consumedMessages, null, 2)}`, false);
888
+ const timer = setTimeout(() => {
889
+ accepting = false;
890
+ timedOut.resolve();
891
+ }, timeoutMs);
892
+ const preflightHook = needsPreflight
893
+ ? applyPostAssignmentHook({
894
+ consumer: consumerActive,
895
+ plan,
896
+ deadlineMs: Date.now() + timeoutMs,
897
+ onApplied: () => {
898
+ preflightApplied = true;
899
+ },
900
+ onAssignmentTimedOut: () => {
901
+ accepting = false;
902
+ timedOut.resolve();
903
+ },
904
+ })
905
+ : Promise.resolve();
906
+ const runPromise = consumerActive.run({ eachMessage });
907
+ try {
908
+ await Promise.race([
909
+ maxReached.promise,
910
+ timedOut.promise,
911
+ // Success of either branch means "engine still running" — only
912
+ // their rejection is a terminal condition for the race.
913
+ rejectOnly(runPromise),
914
+ rejectOnly(preflightHook),
915
+ ]);
916
+ }
917
+ finally {
918
+ clearTimeout(timer);
919
+ accepting = false;
920
+ }
921
+ return this.createResponse(`Consumed ${consumedMessages.length} messages from topics ${plan.topicNames.join(", ")}.\nConsumed messages: ${JSON.stringify(consumedMessages, null, 2)}`, false);
160
922
  }
161
923
  catch (error) {
162
924
  return this.createResponse(`Failed to consume messages: ${formatKafkaError(error)}`, true);
@@ -175,11 +937,13 @@ export class ConsumeKafkaMessagesHandler extends BaseToolHandler {
175
937
  getToolConfig() {
176
938
  return {
177
939
  name: ToolName.CONSUME_MESSAGES,
178
- description: "Consumes messages from one or more Kafka topics. Supports automatic deserialization of Schema Registry encoded messages (AVRO, JSON, PROTOBUF).",
940
+ description: "Consume messages from Kafka topics. Optionally restrict to a partition, start from an offset, timestamp, earliest, latest, or tailmost pre-existing messages." +
941
+ " Auto-deserializes Schema Registry messages (AVRO/JSON/PROTOBUF) when SR is configured on the connection; pass `valueFormat: {disableSchemaRegistry: true}` (or `keyFormat`) to skip decoding per side.",
179
942
  inputSchema: consumeKafkaMessagesArgs.shape,
180
943
  annotations: READ_ONLY,
181
944
  };
182
945
  }
946
+ category = ToolCategory.Kafka;
183
947
  predicate = kafkaBootstrapOrOAuth;
184
948
  }
185
949
  //# sourceMappingURL=consume-kafka-messages-handler.js.map