@mcp-abap-adt/core 2.1.3 → 2.1.6

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 (438) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/bin/help.d.ts +6 -0
  3. package/dist/bin/help.d.ts.map +1 -0
  4. package/dist/bin/help.js +59 -0
  5. package/dist/bin/mcp-abap-adt-v2.d.ts +9 -0
  6. package/dist/bin/mcp-abap-adt-v2.d.ts.map +1 -0
  7. package/dist/bin/mcp-abap-adt-v2.js +247 -0
  8. package/dist/handlers/behavior_definition/readonly/handleGetBdef.d.ts +21 -0
  9. package/dist/handlers/behavior_definition/readonly/handleGetBdef.d.ts.map +1 -0
  10. package/dist/handlers/behavior_definition/readonly/handleGetBdef.js +24 -0
  11. package/dist/handlers/behavior_definition/readonly/handleGetBdef.js.map +1 -0
  12. package/dist/handlers/class/high/handleCreateLocalTestClass.d.ts.map +1 -1
  13. package/dist/handlers/class/high/handleCreateLocalTestClass.js +6 -1
  14. package/dist/handlers/class/high/handleCreateLocalTestClass.js.map +1 -1
  15. package/dist/handlers/class/high/handleGetUnitTest.d.ts +38 -0
  16. package/dist/handlers/class/high/handleGetUnitTest.d.ts.map +1 -0
  17. package/dist/handlers/class/high/handleGetUnitTest.js +73 -0
  18. package/dist/handlers/class/high/handleGetUnitTest.js.map +1 -0
  19. package/dist/handlers/class/high/handleRunUnitTest.d.ts +122 -0
  20. package/dist/handlers/class/high/handleRunUnitTest.d.ts.map +1 -0
  21. package/dist/handlers/class/high/handleRunUnitTest.js +133 -0
  22. package/dist/handlers/class/high/handleRunUnitTest.js.map +1 -0
  23. package/dist/handlers/class/readonly/handleGetClass.d.ts +23 -0
  24. package/dist/handlers/class/readonly/handleGetClass.d.ts.map +1 -0
  25. package/dist/handlers/class/readonly/handleGetClass.js +77 -0
  26. package/dist/handlers/class/readonly/handleGetClass.js.map +1 -0
  27. package/dist/handlers/data_element/readonly/handleGetDataElement.d.ts +23 -0
  28. package/dist/handlers/data_element/readonly/handleGetDataElement.d.ts.map +1 -0
  29. package/dist/handlers/data_element/readonly/handleGetDataElement.js +79 -0
  30. package/dist/handlers/data_element/readonly/handleGetDataElement.js.map +1 -0
  31. package/dist/handlers/domain/readonly/handleGetDomain.d.ts +23 -0
  32. package/dist/handlers/domain/readonly/handleGetDomain.d.ts.map +1 -0
  33. package/dist/handlers/domain/readonly/handleGetDomain.js +79 -0
  34. package/dist/handlers/domain/readonly/handleGetDomain.js.map +1 -0
  35. package/dist/handlers/enhancement/readonly/handleGetEnhancements.d.ts.map +1 -1
  36. package/dist/handlers/enhancement/readonly/handleGetEnhancements.js +7 -7
  37. package/dist/handlers/enhancement/readonly/handleGetEnhancements.js.map +1 -1
  38. package/dist/handlers/function/readonly/handleGetFunctionGroup.d.ts +23 -0
  39. package/dist/handlers/function/readonly/handleGetFunctionGroup.d.ts.map +1 -0
  40. package/dist/handlers/function/readonly/handleGetFunctionGroup.js +77 -0
  41. package/dist/handlers/function/readonly/handleGetFunctionGroup.js.map +1 -0
  42. package/dist/handlers/include/readonly/handleGetIncludesList.d.ts.map +1 -1
  43. package/dist/handlers/include/readonly/handleGetIncludesList.js +8 -5
  44. package/dist/handlers/include/readonly/handleGetIncludesList.js.map +1 -1
  45. package/dist/handlers/interface/readonly/handleGetInterface.d.ts +23 -0
  46. package/dist/handlers/interface/readonly/handleGetInterface.d.ts.map +1 -0
  47. package/dist/handlers/interface/readonly/handleGetInterface.js +77 -0
  48. package/dist/handlers/interface/readonly/handleGetInterface.js.map +1 -0
  49. package/dist/handlers/package/readonly/handleGetPackage.d.ts +22 -0
  50. package/dist/handlers/package/readonly/handleGetPackage.d.ts.map +1 -0
  51. package/dist/handlers/package/readonly/handleGetPackage.js +108 -0
  52. package/dist/handlers/package/readonly/handleGetPackage.js.map +1 -0
  53. package/dist/handlers/program/readonly/handleGetProgram.d.ts +23 -0
  54. package/dist/handlers/program/readonly/handleGetProgram.d.ts.map +1 -0
  55. package/dist/handlers/program/readonly/handleGetProgram.js +77 -0
  56. package/dist/handlers/program/readonly/handleGetProgram.js.map +1 -0
  57. package/dist/handlers/search/readonly/handleGetObjectsByType.d.ts.map +1 -1
  58. package/dist/handlers/search/readonly/handleGetObjectsByType.js +5 -1
  59. package/dist/handlers/search/readonly/handleGetObjectsByType.js.map +1 -1
  60. package/dist/handlers/search/readonly/handleGetObjectsList.d.ts.map +1 -1
  61. package/dist/handlers/search/readonly/handleGetObjectsList.js +8 -4
  62. package/dist/handlers/search/readonly/handleGetObjectsList.js.map +1 -1
  63. package/dist/handlers/service_definition/readonly/handleGetServiceDefinition.d.ts +18 -0
  64. package/dist/handlers/service_definition/readonly/handleGetServiceDefinition.d.ts.map +1 -0
  65. package/dist/handlers/service_definition/readonly/handleGetServiceDefinition.js +109 -0
  66. package/dist/handlers/service_definition/readonly/handleGetServiceDefinition.js.map +1 -0
  67. package/dist/handlers/structure/readonly/handleGetStructure.d.ts +23 -0
  68. package/dist/handlers/structure/readonly/handleGetStructure.d.ts.map +1 -0
  69. package/dist/handlers/structure/readonly/handleGetStructure.js +77 -0
  70. package/dist/handlers/structure/readonly/handleGetStructure.js.map +1 -0
  71. package/dist/handlers/system/readonly/handleGetObjectInfo.d.ts.map +1 -1
  72. package/dist/handlers/system/readonly/handleGetObjectInfo.js +8 -14
  73. package/dist/handlers/system/readonly/handleGetObjectInfo.js.map +1 -1
  74. package/dist/handlers/table/readonly/handleGetTable.d.ts +23 -0
  75. package/dist/handlers/table/readonly/handleGetTable.d.ts.map +1 -0
  76. package/dist/handlers/table/readonly/handleGetTable.js +77 -0
  77. package/dist/handlers/table/readonly/handleGetTable.js.map +1 -0
  78. package/dist/handlers/view/readonly/handleGetView.d.ts +33 -0
  79. package/dist/handlers/view/readonly/handleGetView.d.ts.map +1 -0
  80. package/dist/handlers/view/readonly/handleGetView.js +90 -0
  81. package/dist/handlers/view/readonly/handleGetView.js.map +1 -0
  82. package/dist/index.d.ts +126 -0
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/index.js +2979 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/lib/ServerConfigManager.d.ts +90 -0
  87. package/dist/lib/ServerConfigManager.d.ts.map +1 -0
  88. package/dist/lib/ServerConfigManager.js +237 -0
  89. package/dist/lib/ServerConfigManager.js.map +1 -0
  90. package/dist/lib/activationUtils.d.ts +59 -0
  91. package/dist/lib/activationUtils.d.ts.map +1 -0
  92. package/dist/lib/activationUtils.js +168 -0
  93. package/dist/lib/authBrokerFactory.d.ts +96 -0
  94. package/dist/lib/authBrokerFactory.d.ts.map +1 -0
  95. package/dist/lib/authBrokerFactory.js +531 -0
  96. package/dist/lib/authBrokerFactory.js.map +1 -0
  97. package/dist/lib/config/ServerConfig.d.ts +17 -0
  98. package/dist/lib/config/ServerConfig.d.ts.map +1 -0
  99. package/dist/lib/config/ServerConfig.js +7 -0
  100. package/dist/lib/config/ServerConfig.js.map +1 -0
  101. package/dist/lib/getFullCodeCache.d.ts +3 -0
  102. package/dist/lib/getFullCodeCache.d.ts.map +1 -0
  103. package/dist/lib/getFullCodeCache.js +56 -0
  104. package/dist/lib/handlers/handlers/base/BaseHandlerGroup.d.ts +36 -0
  105. package/dist/lib/handlers/handlers/base/BaseHandlerGroup.d.ts.map +1 -0
  106. package/dist/lib/handlers/handlers/base/BaseHandlerGroup.js +268 -0
  107. package/dist/lib/handlers/handlers/base/BaseHandlerGroup.js.map +1 -0
  108. package/dist/lib/handlers/handlers/examples/usage-example.d.ts +29 -0
  109. package/dist/lib/handlers/handlers/examples/usage-example.d.ts.map +1 -0
  110. package/dist/lib/handlers/handlers/examples/usage-example.js +119 -0
  111. package/dist/lib/handlers/handlers/examples/usage-example.js.map +1 -0
  112. package/dist/lib/handlers/handlers/groups/HighLevelHandlersGroup.d.ts +14 -0
  113. package/dist/lib/handlers/handlers/groups/HighLevelHandlersGroup.d.ts.map +1 -0
  114. package/dist/lib/handlers/handlers/groups/HighLevelHandlersGroup.js +322 -0
  115. package/dist/lib/handlers/handlers/groups/HighLevelHandlersGroup.js.map +1 -0
  116. package/dist/lib/handlers/handlers/groups/LowLevelHandlersGroup.d.ts +14 -0
  117. package/dist/lib/handlers/handlers/groups/LowLevelHandlersGroup.d.ts.map +1 -0
  118. package/dist/lib/handlers/handlers/groups/LowLevelHandlersGroup.js +1214 -0
  119. package/dist/lib/handlers/handlers/groups/LowLevelHandlersGroup.js.map +1 -0
  120. package/dist/lib/handlers/handlers/groups/ReadOnlyHandlersGroup.d.ts +14 -0
  121. package/dist/lib/handlers/handlers/groups/ReadOnlyHandlersGroup.d.ts.map +1 -0
  122. package/dist/lib/handlers/handlers/groups/ReadOnlyHandlersGroup.js +232 -0
  123. package/dist/lib/handlers/handlers/groups/ReadOnlyHandlersGroup.js.map +1 -0
  124. package/dist/lib/handlers/handlers/groups/SearchHandlersGroup.d.ts +14 -0
  125. package/dist/lib/handlers/handlers/groups/SearchHandlersGroup.d.ts.map +1 -0
  126. package/dist/lib/handlers/handlers/groups/SearchHandlersGroup.js +57 -0
  127. package/dist/lib/handlers/handlers/groups/SearchHandlersGroup.js.map +1 -0
  128. package/dist/lib/handlers/handlers/groups/SystemHandlersGroup.d.ts +14 -0
  129. package/dist/lib/handlers/handlers/groups/SystemHandlersGroup.d.ts.map +1 -0
  130. package/dist/lib/handlers/handlers/groups/SystemHandlersGroup.js +176 -0
  131. package/dist/lib/handlers/handlers/groups/SystemHandlersGroup.js.map +1 -0
  132. package/dist/lib/handlers/handlers/groups/index.d.ts +13 -0
  133. package/dist/lib/handlers/handlers/groups/index.d.ts.map +1 -0
  134. package/dist/lib/handlers/handlers/groups/index.js +21 -0
  135. package/dist/lib/handlers/handlers/groups/index.js.map +1 -0
  136. package/dist/lib/handlers/handlers/index.d.ts +14 -0
  137. package/dist/lib/handlers/handlers/index.d.ts.map +1 -0
  138. package/dist/lib/handlers/handlers/index.js +37 -0
  139. package/dist/lib/handlers/handlers/index.js.map +1 -0
  140. package/dist/lib/handlers/handlers/interfaces.d.ts +67 -0
  141. package/dist/lib/handlers/handlers/interfaces.d.ts.map +1 -0
  142. package/dist/lib/handlers/handlers/interfaces.js +3 -0
  143. package/dist/lib/handlers/handlers/interfaces.js.map +1 -0
  144. package/dist/lib/handlers/handlers/registry/CompositeHandlersRegistry.d.ts +49 -0
  145. package/dist/lib/handlers/handlers/registry/CompositeHandlersRegistry.d.ts.map +1 -0
  146. package/dist/lib/handlers/handlers/registry/CompositeHandlersRegistry.js +88 -0
  147. package/dist/lib/handlers/handlers/registry/CompositeHandlersRegistry.js.map +1 -0
  148. package/dist/lib/runtimeConfig.d.ts +20 -0
  149. package/dist/lib/runtimeConfig.d.ts.map +1 -0
  150. package/dist/lib/runtimeConfig.js +130 -0
  151. package/dist/lib/runtimeConfig.js.map +1 -0
  152. package/dist/lib/servers/BaseMcpServer.d.ts +56 -0
  153. package/dist/lib/servers/BaseMcpServer.d.ts.map +1 -0
  154. package/dist/lib/servers/BaseMcpServer.js +198 -0
  155. package/dist/lib/servers/BaseMcpServer.js.map +1 -0
  156. package/dist/lib/servers/ConnectionContext.d.ts +20 -0
  157. package/dist/lib/servers/ConnectionContext.d.ts.map +1 -0
  158. package/dist/lib/servers/ConnectionContext.js +3 -0
  159. package/dist/lib/servers/ConnectionContext.js.map +1 -0
  160. package/dist/lib/servers/SseServer.d.ts +32 -0
  161. package/dist/lib/servers/SseServer.d.ts.map +1 -0
  162. package/dist/lib/servers/SseServer.js +139 -0
  163. package/dist/lib/servers/SseServer.js.map +1 -0
  164. package/dist/lib/servers/StdioServer.d.ts +20 -0
  165. package/dist/lib/servers/StdioServer.d.ts.map +1 -0
  166. package/dist/lib/servers/StdioServer.js +32 -0
  167. package/dist/lib/servers/StdioServer.js.map +1 -0
  168. package/dist/lib/servers/StreamableHttpServer.d.ts +30 -0
  169. package/dist/lib/servers/StreamableHttpServer.d.ts.map +1 -0
  170. package/dist/lib/servers/StreamableHttpServer.js +83 -0
  171. package/dist/lib/servers/StreamableHttpServer.js.map +1 -0
  172. package/dist/lib/servers/handlers/base/BaseHandlerGroup.d.ts +36 -0
  173. package/dist/lib/servers/handlers/base/BaseHandlerGroup.d.ts.map +1 -0
  174. package/dist/lib/servers/handlers/base/BaseHandlerGroup.js +268 -0
  175. package/dist/lib/servers/handlers/base/BaseHandlerGroup.js.map +1 -0
  176. package/dist/lib/servers/handlers/examples/usage-example.d.ts +29 -0
  177. package/dist/lib/servers/handlers/examples/usage-example.d.ts.map +1 -0
  178. package/dist/lib/servers/handlers/examples/usage-example.js +119 -0
  179. package/dist/lib/servers/handlers/examples/usage-example.js.map +1 -0
  180. package/dist/lib/servers/handlers/groups/HighLevelHandlersGroup.d.ts +14 -0
  181. package/dist/lib/servers/handlers/groups/HighLevelHandlersGroup.d.ts.map +1 -0
  182. package/dist/lib/servers/handlers/groups/HighLevelHandlersGroup.js +322 -0
  183. package/dist/lib/servers/handlers/groups/HighLevelHandlersGroup.js.map +1 -0
  184. package/dist/lib/servers/handlers/groups/LowLevelHandlersGroup.d.ts +14 -0
  185. package/dist/lib/servers/handlers/groups/LowLevelHandlersGroup.d.ts.map +1 -0
  186. package/dist/lib/servers/handlers/groups/LowLevelHandlersGroup.js +1214 -0
  187. package/dist/lib/servers/handlers/groups/LowLevelHandlersGroup.js.map +1 -0
  188. package/dist/lib/servers/handlers/groups/ReadOnlyHandlersGroup.d.ts +14 -0
  189. package/dist/lib/servers/handlers/groups/ReadOnlyHandlersGroup.d.ts.map +1 -0
  190. package/dist/lib/servers/handlers/groups/ReadOnlyHandlersGroup.js +232 -0
  191. package/dist/lib/servers/handlers/groups/ReadOnlyHandlersGroup.js.map +1 -0
  192. package/dist/lib/servers/handlers/groups/SearchHandlersGroup.d.ts +14 -0
  193. package/dist/lib/servers/handlers/groups/SearchHandlersGroup.d.ts.map +1 -0
  194. package/dist/lib/servers/handlers/groups/SearchHandlersGroup.js +57 -0
  195. package/dist/lib/servers/handlers/groups/SearchHandlersGroup.js.map +1 -0
  196. package/dist/lib/servers/handlers/groups/SystemHandlersGroup.d.ts +14 -0
  197. package/dist/lib/servers/handlers/groups/SystemHandlersGroup.d.ts.map +1 -0
  198. package/dist/lib/servers/handlers/groups/SystemHandlersGroup.js +176 -0
  199. package/dist/lib/servers/handlers/groups/SystemHandlersGroup.js.map +1 -0
  200. package/dist/lib/servers/handlers/groups/index.d.ts +13 -0
  201. package/dist/lib/servers/handlers/groups/index.d.ts.map +1 -0
  202. package/dist/lib/servers/handlers/groups/index.js +21 -0
  203. package/dist/lib/servers/handlers/groups/index.js.map +1 -0
  204. package/dist/lib/servers/handlers/index.d.ts +14 -0
  205. package/dist/lib/servers/handlers/index.d.ts.map +1 -0
  206. package/dist/lib/servers/handlers/index.js +37 -0
  207. package/dist/lib/servers/handlers/index.js.map +1 -0
  208. package/dist/lib/servers/handlers/interfaces.d.ts +67 -0
  209. package/dist/lib/servers/handlers/interfaces.d.ts.map +1 -0
  210. package/dist/lib/servers/handlers/interfaces.js +3 -0
  211. package/dist/lib/servers/handlers/interfaces.js.map +1 -0
  212. package/dist/lib/servers/handlers/registry/CompositeHandlersRegistry.d.ts +49 -0
  213. package/dist/lib/servers/handlers/registry/CompositeHandlersRegistry.d.ts.map +1 -0
  214. package/dist/lib/servers/handlers/registry/CompositeHandlersRegistry.js +88 -0
  215. package/dist/lib/servers/handlers/registry/CompositeHandlersRegistry.js.map +1 -0
  216. package/dist/lib/servers/index.d.ts +5 -0
  217. package/dist/lib/servers/index.d.ts.map +1 -0
  218. package/dist/lib/servers/index.js +21 -0
  219. package/dist/lib/servers/index.js.map +1 -0
  220. package/dist/lib/servers/launcher.d.ts +2 -0
  221. package/dist/lib/servers/launcher.d.ts.map +1 -0
  222. package/dist/lib/servers/launcher.js +123 -0
  223. package/dist/lib/servers/launcher.js.map +1 -0
  224. package/dist/lib/servers/mcp_handlers.d.ts +25 -0
  225. package/dist/lib/servers/mcp_handlers.d.ts.map +1 -0
  226. package/dist/lib/servers/mcp_handlers.js +822 -0
  227. package/dist/lib/servers/mcp_handlers.js.map +1 -0
  228. package/dist/lib/servers/mcp_server.d.ts +7 -0
  229. package/dist/lib/servers/mcp_server.d.ts.map +1 -0
  230. package/dist/lib/servers/mcp_server.js +12 -0
  231. package/dist/lib/servers/utils.d.ts +42 -0
  232. package/dist/lib/servers/utils.d.ts.map +1 -0
  233. package/dist/lib/servers/utils.js +620 -0
  234. package/dist/lib/servers/utils.js.map +1 -0
  235. package/dist/lib/servers/v2/__tests__/unit/McpServer.test.d.ts +7 -0
  236. package/dist/lib/servers/v2/__tests__/unit/McpServer.test.d.ts.map +1 -0
  237. package/dist/lib/servers/v2/__tests__/unit/McpServer.test.js +218 -0
  238. package/dist/lib/servers/v2/connection/LocalConnectionProvider.d.ts +20 -0
  239. package/dist/lib/servers/v2/connection/LocalConnectionProvider.d.ts.map +1 -0
  240. package/dist/lib/servers/v2/connection/LocalConnectionProvider.js +70 -0
  241. package/dist/lib/servers/v2/connection/RemoteConnectionProvider.d.ts +21 -0
  242. package/dist/lib/servers/v2/connection/RemoteConnectionProvider.d.ts.map +1 -0
  243. package/dist/lib/servers/v2/connection/RemoteConnectionProvider.js +36 -0
  244. package/dist/lib/servers/v2/connection/index.d.ts +6 -0
  245. package/dist/lib/servers/v2/connection/index.d.ts.map +1 -0
  246. package/dist/lib/servers/v2/connection/index.js +10 -0
  247. package/dist/lib/servers/v2/factory/AuthBrokerFactory.d.ts +29 -0
  248. package/dist/lib/servers/v2/factory/AuthBrokerFactory.d.ts.map +1 -0
  249. package/dist/lib/servers/v2/factory/AuthBrokerFactory.js +79 -0
  250. package/dist/lib/servers/v2/factory/LocalModeFactory.d.ts +38 -0
  251. package/dist/lib/servers/v2/factory/LocalModeFactory.d.ts.map +1 -0
  252. package/dist/lib/servers/v2/factory/LocalModeFactory.js +38 -0
  253. package/dist/lib/servers/v2/factory/index.d.ts +6 -0
  254. package/dist/lib/servers/v2/factory/index.d.ts.map +1 -0
  255. package/dist/lib/servers/v2/factory/index.js +10 -0
  256. package/dist/lib/servers/v2/index.d.ts +9 -0
  257. package/dist/lib/servers/v2/index.d.ts.map +1 -0
  258. package/dist/lib/servers/v2/index.js +27 -0
  259. package/dist/lib/servers/v2/index.js.map +1 -0
  260. package/dist/lib/servers/v2/interfaces/connection.d.ts +188 -0
  261. package/dist/lib/servers/v2/interfaces/connection.d.ts.map +1 -0
  262. package/dist/lib/servers/v2/interfaces/connection.js +7 -0
  263. package/dist/lib/servers/v2/interfaces/index.d.ts +10 -0
  264. package/dist/lib/servers/v2/interfaces/index.d.ts.map +1 -0
  265. package/dist/lib/servers/v2/interfaces/index.js +25 -0
  266. package/dist/lib/servers/v2/interfaces/protocol.d.ts +30 -0
  267. package/dist/lib/servers/v2/interfaces/protocol.d.ts.map +1 -0
  268. package/dist/lib/servers/v2/interfaces/protocol.js +7 -0
  269. package/dist/lib/servers/v2/interfaces/session.d.ts +98 -0
  270. package/dist/lib/servers/v2/interfaces/session.d.ts.map +1 -0
  271. package/dist/lib/servers/v2/interfaces/session.js +7 -0
  272. package/dist/lib/servers/v2/interfaces/transport.d.ts +88 -0
  273. package/dist/lib/servers/v2/interfaces/transport.d.ts.map +1 -0
  274. package/dist/lib/servers/v2/interfaces/transport.js +7 -0
  275. package/dist/lib/servers/v2/protocol/ProtocolHandler.d.ts +21 -0
  276. package/dist/lib/servers/v2/protocol/ProtocolHandler.d.ts.map +1 -0
  277. package/dist/lib/servers/v2/protocol/ProtocolHandler.js +77 -0
  278. package/dist/lib/servers/v2/protocol/index.d.ts +5 -0
  279. package/dist/lib/servers/v2/protocol/index.d.ts.map +1 -0
  280. package/dist/lib/servers/v2/protocol/index.js +8 -0
  281. package/dist/lib/servers/v2/server/McpServer.d.ts +76 -0
  282. package/dist/lib/servers/v2/server/McpServer.d.ts.map +1 -0
  283. package/dist/lib/servers/v2/server/McpServer.js +243 -0
  284. package/dist/lib/servers/v2/server/index.d.ts +5 -0
  285. package/dist/lib/servers/v2/server/index.d.ts.map +1 -0
  286. package/dist/lib/servers/v2/server/index.js +8 -0
  287. package/dist/lib/servers/v2/session/SessionManager.d.ts +40 -0
  288. package/dist/lib/servers/v2/session/SessionManager.d.ts.map +1 -0
  289. package/dist/lib/servers/v2/session/SessionManager.js +136 -0
  290. package/dist/lib/servers/v2/session/index.d.ts +5 -0
  291. package/dist/lib/servers/v2/session/index.d.ts.map +1 -0
  292. package/dist/lib/servers/v2/session/index.js +8 -0
  293. package/dist/lib/servers/v2/transports/SseTransport.d.ts +49 -0
  294. package/dist/lib/servers/v2/transports/SseTransport.d.ts.map +1 -0
  295. package/dist/lib/servers/v2/transports/SseTransport.js +89 -0
  296. package/dist/lib/servers/v2/transports/StdioTransport.d.ts +31 -0
  297. package/dist/lib/servers/v2/transports/StdioTransport.d.ts.map +1 -0
  298. package/dist/lib/servers/v2/transports/StdioTransport.js +91 -0
  299. package/dist/lib/servers/v2/transports/StreamableHttpTransport.d.ts +51 -0
  300. package/dist/lib/servers/v2/transports/StreamableHttpTransport.d.ts.map +1 -0
  301. package/dist/lib/servers/v2/transports/StreamableHttpTransport.js +147 -0
  302. package/dist/lib/servers/v2/transports/index.d.ts +7 -0
  303. package/dist/lib/servers/v2/transports/index.d.ts.map +1 -0
  304. package/dist/lib/servers/v2/transports/index.js +12 -0
  305. package/dist/lib/servers/v2/types/common.d.ts +17 -0
  306. package/dist/lib/servers/v2/types/common.d.ts.map +1 -0
  307. package/dist/lib/servers/v2/types/common.js +6 -0
  308. package/dist/lib/servers/v2/types/common.js.map +1 -0
  309. package/dist/lib/servers/v2/types/index.d.ts +8 -0
  310. package/dist/lib/servers/v2/types/index.d.ts.map +1 -0
  311. package/dist/lib/servers/v2/types/index.js +24 -0
  312. package/dist/lib/servers/v2/types/index.js.map +1 -0
  313. package/dist/lib/servers/v2/types/transport.d.ts +28 -0
  314. package/dist/lib/servers/v2/types/transport.d.ts.map +1 -0
  315. package/dist/lib/servers/v2/types/transport.js +8 -0
  316. package/dist/lib/servers/v2/types/transport.js.map +1 -0
  317. package/dist/lib/servers/v2/utils/StdioLogger.d.ts +17 -0
  318. package/dist/lib/servers/v2/utils/StdioLogger.d.ts.map +1 -0
  319. package/dist/lib/servers/v2/utils/StdioLogger.js +33 -0
  320. package/dist/lib/servers/v2/utils/StdioLogger.js.map +1 -0
  321. package/dist/lib/stores/UnixFileServiceKeyStore.d.ts +42 -0
  322. package/dist/lib/stores/UnixFileServiceKeyStore.d.ts.map +1 -0
  323. package/dist/lib/stores/UnixFileServiceKeyStore.js +53 -0
  324. package/dist/lib/stores/UnixFileSessionStore.d.ts +54 -0
  325. package/dist/lib/stores/UnixFileSessionStore.d.ts.map +1 -0
  326. package/dist/lib/stores/UnixFileSessionStore.js +70 -0
  327. package/dist/lib/stores/WindowsFileServiceKeyStore.d.ts +42 -0
  328. package/dist/lib/stores/WindowsFileServiceKeyStore.d.ts.map +1 -0
  329. package/dist/lib/stores/WindowsFileServiceKeyStore.js +53 -0
  330. package/dist/lib/stores/WindowsFileSessionStore.d.ts +54 -0
  331. package/dist/lib/stores/WindowsFileSessionStore.d.ts.map +1 -0
  332. package/dist/lib/stores/WindowsFileSessionStore.js +70 -0
  333. package/dist/lib/toolsRegistry.d.ts +13 -0
  334. package/dist/lib/toolsRegistry.d.ts.map +1 -0
  335. package/dist/lib/toolsRegistry.js +410 -0
  336. package/dist/lib/transportConfig.d.ts +27 -0
  337. package/dist/lib/transportConfig.d.ts.map +1 -0
  338. package/dist/lib/transportConfig.js +124 -0
  339. package/dist/lib/transports/stdio.d.ts +10 -0
  340. package/dist/lib/transports/stdio.d.ts.map +1 -0
  341. package/dist/lib/transports/stdio.js +73 -0
  342. package/dist/lib/utils.d.ts +37 -0
  343. package/dist/lib/utils.d.ts.map +1 -1
  344. package/dist/lib/utils.js +83 -0
  345. package/dist/lib/utils.js.map +1 -1
  346. package/dist/lib/yamlConfig.d.ts +59 -0
  347. package/dist/lib/yamlConfig.d.ts.map +1 -0
  348. package/dist/lib/yamlConfig.js +349 -0
  349. package/dist/lib/yamlConfig.js.map +1 -0
  350. package/dist/server/launcher.js +49 -0
  351. package/dist/server/launcher.js.map +1 -1
  352. package/dist/server/v1/AuthBrokerConfig.d.ts +19 -0
  353. package/dist/server/v1/AuthBrokerConfig.d.ts.map +1 -0
  354. package/dist/server/v1/AuthBrokerConfig.js +44 -0
  355. package/dist/server/v1/AuthBrokerConfig.js.map +1 -0
  356. package/dist/server/v1/IServerConfig.d.ts +17 -0
  357. package/dist/server/v1/IServerConfig.d.ts.map +1 -0
  358. package/dist/server/v1/IServerConfig.js +7 -0
  359. package/dist/server/v1/IServerConfig.js.map +1 -0
  360. package/dist/server/v1/ServerConfig.d.ts +16 -0
  361. package/dist/server/v1/ServerConfig.d.ts.map +1 -0
  362. package/dist/server/v1/ServerConfig.js +6 -0
  363. package/dist/server/v1/ServerConfig.js.map +1 -0
  364. package/dist/server/v1/embeddable-server.d.ts +89 -0
  365. package/dist/server/v1/embeddable-server.d.ts.map +1 -0
  366. package/dist/server/v1/embeddable-server.js +83 -0
  367. package/dist/server/v1/embeddable-server.js.map +1 -0
  368. package/dist/server/v1/index.d.ts +30 -0
  369. package/dist/server/v1/index.d.ts.map +1 -0
  370. package/dist/server/v1/index.js +65 -0
  371. package/dist/server/v1/index.js.map +1 -0
  372. package/dist/server/v1/legacy-server.d.ts +141 -0
  373. package/dist/server/v1/legacy-server.d.ts.map +1 -0
  374. package/dist/server/v1/legacy-server.js +2099 -0
  375. package/dist/server/v1/legacy-server.js.map +1 -0
  376. package/dist/server/v1/mcp_handlers.d.ts +19 -0
  377. package/dist/server/v1/mcp_handlers.d.ts.map +1 -0
  378. package/dist/server/v1/mcp_handlers.js +85 -0
  379. package/dist/server/v1/mcp_handlers.js.map +1 -0
  380. package/dist/server/v2/AuthBrokerConfig.d.ts +22 -0
  381. package/dist/server/v2/AuthBrokerConfig.d.ts.map +1 -0
  382. package/dist/server/v2/AuthBrokerConfig.js +46 -0
  383. package/dist/server/v2/AuthBrokerConfig.js.map +1 -0
  384. package/dist/server/v2/BaseMcpServer.d.ts +67 -0
  385. package/dist/server/v2/BaseMcpServer.d.ts.map +1 -0
  386. package/dist/server/v2/BaseMcpServer.js +273 -0
  387. package/dist/server/v2/BaseMcpServer.js.map +1 -0
  388. package/dist/server/v2/ConnectionContext.d.ts +20 -0
  389. package/dist/server/v2/ConnectionContext.d.ts.map +1 -0
  390. package/dist/server/v2/ConnectionContext.js +3 -0
  391. package/dist/server/v2/ConnectionContext.js.map +1 -0
  392. package/dist/server/v2/IHttpApplication.d.ts +42 -0
  393. package/dist/server/v2/IHttpApplication.d.ts.map +1 -0
  394. package/dist/server/v2/IHttpApplication.js +3 -0
  395. package/dist/server/v2/IHttpApplication.js.map +1 -0
  396. package/dist/server/v2/IServerConfig.d.ts +15 -0
  397. package/dist/server/v2/IServerConfig.d.ts.map +1 -0
  398. package/dist/server/v2/IServerConfig.js +7 -0
  399. package/dist/server/v2/IServerConfig.js.map +1 -0
  400. package/dist/server/v2/ServerConfig.d.ts +16 -0
  401. package/dist/server/v2/ServerConfig.d.ts.map +1 -0
  402. package/dist/server/v2/ServerConfig.js +6 -0
  403. package/dist/server/v2/ServerConfig.js.map +1 -0
  404. package/dist/server/v2/SseServer.d.ts +95 -0
  405. package/dist/server/v2/SseServer.d.ts.map +1 -0
  406. package/dist/server/v2/SseServer.js +217 -0
  407. package/dist/server/v2/SseServer.js.map +1 -0
  408. package/dist/server/v2/StdioServer.d.ts +20 -0
  409. package/dist/server/v2/StdioServer.d.ts.map +1 -0
  410. package/dist/server/v2/StdioServer.js +32 -0
  411. package/dist/server/v2/StdioServer.js.map +1 -0
  412. package/dist/server/v2/StreamableHttpServer.d.ts +94 -0
  413. package/dist/server/v2/StreamableHttpServer.d.ts.map +1 -0
  414. package/dist/server/v2/StreamableHttpServer.js +170 -0
  415. package/dist/server/v2/StreamableHttpServer.js.map +1 -0
  416. package/dist/server/v2/index.d.ts +6 -0
  417. package/dist/server/v2/index.d.ts.map +1 -0
  418. package/dist/server/v2/index.js +22 -0
  419. package/dist/server/v2/index.js.map +1 -0
  420. package/dist/server/v2/launcher.d.ts +2 -0
  421. package/dist/server/v2/launcher.d.ts.map +1 -0
  422. package/dist/server/v2/launcher.js +181 -0
  423. package/dist/server/v2/launcher.js.map +1 -0
  424. package/dist/server/v2/mcp_handlers.d.ts +25 -0
  425. package/dist/server/v2/mcp_handlers.d.ts.map +1 -0
  426. package/dist/server/v2/mcp_handlers.js +828 -0
  427. package/dist/server/v2/mcp_handlers.js.map +1 -0
  428. package/dist/server/v2/utils.d.ts +42 -0
  429. package/dist/server/v2/utils.d.ts.map +1 -0
  430. package/dist/server/v2/utils.js +620 -0
  431. package/dist/server/v2/utils.js.map +1 -0
  432. package/dist/tools/test-v2-server-stdio-compiled.js +132 -0
  433. package/dist/utils/lockStateManager.d.ts +2 -12
  434. package/dist/utils/lockStateManager.d.ts.map +1 -1
  435. package/dist/utils/lockStateManager.js +4 -63
  436. package/dist/utils/lockStateManager.js.map +1 -1
  437. package/docs/user-guide/AVAILABLE_TOOLS.md +1 -1
  438. package/package.json +1 -1
package/dist/index.js ADDED
@@ -0,0 +1,2979 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ // Simple stdio mode detection (like reference implementation)
4
+ // No output suppression needed - dotenv removed, manual .env parsing used
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
39
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
40
+ };
41
+ var __importDefault = (this && this.__importDefault) || function (mod) {
42
+ return (mod && mod.__esModule) ? mod : { "default": mod };
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.mcp_abap_adt_server = exports.getConfig = exports.setSapConfigOverride = void 0;
46
+ exports.setAbapConnectionOverride = setAbapConnectionOverride;
47
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
48
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
49
+ const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
50
+ const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
51
+ const mcp_handlers_1 = require("./lib/servers/mcp_handlers");
52
+ const path_1 = __importDefault(require("path"));
53
+ const os = __importStar(require("os"));
54
+ // dotenv removed - using manual .env parsing for all modes to avoid stdout pollution
55
+ const http_1 = require("http");
56
+ const crypto_1 = require("crypto");
57
+ const header_validator_1 = require("@mcp-abap-adt/header-validator");
58
+ const interfaces_1 = require("@mcp-abap-adt/interfaces");
59
+ const authBrokerFactory_1 = require("./lib/authBrokerFactory");
60
+ const platformPaths_1 = require("./lib/stores/platformPaths");
61
+ const runtimeConfig_1 = require("./lib/runtimeConfig");
62
+ const yamlConfig_1 = require("./lib/yamlConfig");
63
+ // Import handler functions
64
+ // Import handler functions
65
+ // New low-level handlers imports
66
+ // Import shared utility functions and types
67
+ const utils_1 = require("./lib/utils");
68
+ const config_js_1 = require("./lib/config.js");
69
+ Object.defineProperty(exports, "getConfig", { enumerable: true, get: function () { return config_js_1.getConfig; } });
70
+ Object.defineProperty(exports, "setSapConfigOverride", { enumerable: true, get: function () { return config_js_1.setSapConfigOverride; } });
71
+ const connection_1 = require("@mcp-abap-adt/connection");
72
+ const loggerAdapter_1 = require("./lib/loggerAdapter");
73
+ // Import logger
74
+ const logger_1 = require("./lib/logger");
75
+ // import { defaultLogger as logger } from "@mcp-abap-adt/logger";
76
+ // Import tool registry
77
+ // Import TOOL_DEFINITION from handlers
78
+ // New low-level handlers TOOL_DEFINITION imports
79
+ // --- ENV FILE LOADING LOGIC ---
80
+ const fs_1 = __importDefault(require("fs"));
81
+ /**
82
+ * Display help message
83
+ */
84
+ function showHelp() {
85
+ const help = `
86
+ MCP ABAP ADT Server - SAP ABAP Development Tools MCP Integration
87
+
88
+ USAGE:
89
+ mcp-abap-adt [options]
90
+
91
+ DESCRIPTION:
92
+ MCP server for interacting with SAP ABAP systems via ADT (ABAP Development Tools).
93
+ Supports multiple transport modes: HTTP (default), stdio, and SSE.
94
+
95
+ TRANSPORT MODES:
96
+ Default: stdio (for MCP clients like Cline, Cursor, Claude Desktop)
97
+ HTTP: --transport=http (for web interfaces, receives config via HTTP headers)
98
+ SSE: --transport=sse
99
+
100
+ OPTIONS:
101
+ --help Show this help message
102
+ --config=<path> Path to YAML configuration file
103
+ If file doesn't exist, generates a template and exits
104
+ Command-line arguments override YAML values
105
+ Example: --config=config.yaml
106
+
107
+ YAML CONFIGURATION:
108
+ Instead of passing many command-line arguments, you can use a YAML config file:
109
+
110
+ mcp-abap-adt --config=config.yaml
111
+
112
+ If the file doesn't exist, a template will be generated automatically and the command will exit.
113
+ Edit the template to configure your server settings, then run the command again.
114
+
115
+ The YAML file is validated on load - invalid configurations will cause the command to exit with an error.
116
+
117
+ Command-line arguments always override YAML values for flexibility.
118
+
119
+ See docs/configuration/YAML_CONFIG.md for detailed documentation.
120
+
121
+ ENVIRONMENT FILE:
122
+ --env=<path> Path to .env file (uses .env instead of auth-broker)
123
+ --env <path> Alternative syntax for --env
124
+ --auth-broker Force use of auth-broker (service keys) instead of .env file
125
+ Ignores .env file even if present in current directory
126
+ By default, .env in current directory is used automatically (if exists)
127
+ --auth-broker-path=<path> Custom path for auth-broker service keys and sessions
128
+ Creates service-keys and sessions subdirectories in this path
129
+ Example: --auth-broker-path=~/prj/tmp/
130
+ This will use ~/prj/tmp/service-keys and ~/prj/tmp/sessions
131
+ --mcp=<destination> Default MCP destination name (overrides x-mcp-destination header)
132
+ If specified, this destination will be used when x-mcp-destination
133
+ header is not provided in the request
134
+ Example: --mcp=TRIAL
135
+ This allows using auth-broker with stdio and SSE transports
136
+ When --mcp is specified, .env file is not loaded automatically
137
+ (even if it exists in current directory)
138
+
139
+ TRANSPORT SELECTION:
140
+ --transport=<type> Transport type: stdio|http|streamable-http|sse
141
+ Default: stdio (for MCP clients)
142
+ Shortcuts: --http (same as --transport=http)
143
+ --sse (same as --transport=sse)
144
+ --stdio (same as --transport=stdio)
145
+
146
+ HTTP/STREAMABLE-HTTP OPTIONS:
147
+ --http-port=<port> HTTP server port (default: 3000)
148
+ --http-host=<host> HTTP server host (default: 127.0.0.1 for local only, use 0.0.0.0 for all interfaces)
149
+ Security: When listening on 0.0.0.0, client must provide all connection headers
150
+ Server will not use default destination for non-local connections
151
+ --http-json-response Enable JSON response format
152
+ --http-allowed-origins=<list> Comma-separated allowed origins for CORS
153
+ Example: --http-allowed-origins=http://localhost:3000,https://example.com
154
+ --http-allowed-hosts=<list> Comma-separated allowed hosts
155
+ --http-enable-dns-protection Enable DNS rebinding protection
156
+
157
+ SSE (SERVER-SENT EVENTS) OPTIONS:
158
+ --sse-port=<port> SSE server port (default: 3001)
159
+ --sse-host=<host> SSE server host (default: 127.0.0.1 for local only, use 0.0.0.0 for all interfaces)
160
+ Security: When listening on 0.0.0.0, client must provide all connection headers
161
+ Server will not use default destination for non-local connections
162
+ --sse-allowed-origins=<list> Comma-separated allowed origins for CORS
163
+ Example: --sse-allowed-origins=http://localhost:3000
164
+ --sse-allowed-hosts=<list> Comma-separated allowed hosts
165
+ --sse-enable-dns-protection Enable DNS rebinding protection
166
+
167
+ ENVIRONMENT VARIABLES:
168
+ MCP_ENV_PATH Path to .env file
169
+ MCP_SKIP_ENV_LOAD Skip automatic .env loading (true|false)
170
+ MCP_SKIP_AUTO_START Skip automatic server start (true|false)
171
+ MCP_TRANSPORT Transport type (stdio|http|sse)
172
+ Default: stdio if not specified
173
+ MCP_HTTP_PORT Default HTTP port (default: 3000)
174
+ MCP_HTTP_HOST Default HTTP host (default: 127.0.0.1 for local only, use 0.0.0.0 for all interfaces)
175
+ MCP_HTTP_ENABLE_JSON_RESPONSE Enable JSON responses (true|false)
176
+ MCP_HTTP_ALLOWED_ORIGINS Allowed CORS origins (comma-separated)
177
+ MCP_HTTP_ALLOWED_HOSTS Allowed hosts (comma-separated)
178
+ MCP_HTTP_ENABLE_DNS_PROTECTION Enable DNS protection (true|false)
179
+ MCP_SSE_PORT Default SSE port (default: 3001)
180
+ MCP_SSE_HOST Default SSE host (default: 127.0.0.1 for local only, use 0.0.0.0 for all interfaces)
181
+ MCP_SSE_ALLOWED_ORIGINS Allowed CORS origins for SSE (comma-separated)
182
+ MCP_SSE_ALLOWED_HOSTS Allowed hosts for SSE (comma-separated)
183
+ MCP_SSE_ENABLE_DNS_PROTECTION Enable DNS protection for SSE (true|false)
184
+ AUTH_BROKER_PATH Custom paths for service keys and sessions
185
+ Unix: colon-separated (e.g., /path1:/path2)
186
+ Windows: semicolon-separated (e.g., C:\\path1;C:\\path2)
187
+ If not set, uses platform defaults:
188
+ Unix: ~/.config/mcp-abap-adt/service-keys
189
+ Windows: %USERPROFILE%\\Documents\\mcp-abap-adt\\service-keys
190
+ DEBUG_AUTH_LOG Enable debug logging for auth-broker (true|false)
191
+ Default: false (only info messages shown)
192
+ When true: shows detailed debug messages
193
+ DEBUG_AUTH_BROKER Alias for DEBUG_AUTH_LOG (true|false)
194
+ Same as DEBUG_AUTH_LOG - enables debug logging for auth-broker
195
+ When true: automatically sets DEBUG_AUTH_LOG=true
196
+ DEBUG_HTTP_REQUESTS Enable logging of HTTP requests and MCP calls (true|false)
197
+ Default: false
198
+ When true: logs all incoming HTTP requests, methods, URLs,
199
+ headers (sensitive data redacted), and MCP JSON-RPC calls
200
+ Also enabled by DEBUG_CONNECTORS=true
201
+ DEBUG_CONNECTORS Enable debug logging for connection layer (true|false)
202
+ Default: false
203
+ When true: shows HTTP requests, CSRF tokens, cookies,
204
+ session management, and connection details
205
+ Also enables DEBUG_HTTP_REQUESTS automatically
206
+ DEBUG_HANDLERS Enable debug logging for MCP handlers (true|false)
207
+ Default: false
208
+ When true: shows handler entry/exit, session state,
209
+ lock handles, property validation
210
+ DEBUG_CONNECTION_MANAGER Enable debug logging for connection manager (true|false)
211
+ Default: false
212
+ When true: shows connection cache operations
213
+
214
+ SAP CONNECTION (.env file):
215
+ SAP_URL SAP system URL (required)
216
+ Example: https://your-system.sap.com
217
+ SAP_CLIENT SAP client number (required)
218
+ Example: 100
219
+ SAP_AUTH_TYPE Authentication type: basic|jwt (default: basic)
220
+ SAP_USERNAME SAP username (required for basic auth)
221
+ SAP_PASSWORD SAP password (required for basic auth)
222
+ SAP_JWT_TOKEN JWT token (required for jwt auth)
223
+
224
+ GENERATING .ENV FROM SERVICE KEY (JWT Authentication):
225
+ To generate .env file from SAP BTP service key JSON file, install the
226
+ connection package globally:
227
+
228
+ npm install -g @mcp-abap-adt/connection
229
+
230
+ Then use the sap-abap-auth command:
231
+
232
+ sap-abap-auth auth -k path/to/service-key.json
233
+
234
+ This will create/update .env file with JWT tokens and connection details.
235
+
236
+ EXAMPLES:
237
+ # Default stdio mode (for MCP clients, requires .env file or --mcp parameter)
238
+ mcp-abap-adt
239
+
240
+ # HTTP mode (for web interfaces)
241
+ mcp-abap-adt --transport=http
242
+
243
+ # HTTP server on custom port, localhost only (default)
244
+ mcp-abap-adt --transport=http --http-port=8080
245
+
246
+ # HTTP server accepting connections from all interfaces (less secure)
247
+ mcp-abap-adt --transport=http --http-host=0.0.0.0 --http-port=8080
248
+
249
+ # Use YAML configuration file
250
+ mcp-abap-adt --config=config.yaml
251
+
252
+ # Use stdio mode with --mcp parameter (uses auth-broker, skips .env file)
253
+ mcp-abap-adt --mcp=TRIAL
254
+
255
+ # Default: uses .env from current directory if exists, otherwise auth-broker
256
+ mcp-abap-adt
257
+
258
+ # Force use of auth-broker (service keys), ignore .env file even if exists
259
+ mcp-abap-adt --auth-broker
260
+
261
+ # Use custom path for auth-broker (creates service-keys and sessions subdirectories)
262
+ mcp-abap-adt --auth-broker --auth-broker-path=~/prj/tmp/
263
+
264
+ # Use SSE transport with --mcp parameter (allows auth-broker with SSE transport)
265
+ mcp-abap-adt --transport=sse --mcp=TRIAL
266
+
267
+ # Use .env file from custom path
268
+ mcp-abap-adt --env=/path/to/my.env
269
+
270
+ # Start HTTP server with CORS enabled
271
+ mcp-abap-adt --transport=http --http-port=3000 \\
272
+ --http-allowed-origins=http://localhost:3000,https://example.com
273
+
274
+ # Start SSE server on custom port
275
+ mcp-abap-adt --transport=sse --sse-port=3001
276
+
277
+ # Start SSE server with CORS and DNS protection
278
+ mcp-abap-adt --transport=sse --sse-port=3001 \\
279
+ --sse-allowed-origins=http://localhost:3000 \\
280
+ --sse-enable-dns-protection
281
+
282
+ # Using shortcuts
283
+ mcp-abap-adt --http --http-port=8080
284
+ mcp-abap-adt --sse --sse-port=3001
285
+
286
+ QUICK REFERENCE:
287
+ Transport types:
288
+ http - HTTP StreamableHTTP transport (default)
289
+ streamable-http - Same as http
290
+ stdio - Standard input/output (for MCP clients, requires .env file or --mcp parameter)
291
+ sse - Server-Sent Events transport
292
+
293
+ Common use cases:
294
+ Web interfaces (HTTP): mcp-abap-adt (default, no .env needed)
295
+ MCP clients (Cline, Cursor): mcp-abap-adt --transport=stdio
296
+ MCP clients with auth-broker: mcp-abap-adt --transport=stdio --mcp=TRIAL (skips .env)
297
+ Web interfaces (SSE): mcp-abap-adt --transport=sse --sse-port=3001
298
+ SSE with auth-broker: mcp-abap-adt --transport=sse --mcp=TRIAL (skips .env)
299
+
300
+ DOCUMENTATION:
301
+ https://github.com/fr0ster/mcp-abap-adt
302
+ Installation: docs/installation/INSTALLATION.md
303
+ Configuration: docs/user-guide/CLIENT_CONFIGURATION.md
304
+ Available Tools: docs/user-guide/AVAILABLE_TOOLS.md
305
+
306
+ AUTHENTICATION:
307
+ For JWT authentication with SAP BTP service keys:
308
+ 1. Install: npm install -g @mcp-abap-adt/connection
309
+ 2. Run: sap-abap-auth auth -k path/to/service-key.json
310
+ 3. This generates .env file with JWT tokens automatically
311
+
312
+ SERVICE KEYS (Destination-Based Authentication):
313
+ The server supports destination-based authentication using service keys stored locally.
314
+ This allows you to configure authentication once per destination and reuse it.
315
+
316
+ IMPORTANT: Auth-broker (service keys) is only available for HTTP/streamable-http transport.
317
+ For stdio and SSE transports, use .env file instead.
318
+
319
+ How to Save Service Keys:
320
+
321
+ Linux:
322
+ 1. Create service keys directory:
323
+ mkdir -p ~/.config/mcp-abap-adt/service-keys
324
+
325
+ 2. Download service key from SAP BTP (from the corresponding service instance)
326
+ and copy it to: ~/.config/mcp-abap-adt/service-keys/{destination}.json
327
+ (e.g., TRIAL.json - the filename without .json extension becomes the destination name)
328
+
329
+ Storage locations:
330
+ Service keys: ~/.config/mcp-abap-adt/service-keys/{destination}.json
331
+ Sessions: ~/.config/mcp-abap-adt/sessions/{destination}.env
332
+
333
+ macOS:
334
+ 1. Create service keys directory:
335
+ mkdir -p ~/.config/mcp-abap-adt/service-keys
336
+
337
+ 2. Download service key from SAP BTP (from the corresponding service instance)
338
+ and copy it to: ~/.config/mcp-abap-adt/service-keys/{destination}.json
339
+ (e.g., TRIAL.json - the filename without .json extension becomes the destination name)
340
+
341
+ Storage locations:
342
+ Service keys: ~/.config/mcp-abap-adt/service-keys/{destination}.json
343
+ Sessions: ~/.config/mcp-abap-adt/sessions/{destination}.env
344
+
345
+ Windows:
346
+ 1. Create service keys directory (PowerShell):
347
+ New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\\Documents\\mcp-abap-adt\\service-keys"
348
+
349
+ 2. Download service key from SAP BTP (from the corresponding service instance)
350
+ and copy it to: %USERPROFILE%\\Documents\\mcp-abap-adt\\service-keys\\{destination}.json
351
+ (e.g., TRIAL.json - the filename without .json extension becomes the destination name)
352
+
353
+ Or using Command Prompt (cmd):
354
+ mkdir "%USERPROFILE%\\Documents\\mcp-abap-adt\\service-keys"
355
+ (Then copy the downloaded service key file to this directory)
356
+
357
+ Storage locations:
358
+ Service keys: %USERPROFILE%\\Documents\\mcp-abap-adt\\service-keys\\{destination}.json
359
+ Sessions: %USERPROFILE%\\Documents\\mcp-abap-adt\\sessions\\{destination}.env
360
+
361
+ Fallback: Server also searches in current working directory (where server is launched)
362
+
363
+ Service Key:
364
+ Download the service key JSON file from SAP BTP (from the corresponding service instance)
365
+ and save it as {destination}.json (e.g., TRIAL.json).
366
+ The filename without .json extension becomes the destination name (case-sensitive).
367
+
368
+ Using Destinations:
369
+ In HTTP headers, use:
370
+ x-sap-destination: TRIAL (for SAP Cloud, URL derived from service key)
371
+ x-mcp-destination: TRIAL (for MCP destinations, URL derived from service key)
372
+
373
+ The destination name must exactly match the service key filename (without .json extension, case-sensitive).
374
+
375
+ Example Cline configuration (~/.cline/mcp.json):
376
+ {
377
+ "mcpServers": {
378
+ "mcp-abap-adt": {
379
+ "command": "npx",
380
+ "args": ["-y", "@mcp-abap-adt/server", "--transport=http", "--http-port=3000"],
381
+ "env": {}
382
+ }
383
+ }
384
+ }
385
+
386
+ Then in Cline, use destination in requests:
387
+ Headers: x-sap-destination: TRIAL
388
+
389
+ First-Time Authentication:
390
+ - Server reads service key from {destination}.json
391
+ - Opens browser for OAuth2 authentication (if no valid session exists)
392
+ - Saves tokens to {destination}.env for future use
393
+ - Subsequent requests use cached tokens automatically
394
+
395
+ Automatic Token Management:
396
+ - Validates tokens before use
397
+ - Refreshes expired tokens using refresh tokens
398
+ - Caches valid tokens for performance
399
+ - Falls back to browser authentication if refresh fails
400
+
401
+ Custom Paths:
402
+ Set AUTH_BROKER_PATH environment variable to override default paths:
403
+ Linux/macOS: export AUTH_BROKER_PATH="/custom/path:/another/path"
404
+ Windows: set AUTH_BROKER_PATH=C:\\custom\\path;C:\\another\\path
405
+
406
+ Or use --auth-broker-path command-line option:
407
+ mcp-abap-adt --auth-broker --auth-broker-path=~/prj/tmp/
408
+ This creates service-keys and sessions subdirectories in the specified path.
409
+
410
+ For more details, see: docs/user-guide/CLIENT_CONFIGURATION.md#destination-based-authentication
411
+
412
+ `;
413
+ console.log(help);
414
+ process.exit(0);
415
+ }
416
+ // Check for version/help flags
417
+ if (process.argv.includes("--version") || process.argv.includes("-v")) {
418
+ const packageJsonPath = path_1.default.join(__dirname, "..", "package.json");
419
+ const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, "utf8"));
420
+ console.log(packageJson.version);
421
+ process.exit(0);
422
+ }
423
+ if (process.argv.includes("--help") || process.argv.includes("-h")) {
424
+ showHelp();
425
+ }
426
+ // Load YAML config if --config parameter is provided
427
+ // YAML config values are applied to process.argv (command-line args override YAML)
428
+ const configPath = (0, yamlConfig_1.parseConfigArg)();
429
+ if (configPath) {
430
+ try {
431
+ // Generate template if file doesn't exist
432
+ const templateGenerated = (0, yamlConfig_1.generateConfigTemplateIfNeeded)(configPath);
433
+ // If template was just generated, exit successfully
434
+ // User needs to edit the file before running the server
435
+ if (templateGenerated) {
436
+ if (process.platform === 'win32') {
437
+ setTimeout(() => process.exit(0), 100);
438
+ }
439
+ else {
440
+ process.exit(0);
441
+ }
442
+ }
443
+ // Load and apply YAML config (only if file exists)
444
+ const yamlConfig = (0, yamlConfig_1.loadYamlConfig)(configPath);
445
+ if (yamlConfig) {
446
+ (0, yamlConfig_1.applyYamlConfigToArgs)(yamlConfig);
447
+ // Only write to stderr if not in stdio mode (stdio mode requires clean JSON only)
448
+ const isStdioMode = process.env.MCP_TRANSPORT === "stdio" || !process.stdin.isTTY;
449
+ if (!isStdioMode) {
450
+ process.stderr.write(`[MCP-CONFIG] Loaded and validated configuration from: ${configPath}\n`);
451
+ }
452
+ }
453
+ }
454
+ catch (error) {
455
+ const errorMsg = `Failed to load YAML config: ${error instanceof Error ? error.message : String(error)}`;
456
+ process.stderr.write(`[MCP-CONFIG] ✗ ERROR: ${errorMsg}\n`);
457
+ if (process.platform === 'win32') {
458
+ setTimeout(() => process.exit(1), 100);
459
+ }
460
+ else {
461
+ process.exit(1);
462
+ }
463
+ }
464
+ }
465
+ // Runtime config (shared, no side-effects)
466
+ const { useAuthBroker, isTestEnv, authBrokerPath, defaultMcpDestination, unsafe, explicitTransportType, transportType, isHttp, isSse, isStdio, isEnvMandatory, envFilePath: initialEnvFilePath, } = (0, runtimeConfig_1.buildRuntimeConfig)();
467
+ // Skip .env autoload under explicit instructions, auth-broker, mcp default, or test env
468
+ const skipEnvAutoload = process.env.MCP_SKIP_ENV_LOAD === "true" ||
469
+ process.env.MCP_ENV_LOADED_BY_LAUNCHER === "true" ||
470
+ useAuthBroker ||
471
+ !!defaultMcpDestination ||
472
+ isTestEnv;
473
+ // If using auth-broker (--mcp or --auth-broker), clear SAP_* env vars to prevent .env from being used
474
+ // This ensures that even if .env was loaded earlier or vars are set in system, they won't be used
475
+ if (skipEnvAutoload && (useAuthBroker || !!defaultMcpDestination)) {
476
+ const sapEnvKeys = Object.keys(process.env).filter(key => key.startsWith('SAP_'));
477
+ for (const key of sapEnvKeys) {
478
+ delete process.env[key];
479
+ }
480
+ if (sapEnvKeys.length > 0 && !isStdio) {
481
+ process.stderr.write(`[MCP-ENV] Cleared ${sapEnvKeys.length} SAP_* environment variables (using auth-broker)\n`);
482
+ }
483
+ }
484
+ let envFilePath = initialEnvFilePath;
485
+ // Debug: Always log on Windows to help diagnose issues
486
+ if (process.platform === 'win32' && !isStdio) {
487
+ process.stderr.write(`[MCP-ENV] parseEnvArg() returned: ${envFilePath || '(undefined)'}\n`);
488
+ process.stderr.write(`[MCP-ENV] MCP_ENV_PATH: ${process.env.MCP_ENV_PATH || '(not set)'}\n`);
489
+ process.stderr.write(`[MCP-ENV] Final envFilePath: ${envFilePath || '(will search for .env)'}\n`);
490
+ }
491
+ if (!skipEnvAutoload) {
492
+ if (!envFilePath) {
493
+ // Default behavior: search in current working directory (where user runs the command)
494
+ // If .env exists, use it; otherwise will use auth-broker
495
+ const cwdEnvPath = path_1.default.resolve(process.cwd(), ".env");
496
+ if (fs_1.default.existsSync(cwdEnvPath)) {
497
+ envFilePath = cwdEnvPath;
498
+ // Only write to stderr if not in stdio mode (stdio mode requires clean JSON only)
499
+ if (!isStdio) {
500
+ process.stderr.write(`[MCP-ENV] Found .env file: ${envFilePath}\n`);
501
+ }
502
+ }
503
+ }
504
+ else {
505
+ // Only write to stderr if not in stdio mode
506
+ if (!isStdio) {
507
+ process.stderr.write(`[MCP-ENV] Using .env from argument/env: ${envFilePath}\n`);
508
+ // On Windows, also log the resolved path for debugging
509
+ if (process.platform === 'win32') {
510
+ const resolvedPath = path_1.default.isAbsolute(envFilePath)
511
+ ? envFilePath
512
+ : path_1.default.resolve(process.cwd(), envFilePath);
513
+ process.stderr.write(`[MCP-ENV] Will resolve to: ${resolvedPath}\n`);
514
+ }
515
+ }
516
+ }
517
+ if (envFilePath) {
518
+ // Normalize path separators for Windows compatibility
519
+ // On Windows, backslashes in paths need special handling
520
+ // path.resolve() and path.normalize() should handle this, but let's be explicit
521
+ // First, normalize all backslashes to forward slashes for consistent processing
522
+ // Then use path methods which handle platform-specific separators correctly
523
+ const normalizedPath = envFilePath.replace(/\\/g, '/');
524
+ if (!path_1.default.isAbsolute(normalizedPath)) {
525
+ // For relative paths, use path.resolve which handles .\ and ./ correctly on all platforms
526
+ // path.resolve automatically handles both .\mdd.env and ./mdd.env formats
527
+ envFilePath = path_1.default.resolve(process.cwd(), normalizedPath);
528
+ // Only write to stderr if not in stdio mode
529
+ if (!isStdio) {
530
+ process.stderr.write(`[MCP-ENV] Resolved relative path to: ${envFilePath}\n`);
531
+ }
532
+ }
533
+ else {
534
+ // For absolute paths, normalize using path.normalize
535
+ envFilePath = path_1.default.normalize(envFilePath);
536
+ }
537
+ // Verify file exists before attempting to load
538
+ if (!fs_1.default.existsSync(envFilePath)) {
539
+ const errorMsg = `[MCP-ENV] ✗ ERROR: .env file not found at: ${envFilePath}\n` +
540
+ `[MCP-ENV] Current working directory: ${process.cwd()}\n` +
541
+ `[MCP-ENV] Please check the path and try again.\n`;
542
+ process.stderr.write(errorMsg);
543
+ if (process.platform === 'win32') {
544
+ setTimeout(() => process.exit(1), 100);
545
+ }
546
+ else {
547
+ process.exit(1);
548
+ }
549
+ }
550
+ if (fs_1.default.existsSync(envFilePath)) {
551
+ // For stdio mode, load .env manually to avoid any output from dotenv library
552
+ if (isStdio) {
553
+ // Manual .env parsing for stdio mode (no library output)
554
+ try {
555
+ const envContent = fs_1.default.readFileSync(envFilePath, "utf8");
556
+ const lines = envContent.split(/\r?\n/);
557
+ for (const line of lines) {
558
+ const trimmed = line.trim();
559
+ // Skip empty lines and comments
560
+ if (!trimmed || trimmed.startsWith("#")) {
561
+ continue;
562
+ }
563
+ // Parse KEY=VALUE format
564
+ const eqIndex = trimmed.indexOf("=");
565
+ if (eqIndex === -1) {
566
+ continue;
567
+ }
568
+ const key = trimmed.substring(0, eqIndex).trim();
569
+ let value = trimmed.substring(eqIndex + 1);
570
+ // Remove inline comments (everything after #)
571
+ // This handles cases like: KEY=value # comment or KEY=value#comment
572
+ // Find first # and remove everything after it (including the #)
573
+ const commentIndex = value.indexOf('#');
574
+ if (commentIndex !== -1) {
575
+ // Remove everything from # onwards, then trim trailing whitespace
576
+ const beforeComment = value.substring(0, commentIndex);
577
+ value = beforeComment.trim();
578
+ }
579
+ else {
580
+ value = value.trim();
581
+ }
582
+ // Parse value: remove quotes and trim
583
+ let unquotedValue = value.trim();
584
+ unquotedValue = unquotedValue.replace(/^["']+|["']+$/g, '').trim();
585
+ // URLs from .env files are expected to be clean - just use as-is
586
+ if (key === 'SAP_URL') {
587
+ // No special processing needed
588
+ // Debug logging for Windows
589
+ if (process.platform === 'win32' && !isStdio) {
590
+ process.stderr.write(`[MCP-ENV] Parsed SAP_URL: "${unquotedValue}" (length: ${unquotedValue.length})\n`);
591
+ }
592
+ }
593
+ // Only set if not already in process.env (don't override launcher's cleaned values)
594
+ if (key && !process.env[key]) {
595
+ process.env[key] = unquotedValue;
596
+ }
597
+ }
598
+ }
599
+ catch (error) {
600
+ // Silent fail for stdio mode - just exit
601
+ process.exit(1);
602
+ }
603
+ }
604
+ else {
605
+ // For non-stdio modes, use manual parsing (dotenv removed to avoid stdout pollution)
606
+ try {
607
+ const envContent = fs_1.default.readFileSync(envFilePath, "utf8");
608
+ const lines = envContent.split(/\r?\n/);
609
+ for (const line of lines) {
610
+ const trimmed = line.trim();
611
+ if (!trimmed || trimmed.startsWith("#")) {
612
+ continue;
613
+ }
614
+ const eqIndex = trimmed.indexOf("=");
615
+ if (eqIndex === -1) {
616
+ continue;
617
+ }
618
+ const key = trimmed.substring(0, eqIndex).trim();
619
+ let value = trimmed.substring(eqIndex + 1);
620
+ // Remove inline comments (everything after #)
621
+ // This handles cases like: KEY=value # comment or KEY=value#comment
622
+ // Find first # and remove everything after it (including the #)
623
+ const commentIndex = value.indexOf('#');
624
+ if (commentIndex !== -1) {
625
+ // Remove everything from # onwards, then trim trailing whitespace
626
+ const beforeComment = value.substring(0, commentIndex);
627
+ value = beforeComment.trim();
628
+ }
629
+ else {
630
+ value = value.trim();
631
+ }
632
+ // Parse value: remove quotes and trim
633
+ let unquotedValue = value.trim();
634
+ unquotedValue = unquotedValue.replace(/^["']+|["']+$/g, '').trim();
635
+ // URLs from .env files are expected to be clean - just use as-is
636
+ if (key === 'SAP_URL') {
637
+ // No special processing needed
638
+ // Debug logging for Windows
639
+ if (process.platform === 'win32' && !isStdio) {
640
+ process.stderr.write(`[MCP-ENV] Parsed SAP_URL: "${unquotedValue}" (length: ${unquotedValue.length})\n`);
641
+ }
642
+ }
643
+ // Only set if not already in process.env (don't override launcher's cleaned values)
644
+ if (key && !process.env[key]) {
645
+ process.env[key] = unquotedValue;
646
+ }
647
+ }
648
+ process.stderr.write(`[MCP-ENV] ✓ Successfully loaded: ${envFilePath}\n`);
649
+ // Debug: log SAP_URL if loaded (for troubleshooting on Windows)
650
+ if (process.env.SAP_URL) {
651
+ const urlHex = Buffer.from(process.env.SAP_URL, 'utf8').toString('hex');
652
+ process.stderr.write(`[MCP-ENV] SAP_URL loaded: "${process.env.SAP_URL}" (length: ${process.env.SAP_URL.length})\n`);
653
+ if (process.platform === 'win32') {
654
+ process.stderr.write(`[MCP-ENV] SAP_URL (hex): ${urlHex.substring(0, 60)}...\n`);
655
+ // Check for comments
656
+ if (process.env.SAP_URL.includes('#')) {
657
+ process.stderr.write(`[MCP-ENV] ⚠ WARNING: SAP_URL contains # character (comment not removed?)\n`);
658
+ }
659
+ }
660
+ }
661
+ else {
662
+ process.stderr.write(`[MCP-ENV] ⚠ WARNING: SAP_URL not found in .env file\n`);
663
+ }
664
+ }
665
+ catch (error) {
666
+ process.stderr.write(`[MCP-ENV] ✗ Failed to load: ${envFilePath}\n`);
667
+ if (error instanceof Error) {
668
+ process.stderr.write(`[MCP-ENV] Error: ${error.message}\n`);
669
+ }
670
+ }
671
+ }
672
+ }
673
+ else {
674
+ // .env file specified but not found
675
+ if (isEnvMandatory) {
676
+ // Always write error to stderr (stderr is safe even in stdio mode, unlike stdout)
677
+ logger_1.logger?.error(".env file not found", { path: envFilePath });
678
+ process.stderr.write(`[MCP-ENV] ✗ ERROR: .env file not found at: ${envFilePath}\n`);
679
+ process.stderr.write(`[MCP-ENV] Current working directory: ${process.cwd()}\n`);
680
+ process.stderr.write(`[MCP-ENV] Transport mode '${transportType}' requires .env file.\n`);
681
+ process.stderr.write(`[MCP-ENV] Use --env=/path/to/.env to specify custom location\n`);
682
+ // On Windows, add a small delay before exit to allow error message to be visible
683
+ if (process.platform === 'win32') {
684
+ setTimeout(() => process.exit(1), 100);
685
+ }
686
+ else {
687
+ process.exit(1);
688
+ }
689
+ }
690
+ else {
691
+ // Always write error to stderr (stderr is safe even in stdio mode)
692
+ process.stderr.write(`[MCP-ENV] ✗ ERROR: .env file not found at: ${envFilePath}\n`);
693
+ process.stderr.write(`[MCP-ENV] Transport mode '${transportType}' was explicitly specified but .env file is missing.\n`);
694
+ process.stderr.write(`[MCP-ENV] Use --env=/path/to/.env to specify custom location\n`);
695
+ // On Windows, add a small delay before exit to allow error message to be visible
696
+ if (process.platform === 'win32') {
697
+ setTimeout(() => process.exit(1), 100);
698
+ }
699
+ else {
700
+ process.exit(1);
701
+ }
702
+ }
703
+ }
704
+ }
705
+ else {
706
+ // No .env file found and none specified
707
+ if (isEnvMandatory) {
708
+ // Transport explicitly set to stdio/sse but no .env found
709
+ const cwdEnvPath = path_1.default.resolve(process.cwd(), ".env");
710
+ // Always write error to stderr (stderr is safe even in stdio mode)
711
+ logger_1.logger?.error(".env file not found", { path: cwdEnvPath });
712
+ process.stderr.write(`[MCP-ENV] ✗ ERROR: .env file not found in current directory: ${process.cwd()}\n`);
713
+ process.stderr.write(`[MCP-ENV] Transport mode '${transportType}' requires .env file.\n`);
714
+ process.stderr.write(`[MCP-ENV] Use --env=/path/to/.env to specify custom location\n`);
715
+ // On Windows, add a small delay before exit to allow error message to be visible
716
+ if (process.platform === 'win32') {
717
+ setTimeout(() => process.exit(1), 100);
718
+ }
719
+ else {
720
+ process.exit(1);
721
+ }
722
+ }
723
+ else {
724
+ // No .env found, but transport is stdio (default) - this is OK for HTTP/SSE, but stdio requires .env or --mcp
725
+ if (explicitTransportType === null) {
726
+ // Transport not specified, using default (stdio)
727
+ // For stdio mode, don't write to stderr
728
+ if (!isStdio) {
729
+ process.stderr.write(`[MCP-ENV] NOTE: No .env file found in current directory: ${process.cwd()}\n`);
730
+ process.stderr.write(`[MCP-ENV] Starting in HTTP mode (no .env file required)\n`);
731
+ }
732
+ }
733
+ else {
734
+ // Transport explicitly set to HTTP - this is OK
735
+ // For stdio mode, don't write to stderr
736
+ if (!isStdio) {
737
+ process.stderr.write(`[MCP-ENV] NOTE: No .env file found, continuing in ${transportType} mode\n`);
738
+ }
739
+ }
740
+ }
741
+ }
742
+ }
743
+ else if (envFilePath) {
744
+ if (!path_1.default.isAbsolute(envFilePath)) {
745
+ envFilePath = path_1.default.resolve(process.cwd(), envFilePath);
746
+ }
747
+ // For stdio mode, don't write to stderr
748
+ if (!isStdio) {
749
+ process.stderr.write(`[MCP-ENV] Environment autoload skipped; using provided path reference: ${envFilePath}\n`);
750
+ }
751
+ }
752
+ else {
753
+ // For stdio mode, don't write to stderr
754
+ if (!isStdio) {
755
+ process.stderr.write(`[MCP-ENV] Environment autoload skipped (MCP_SKIP_ENV_LOAD=true).\n`);
756
+ }
757
+ }
758
+ // --- END ENV FILE LOADING LOGIC ---
759
+ // Debug: Log loaded SAP_URL and SAP_CLIENT using the MCP-compatible logger
760
+ // Skip logging in stdio mode (MCP protocol requires clean JSON only)
761
+ if (!isStdio) {
762
+ const envLogPath = envFilePath ?? "(skipped)";
763
+ logger_1.logger?.info("SAP configuration loaded", {
764
+ type: "CONFIG_INFO",
765
+ SAP_URL: process.env.SAP_URL,
766
+ SAP_CLIENT: process.env.SAP_CLIENT || "(not set)",
767
+ SAP_AUTH_TYPE: process.env.SAP_AUTH_TYPE || "(not set)",
768
+ SAP_JWT_TOKEN: process.env.SAP_JWT_TOKEN ? "[set]" : "(not set)",
769
+ ENV_PATH: envLogPath,
770
+ CWD: process.cwd(),
771
+ DIRNAME: __dirname,
772
+ TRANSPORT: transportType
773
+ });
774
+ }
775
+ function getArgValue(name) {
776
+ const args = process.argv;
777
+ for (let i = 0; i < args.length; i++) {
778
+ const arg = args[i];
779
+ if (arg.startsWith(`${name}=`)) {
780
+ return arg.slice(name.length + 1);
781
+ }
782
+ if (arg === name && i + 1 < args.length) {
783
+ return args[i + 1];
784
+ }
785
+ }
786
+ return undefined;
787
+ }
788
+ function hasFlag(name) {
789
+ return process.argv.includes(name);
790
+ }
791
+ function parseBoolean(value) {
792
+ if (!value) {
793
+ return false;
794
+ }
795
+ const normalized = value.trim().toLowerCase();
796
+ return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
797
+ }
798
+ function resolvePortOption(argName, envName, defaultValue) {
799
+ const rawValue = getArgValue(argName) ?? process.env[envName];
800
+ if (!rawValue) {
801
+ return defaultValue;
802
+ }
803
+ const port = Number.parseInt(rawValue, 10);
804
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) {
805
+ throw new Error(`Invalid port value for ${argName}: ${rawValue}`);
806
+ }
807
+ return port;
808
+ }
809
+ function resolveBooleanOption(argName, envName, defaultValue) {
810
+ const argValue = getArgValue(argName);
811
+ if (argValue !== undefined) {
812
+ return parseBoolean(argValue);
813
+ }
814
+ if (hasFlag(argName)) {
815
+ return true;
816
+ }
817
+ const envValue = process.env[envName];
818
+ if (envValue !== undefined) {
819
+ return parseBoolean(envValue);
820
+ }
821
+ return defaultValue;
822
+ }
823
+ function resolveListOption(argName, envName) {
824
+ const rawValue = getArgValue(argName) ?? process.env[envName];
825
+ if (!rawValue) {
826
+ return undefined;
827
+ }
828
+ const items = rawValue
829
+ .split(",")
830
+ .map((entry) => entry.trim())
831
+ .filter((entry) => entry.length > 0);
832
+ return items.length > 0 ? items : undefined;
833
+ }
834
+ function parseTransportConfig() {
835
+ // Use the transport type we already determined (handles explicit args, env vars, and defaults)
836
+ const normalized = transportType;
837
+ if (normalized &&
838
+ normalized !== "stdio" &&
839
+ normalized !== "http" &&
840
+ normalized !== "streamable-http" &&
841
+ normalized !== "server" &&
842
+ normalized !== "sse") {
843
+ throw new Error(`Unsupported transport: ${normalized}`);
844
+ }
845
+ const sseRequested = normalized === "sse" ||
846
+ hasFlag("--sse");
847
+ if (sseRequested) {
848
+ const port = resolvePortOption("--sse-port", "MCP_SSE_PORT", 3001);
849
+ // Default to localhost (127.0.0.1) for security - only accepts local connections
850
+ // Use 0.0.0.0 to accept connections from all interfaces (less secure)
851
+ const host = getArgValue("--sse-host") ?? process.env.MCP_SSE_HOST ?? "127.0.0.1";
852
+ const allowedOrigins = resolveListOption("--sse-allowed-origins", "MCP_SSE_ALLOWED_ORIGINS");
853
+ const allowedHosts = resolveListOption("--sse-allowed-hosts", "MCP_SSE_ALLOWED_HOSTS");
854
+ const enableDnsRebindingProtection = resolveBooleanOption("--sse-enable-dns-protection", "MCP_SSE_ENABLE_DNS_PROTECTION", false);
855
+ return {
856
+ type: "sse",
857
+ host,
858
+ port,
859
+ allowedOrigins,
860
+ allowedHosts,
861
+ enableDnsRebindingProtection,
862
+ };
863
+ }
864
+ const httpRequested = normalized === "http" ||
865
+ normalized === "streamable-http" ||
866
+ normalized === "server" ||
867
+ hasFlag("--http") ||
868
+ // Note: Default is stdio (set in runtimeConfig), so this only applies if explicitly requested
869
+ (!sseRequested && normalized !== "stdio");
870
+ if (httpRequested) {
871
+ const port = resolvePortOption("--http-port", "MCP_HTTP_PORT", 3000);
872
+ // Default to localhost (127.0.0.1) for security - only accepts local connections
873
+ // Use 0.0.0.0 to accept connections from all interfaces (less secure)
874
+ const host = getArgValue("--http-host") ?? process.env.MCP_HTTP_HOST ?? "127.0.0.1";
875
+ const enableJsonResponse = resolveBooleanOption("--http-json-response", "MCP_HTTP_ENABLE_JSON_RESPONSE", false);
876
+ const allowedOrigins = resolveListOption("--http-allowed-origins", "MCP_HTTP_ALLOWED_ORIGINS");
877
+ const allowedHosts = resolveListOption("--http-allowed-hosts", "MCP_HTTP_ALLOWED_HOSTS");
878
+ const enableDnsRebindingProtection = resolveBooleanOption("--http-enable-dns-protection", "MCP_HTTP_ENABLE_DNS_PROTECTION", false);
879
+ return {
880
+ type: "streamable-http",
881
+ host,
882
+ port,
883
+ enableJsonResponse,
884
+ allowedOrigins,
885
+ allowedHosts,
886
+ enableDnsRebindingProtection,
887
+ };
888
+ }
889
+ return { type: "stdio" };
890
+ }
891
+ function setAbapConnectionOverride(connection) {
892
+ (0, utils_1.setConnectionOverride)(connection);
893
+ }
894
+ /**
895
+ * Retrieves SAP configuration from environment variables.
896
+ * Reads configuration from process.env (caller is responsible for loading .env file if needed).
897
+ *
898
+ * @returns {SapConfig} The SAP configuration object.
899
+ * @throws {Error} If any required environment variable is missing.
900
+ */
901
+ // Helper function for Windows-compatible logging
902
+ // Only logs when DEBUG_CONNECTORS, DEBUG_TESTS, or DEBUG_ADT_TESTS is enabled
903
+ function debugLog(message) {
904
+ const debugEnabled = process.env.DEBUG_CONNECTORS === 'true' ||
905
+ process.env.DEBUG_TESTS === 'true' ||
906
+ process.env.DEBUG_ADT_TESTS === 'true';
907
+ if (!debugEnabled) {
908
+ return; // Suppress debug logs when not in debug mode
909
+ }
910
+ // Try stderr first
911
+ try {
912
+ process.stderr.write(message);
913
+ }
914
+ catch (e) {
915
+ // Fallback to console.error for Windows
916
+ console.error(message.trim());
917
+ }
918
+ // Also try to write to a debug file on Windows
919
+ if (process.platform === 'win32') {
920
+ try {
921
+ const fs = require('fs');
922
+ const path = require('path');
923
+ const debugFile = path.join(process.cwd(), 'mcp-debug.log');
924
+ fs.appendFileSync(debugFile, `${new Date().toISOString()} ${message}`, 'utf8');
925
+ }
926
+ catch (e) {
927
+ // Ignore file write errors
928
+ }
929
+ }
930
+ }
931
+ // Re-export header constants from interfaces package
932
+ __exportStar(require("@mcp-abap-adt/interfaces"), exports);
933
+ /**
934
+ * Server class for interacting with ABAP systems via ADT.
935
+ */
936
+ class mcp_abap_adt_server {
937
+ allowProcessExit;
938
+ registerSignalHandlers;
939
+ mcpServer; // MCP server for all transports
940
+ sapConfig; // SAP configuration
941
+ hasEnvFile; // Track if .env file was found at startup
942
+ transportConfig;
943
+ httpServer;
944
+ shuttingDown = false;
945
+ defaultMcpDestination; // Default MCP destination from --mcp parameter
946
+ defaultDestination; // Default destination (from --mcp or .env, used when client doesn't specify)
947
+ mcpHandlers;
948
+ // Client session tracking for StreamableHTTP (like the example)
949
+ streamableHttpSessions = new Map();
950
+ // Map sessionId -> client key (streamable HTTP) for quick lookups
951
+ streamableSessionIndex = new Map();
952
+ // SSE session tracking (McpServer + SSEServerTransport per session)
953
+ sseSessions = new Map();
954
+ // AuthBroker factory for creating and managing AuthBroker instances
955
+ authBrokerFactory;
956
+ // Path to .env file (used to create SessionStore when --mcp is not specified)
957
+ envFilePath;
958
+ /**
959
+ * Initialize default broker on server startup (for stdio/SSE transports)
960
+ * Creates broker with default destination from --mcp parameter or .env file
961
+ */
962
+ /**
963
+ * Initialize default broker using unified AuthBrokerFactory logic
964
+ * Sets defaultDestination based on what broker was created
965
+ */
966
+ async initializeDefaultBroker() {
967
+ // Use unified AuthBrokerFactory logic to initialize default broker
968
+ await this.authBrokerFactory.initializeDefaultBroker();
969
+ // Get default broker to determine default destination
970
+ const defaultBroker = this.authBrokerFactory.getDefaultBroker();
971
+ if (defaultBroker) {
972
+ // If we have --mcp, use that as default destination
973
+ if (this.defaultMcpDestination) {
974
+ this.defaultDestination = this.defaultMcpDestination;
975
+ }
976
+ else {
977
+ // Otherwise use 'default' as destination name
978
+ this.defaultDestination = 'default';
979
+ }
980
+ logger_1.logger?.info("Default broker initialized", {
981
+ type: "DEFAULT_BROKER_INITIALIZED",
982
+ destination: this.defaultDestination,
983
+ transport: this.transportConfig.type,
984
+ });
985
+ }
986
+ else {
987
+ logger_1.logger?.debug("No default broker created (no conditions met)", {
988
+ type: "NO_DEFAULT_BROKER",
989
+ transport: this.transportConfig.type,
990
+ });
991
+ }
992
+ }
993
+ /**
994
+ * Get or create AuthBroker for a specific destination (lazy initialization)
995
+ * If destination is not specified, returns default broker
996
+ */
997
+ async getOrCreateAuthBroker(destination, clientKey) {
998
+ // clientKey is ignored in unified logic (one broker per destination)
999
+ return this.authBrokerFactory.getOrCreateAuthBroker(destination);
1000
+ }
1001
+ async applyAuthHeaders(headers, sessionId, clientKey) {
1002
+ // Security: If server is listening on non-local interface (0.0.0.0), don't use default destination
1003
+ // Client must provide all connection parameters in headers - server only proxies to ABAP
1004
+ const isNonLocalInterface = this.isListeningOnNonLocalInterface();
1005
+ if (isNonLocalInterface) {
1006
+ // Non-local interface: use only what client provides in headers, no default destination
1007
+ if (!headers || Object.keys(headers).length === 0) {
1008
+ logger_1.logger?.info("No headers provided - server listening on non-local interface requires all headers from client", {
1009
+ type: "NO_HEADERS_NON_LOCAL_INTERFACE",
1010
+ sessionId: sessionId?.substring(0, 8),
1011
+ hint: `Server is listening on 0.0.0.0 - client must provide all connection parameters in headers. Server will not use default destination for security.`,
1012
+ });
1013
+ return;
1014
+ }
1015
+ // Use headers as-is, no default destination
1016
+ logger_1.logger?.debug("Non-local interface: using only client-provided headers", {
1017
+ type: "NON_LOCAL_INTERFACE_HEADERS_ONLY",
1018
+ sessionId: sessionId?.substring(0, 8),
1019
+ });
1020
+ }
1021
+ else {
1022
+ // Local interface (127.0.0.1): can use default destination if no headers
1023
+ // If no headers but defaultDestination is set, use it
1024
+ // Client values have priority (Q10), but if no headers at all, use default
1025
+ if (!headers || Object.keys(headers).length === 0) {
1026
+ if (this.defaultDestination) {
1027
+ // Use default destination to get connection config from broker
1028
+ const authBroker = await this.getOrCreateAuthBroker(this.defaultDestination, clientKey || sessionId);
1029
+ if (authBroker) {
1030
+ try {
1031
+ const connConfig = await authBroker.getConnectionConfig(this.defaultDestination);
1032
+ if (connConfig?.serviceUrl) {
1033
+ // Check authType from connection config BEFORE calling getToken()
1034
+ const isBasicAuth = connConfig.authType === 'basic' || (connConfig.username && connConfig.password);
1035
+ if (isBasicAuth) {
1036
+ // Basic auth for on-premise
1037
+ if (connConfig.username && connConfig.password) {
1038
+ this.processBasicAuthConfigUpdate(connConfig.serviceUrl, connConfig.username, connConfig.password, sessionId);
1039
+ logger_1.logger?.info("No headers provided, using default destination with basic auth", {
1040
+ type: "NO_HEADERS_DEFAULT_DESTINATION_BASIC_AUTH",
1041
+ destination: this.defaultDestination,
1042
+ sessionId: sessionId?.substring(0, 8),
1043
+ });
1044
+ }
1045
+ }
1046
+ else {
1047
+ // JWT auth for cloud
1048
+ const jwtToken = await authBroker.getToken(this.defaultDestination);
1049
+ if (jwtToken) {
1050
+ // Create headers object with default destination
1051
+ headers = {
1052
+ [interfaces_1.HEADER_MCP_DESTINATION]: this.defaultDestination,
1053
+ [interfaces_1.HEADER_SAP_URL]: connConfig.serviceUrl,
1054
+ [interfaces_1.HEADER_SAP_JWT_TOKEN]: jwtToken,
1055
+ };
1056
+ logger_1.logger?.info("No headers provided, using default destination", {
1057
+ type: "NO_HEADERS_DEFAULT_DESTINATION_USED",
1058
+ destination: this.defaultDestination,
1059
+ sessionId: sessionId?.substring(0, 8),
1060
+ });
1061
+ }
1062
+ }
1063
+ }
1064
+ }
1065
+ catch (error) {
1066
+ logger_1.logger?.warn("Failed to get connection config from default destination", {
1067
+ type: "DEFAULT_DESTINATION_CONFIG_FAILED",
1068
+ destination: this.defaultDestination,
1069
+ sessionId: sessionId?.substring(0, 8),
1070
+ error: error instanceof Error ? error.message : String(error),
1071
+ });
1072
+ }
1073
+ }
1074
+ }
1075
+ // If still no headers after trying default destination, log and return
1076
+ if (!headers || Object.keys(headers).length === 0) {
1077
+ logger_1.logger?.info("No headers provided in request and no default destination available", {
1078
+ type: "NO_HEADERS_PROVIDED",
1079
+ sessionId: sessionId?.substring(0, 8),
1080
+ hint: `Provide authentication headers (${interfaces_1.HEADER_SAP_DESTINATION_SERVICE}, ${interfaces_1.HEADER_SAP_URL}, ${interfaces_1.HEADER_SAP_AUTH_TYPE}, etc.) or use --mcp parameter or .env file`,
1081
+ });
1082
+ return;
1083
+ }
1084
+ }
1085
+ // Apply default destination if not provided in headers (client values have priority)
1086
+ // Only for local interface
1087
+ if (this.defaultDestination && !headers[interfaces_1.HEADER_MCP_DESTINATION] && !headers['X-MCP-Destination']) {
1088
+ headers[interfaces_1.HEADER_MCP_DESTINATION] = this.defaultDestination;
1089
+ logger_1.logger?.info("Using default destination (client didn't specify)", {
1090
+ type: "DEFAULT_DESTINATION_APPLIED",
1091
+ destination: this.defaultDestination,
1092
+ sessionId: sessionId?.substring(0, 8),
1093
+ });
1094
+ }
1095
+ }
1096
+ // Ensure headers is set for processing below
1097
+ if (!headers) {
1098
+ headers = {};
1099
+ }
1100
+ const headersWithDefault = { ...headers };
1101
+ // Log which auth headers are present (for debugging)
1102
+ // Check headers in both cases (Node.js normalizes to lowercase, but check both for safety)
1103
+ const authHeaders = {
1104
+ [interfaces_1.HEADER_SAP_DESTINATION_SERVICE]: headersWithDefault[interfaces_1.HEADER_SAP_DESTINATION_SERVICE] || headersWithDefault['X-SAP-Destination'],
1105
+ [interfaces_1.HEADER_MCP_DESTINATION]: headersWithDefault[interfaces_1.HEADER_MCP_DESTINATION] || headersWithDefault['X-MCP-Destination'],
1106
+ [interfaces_1.HEADER_SAP_URL]: (headersWithDefault[interfaces_1.HEADER_SAP_URL] || headersWithDefault['X-SAP-URL']) ? '[present]' : undefined,
1107
+ [interfaces_1.HEADER_SAP_AUTH_TYPE]: headersWithDefault[interfaces_1.HEADER_SAP_AUTH_TYPE] || headersWithDefault['X-SAP-Auth-Type'],
1108
+ [interfaces_1.HEADER_SAP_JWT_TOKEN]: (headersWithDefault[interfaces_1.HEADER_SAP_JWT_TOKEN] || headersWithDefault['X-SAP-JWT-Token']) ? '[present]' : undefined,
1109
+ [interfaces_1.HEADER_SAP_LOGIN]: (headersWithDefault[interfaces_1.HEADER_SAP_LOGIN] || headersWithDefault['X-SAP-Login']) ? '[present]' : undefined,
1110
+ };
1111
+ logger_1.logger?.info("Processing authentication headers", {
1112
+ type: "AUTH_HEADERS_PROCESSING",
1113
+ headers: authHeaders,
1114
+ platform: process.platform,
1115
+ sessionId: sessionId?.substring(0, 8),
1116
+ allHeaderKeys: Object.keys(headersWithDefault).filter(k => {
1117
+ const lowerKey = k.toLowerCase();
1118
+ return lowerKey.startsWith('x-sap') || lowerKey.startsWith('x-mcp');
1119
+ }),
1120
+ });
1121
+ // Use header validator to validate and prioritize authentication methods
1122
+ const validationResult = (0, header_validator_1.validateAuthHeaders)(headersWithDefault);
1123
+ // Log warnings if any
1124
+ if (validationResult.warnings.length > 0) {
1125
+ logger_1.logger?.debug("Header validation warnings", {
1126
+ type: "HEADER_VALIDATION_WARNINGS",
1127
+ warnings: validationResult.warnings,
1128
+ sessionId: sessionId?.substring(0, 8),
1129
+ });
1130
+ }
1131
+ // If validation failed, log info (not error) and return
1132
+ // This is not an error - user may be using .env file or base config
1133
+ if (!validationResult.isValid || !validationResult.config) {
1134
+ if (validationResult.errors.length > 0) {
1135
+ logger_1.logger?.debug("Header validation failed - will use .env file or base config", {
1136
+ type: "HEADER_VALIDATION_FAILED",
1137
+ errors: validationResult.errors,
1138
+ sessionId: sessionId?.substring(0, 8),
1139
+ hint: "No valid headers found. Will use .env file or base config if available.",
1140
+ });
1141
+ }
1142
+ else {
1143
+ logger_1.logger?.debug("No valid authentication headers found - will use .env file or base config", {
1144
+ type: "NO_VALID_AUTH_HEADERS",
1145
+ sessionId: sessionId?.substring(0, 8),
1146
+ hint: "No headers provided. Will use .env file or base config if available.",
1147
+ });
1148
+ }
1149
+ return;
1150
+ }
1151
+ const config = validationResult.config;
1152
+ // Process based on priority (highest to lowest)
1153
+ switch (config.priority) {
1154
+ case header_validator_1.AuthMethodPriority.SAP_DESTINATION: {
1155
+ // Priority 4: x-sap-destination (uses AuthBroker, URL from destination)
1156
+ if (!config.destination) {
1157
+ logger_1.logger?.warn("SAP destination auth requires destination name", {
1158
+ type: "SAP_DESTINATION_AUTH_MISSING",
1159
+ destination: config.destination,
1160
+ sessionId: sessionId?.substring(0, 8),
1161
+ });
1162
+ return;
1163
+ }
1164
+ // Get or create AuthBroker for this destination (lazy initialization)
1165
+ const authBroker = await this.getOrCreateAuthBroker(config.destination, clientKey || sessionId);
1166
+ if (!authBroker) {
1167
+ const errorMessage = `Failed to initialize AuthBroker for destination "${config.destination}". Auth-broker is only available for HTTP/streamable-http transport.`;
1168
+ logger_1.logger?.error(errorMessage, {
1169
+ type: "AUTH_BROKER_NOT_INITIALIZED",
1170
+ destination: config.destination,
1171
+ sessionId: sessionId?.substring(0, 8),
1172
+ transport: this.transportConfig.type,
1173
+ });
1174
+ return;
1175
+ }
1176
+ try {
1177
+ // Get connection config from AuthBroker (loads from .env or service key)
1178
+ // Note: destination name must exactly match service key filename (case-sensitive)
1179
+ // Example: if file is "sk.json", destination must be "sk" (not "SK")
1180
+ const connConfig = await authBroker.getConnectionConfig(config.destination);
1181
+ if (!connConfig || !connConfig.serviceUrl) {
1182
+ logger_1.logger?.error("Failed to get SAP URL from destination", {
1183
+ type: "SAP_DESTINATION_URL_NOT_FOUND",
1184
+ destination: config.destination,
1185
+ sessionId: sessionId?.substring(0, 8),
1186
+ hint: `Service key file "${config.destination}.json" not found or missing URL. Check file name matches destination exactly (case-sensitive).`,
1187
+ });
1188
+ return;
1189
+ }
1190
+ const sapUrl = connConfig.serviceUrl;
1191
+ logger_1.logger?.info("SAP URL retrieved from destination", {
1192
+ type: "SAP_URL_RETRIEVED",
1193
+ destination: config.destination,
1194
+ url: sapUrl,
1195
+ sessionId: sessionId?.substring(0, 8),
1196
+ });
1197
+ // Check authType from connection config BEFORE calling getToken()
1198
+ const isBasicAuth = connConfig.authType === 'basic' || (connConfig.username && connConfig.password);
1199
+ if (isBasicAuth) {
1200
+ // Basic auth for on-premise
1201
+ if (connConfig.username && connConfig.password) {
1202
+ this.processBasicAuthConfigUpdate(sapUrl, connConfig.username, connConfig.password, sessionId);
1203
+ logger_1.logger?.info("Updated SAP configuration using SAP destination with basic auth", {
1204
+ type: "SAP_CONFIG_UPDATED_SAP_DESTINATION_BASIC_AUTH",
1205
+ destination: config.destination,
1206
+ url: sapUrl,
1207
+ sessionId: sessionId?.substring(0, 8),
1208
+ });
1209
+ }
1210
+ else {
1211
+ logger_1.logger?.error("Basic auth requires username and password", {
1212
+ type: "BASIC_AUTH_MISSING_CREDENTIALS",
1213
+ destination: config.destination,
1214
+ sessionId: sessionId?.substring(0, 8),
1215
+ });
1216
+ }
1217
+ }
1218
+ else {
1219
+ // JWT auth for cloud
1220
+ // Get token from AuthBroker
1221
+ const jwtToken = await authBroker.getToken(config.destination);
1222
+ // Register AuthBroker in global registry for connection to use during token refresh
1223
+ const { registerAuthBroker } = require('./lib/utils');
1224
+ registerAuthBroker(config.destination, authBroker);
1225
+ this.processJwtConfigUpdate(sapUrl, jwtToken, undefined, config.destination, sessionId);
1226
+ logger_1.logger?.info("Updated SAP configuration using SAP destination (AuthBroker)", {
1227
+ type: "SAP_CONFIG_UPDATED_SAP_DESTINATION",
1228
+ destination: config.destination,
1229
+ url: sapUrl,
1230
+ sessionId: sessionId?.substring(0, 8),
1231
+ });
1232
+ }
1233
+ }
1234
+ catch (error) {
1235
+ logger_1.logger?.error("Failed to get token from AuthBroker for SAP destination", {
1236
+ type: "AUTH_BROKER_ERROR_SAP_DESTINATION",
1237
+ destination: config.destination,
1238
+ error: error instanceof Error ? error.message : String(error),
1239
+ sessionId: sessionId?.substring(0, 8),
1240
+ });
1241
+ }
1242
+ return;
1243
+ }
1244
+ case header_validator_1.AuthMethodPriority.MCP_DESTINATION: {
1245
+ // Priority 3: x-mcp-destination (uses AuthBroker, URL from destination or x-sap-url header)
1246
+ if (!config.destination) {
1247
+ logger_1.logger?.warn("MCP destination auth requires destination", {
1248
+ type: "MCP_DESTINATION_AUTH_MISSING",
1249
+ destination: config.destination,
1250
+ sessionId: sessionId?.substring(0, 8),
1251
+ });
1252
+ return;
1253
+ }
1254
+ // Get or create AuthBroker for this destination (lazy initialization)
1255
+ const authBroker = await this.getOrCreateAuthBroker(config.destination, clientKey || sessionId);
1256
+ if (!authBroker) {
1257
+ logger_1.logger?.error("Failed to initialize AuthBroker for MCP destination", {
1258
+ type: "AUTH_BROKER_NOT_INITIALIZED",
1259
+ destination: config.destination,
1260
+ sessionId: sessionId?.substring(0, 8),
1261
+ transport: this.transportConfig.type,
1262
+ });
1263
+ return;
1264
+ }
1265
+ try {
1266
+ // Get connection config from AuthBroker (loads from .env or service key)
1267
+ // If x-sap-url was provided in headers, it will be ignored (warning already issued by validator)
1268
+ // Note: destination name must exactly match service key filename (case-sensitive)
1269
+ const connConfig = await authBroker.getConnectionConfig(config.destination);
1270
+ if (!connConfig || !connConfig.serviceUrl) {
1271
+ logger_1.logger?.error("Failed to get SAP URL from destination", {
1272
+ type: "MCP_DESTINATION_URL_NOT_FOUND",
1273
+ destination: config.destination,
1274
+ sessionId: sessionId?.substring(0, 8),
1275
+ hint: `Service key file "${config.destination}.json" not found or missing URL. Check file name matches destination exactly (case-sensitive).`,
1276
+ });
1277
+ return;
1278
+ }
1279
+ const sapUrl = connConfig.serviceUrl;
1280
+ logger_1.logger?.info("SAP URL retrieved from destination", {
1281
+ type: "SAP_URL_RETRIEVED",
1282
+ destination: config.destination,
1283
+ url: sapUrl,
1284
+ sessionId: sessionId?.substring(0, 8),
1285
+ });
1286
+ // Check authType from connection config BEFORE calling getToken()
1287
+ const isBasicAuth = connConfig.authType === 'basic' || (connConfig.username && connConfig.password);
1288
+ if (isBasicAuth) {
1289
+ // Basic auth for on-premise
1290
+ if (connConfig.username && connConfig.password) {
1291
+ this.processBasicAuthConfigUpdate(sapUrl, connConfig.username, connConfig.password, sessionId);
1292
+ logger_1.logger?.info("Updated SAP configuration using MCP destination with basic auth", {
1293
+ type: "SAP_CONFIG_UPDATED_MCP_DESTINATION_BASIC_AUTH",
1294
+ destination: config.destination,
1295
+ url: sapUrl,
1296
+ sessionId: sessionId?.substring(0, 8),
1297
+ });
1298
+ }
1299
+ else {
1300
+ logger_1.logger?.error("Basic auth requires username and password", {
1301
+ type: "BASIC_AUTH_MISSING_CREDENTIALS",
1302
+ destination: config.destination,
1303
+ sessionId: sessionId?.substring(0, 8),
1304
+ });
1305
+ }
1306
+ }
1307
+ else {
1308
+ // JWT auth for cloud
1309
+ // Get token from AuthBroker
1310
+ const jwtToken = await authBroker.getToken(config.destination);
1311
+ // Register AuthBroker in global registry for connection to use during token refresh
1312
+ const { registerAuthBroker } = require('./lib/utils');
1313
+ registerAuthBroker(config.destination, authBroker);
1314
+ this.processJwtConfigUpdate(sapUrl, jwtToken, undefined, config.destination, sessionId);
1315
+ logger_1.logger?.info("Updated SAP configuration using MCP destination (AuthBroker)", {
1316
+ type: "SAP_CONFIG_UPDATED_MCP_DESTINATION",
1317
+ destination: config.destination,
1318
+ url: sapUrl,
1319
+ sessionId: sessionId?.substring(0, 8),
1320
+ });
1321
+ }
1322
+ }
1323
+ catch (error) {
1324
+ logger_1.logger?.error("Failed to get token from AuthBroker for MCP destination", {
1325
+ type: "AUTH_BROKER_ERROR_MCP_DESTINATION",
1326
+ destination: config.destination,
1327
+ error: error instanceof Error ? error.message : String(error),
1328
+ sessionId: sessionId?.substring(0, 8),
1329
+ });
1330
+ }
1331
+ return;
1332
+ }
1333
+ case header_validator_1.AuthMethodPriority.DIRECT_JWT: {
1334
+ // Priority 2: x-sap-jwt-token (direct JWT token)
1335
+ if (!config.sapUrl || !config.jwtToken) {
1336
+ logger_1.logger?.warn("Direct JWT auth requires URL and token", {
1337
+ type: "DIRECT_JWT_AUTH_MISSING",
1338
+ sapUrl: config.sapUrl,
1339
+ hasToken: !!config.jwtToken,
1340
+ sessionId: sessionId?.substring(0, 8),
1341
+ });
1342
+ return;
1343
+ }
1344
+ this.processJwtConfigUpdate(config.sapUrl, config.jwtToken, config.refreshToken, sessionId);
1345
+ logger_1.logger?.info("Updated SAP configuration using direct JWT token", {
1346
+ type: "SAP_CONFIG_UPDATED_DIRECT_JWT",
1347
+ url: config.sapUrl,
1348
+ sessionId: sessionId?.substring(0, 8),
1349
+ });
1350
+ return;
1351
+ }
1352
+ case header_validator_1.AuthMethodPriority.BASIC: {
1353
+ // Priority 1: x-sap-login + x-sap-password (basic auth)
1354
+ if (!config.sapUrl || !config.username || !config.password) {
1355
+ logger_1.logger?.warn("Basic auth requires URL, username, and password", {
1356
+ type: "BASIC_AUTH_MISSING",
1357
+ sapUrl: config.sapUrl,
1358
+ hasUsername: !!config.username,
1359
+ hasPassword: !!config.password,
1360
+ sessionId: sessionId?.substring(0, 8),
1361
+ });
1362
+ return;
1363
+ }
1364
+ this.processBasicAuthConfigUpdate(config.sapUrl, config.username, config.password, sessionId);
1365
+ logger_1.logger?.info("Updated SAP configuration using basic auth", {
1366
+ type: "SAP_CONFIG_UPDATED_BASIC",
1367
+ url: config.sapUrl,
1368
+ sessionId: sessionId?.substring(0, 8),
1369
+ });
1370
+ return;
1371
+ }
1372
+ default: {
1373
+ logger_1.logger?.warn("Unknown authentication method priority", {
1374
+ type: "UNKNOWN_AUTH_PRIORITY",
1375
+ priority: config.priority,
1376
+ sessionId: sessionId?.substring(0, 8),
1377
+ });
1378
+ return;
1379
+ }
1380
+ }
1381
+ }
1382
+ processJwtConfigUpdate(sapUrl, jwtToken, refreshToken, destination, sessionId) {
1383
+ const sanitizeToken = (token) => token.length <= 10 ? token : `${token.substring(0, 6)}…${token.substring(token.length - 4)}`;
1384
+ // URL from auth-broker/service key is already clean and correct, no need to clean it
1385
+ // Only validate format
1386
+ let cleanedUrl = sapUrl.trim();
1387
+ // Ensure URL has protocol
1388
+ if (!/^https?:\/\//.test(cleanedUrl)) {
1389
+ logger_1.logger?.error("Invalid URL format in processJwtConfigUpdate", {
1390
+ type: "INVALID_URL_FORMAT",
1391
+ originalUrl: sapUrl,
1392
+ cleanedUrl: cleanedUrl,
1393
+ sessionId: sessionId?.substring(0, 8),
1394
+ });
1395
+ throw new Error(`Invalid URL format: "${sapUrl}". Expected format: https://your-system.sap.com`);
1396
+ }
1397
+ // Normalize URL using URL object
1398
+ try {
1399
+ const urlObj = new URL(cleanedUrl);
1400
+ cleanedUrl = urlObj.href.replace(/\/$/, ''); // Remove trailing slash
1401
+ }
1402
+ catch (urlError) {
1403
+ logger_1.logger?.error("Failed to parse URL in processJwtConfigUpdate", {
1404
+ type: "URL_PARSE_ERROR",
1405
+ url: cleanedUrl,
1406
+ error: urlError instanceof Error ? urlError.message : String(urlError),
1407
+ sessionId: sessionId?.substring(0, 8),
1408
+ });
1409
+ throw new Error(`Invalid URL: "${sapUrl}". Error: ${urlError instanceof Error ? urlError.message : urlError}`);
1410
+ }
1411
+ // Use cleaned URL
1412
+ sapUrl = cleanedUrl;
1413
+ let baseConfig = this.sapConfig;
1414
+ // Don't load from .env if using auth-broker (--mcp or --auth-broker)
1415
+ // This prevents .env from being used when destination-based auth is specified
1416
+ const isUsingAuthBroker = this.defaultMcpDestination || useAuthBroker;
1417
+ if ((!baseConfig || baseConfig.url === "http://placeholder") && !isUsingAuthBroker) {
1418
+ try {
1419
+ baseConfig = (0, config_js_1.getConfig)();
1420
+ }
1421
+ catch (error) {
1422
+ logger_1.logger?.warn("Failed to load base SAP config when applying headers", {
1423
+ type: "SAP_CONFIG_HEADER_APPLY_FAILED",
1424
+ error: error instanceof Error ? error.message : String(error),
1425
+ });
1426
+ baseConfig = {
1427
+ url: sapUrl,
1428
+ authType: "jwt",
1429
+ };
1430
+ }
1431
+ }
1432
+ else if (!baseConfig || baseConfig.url === "http://placeholder") {
1433
+ // Using auth-broker, don't load from .env
1434
+ baseConfig = {
1435
+ url: sapUrl,
1436
+ authType: "jwt",
1437
+ };
1438
+ }
1439
+ // Check if any configuration changed
1440
+ const urlChanged = sapUrl !== baseConfig.url;
1441
+ const authTypeChanged = "jwt" !== baseConfig.authType;
1442
+ const tokenChanged = baseConfig.jwtToken !== jwtToken ||
1443
+ (!!refreshToken && refreshToken.trim() !== baseConfig.refreshToken);
1444
+ if (!urlChanged && !authTypeChanged && !tokenChanged) {
1445
+ return;
1446
+ }
1447
+ const newConfig = {
1448
+ ...baseConfig,
1449
+ url: sapUrl,
1450
+ authType: "jwt",
1451
+ jwtToken,
1452
+ };
1453
+ if (refreshToken && refreshToken.trim()) {
1454
+ newConfig.refreshToken = refreshToken.trim();
1455
+ }
1456
+ (0, config_js_1.setSapConfigOverride)(newConfig);
1457
+ this.sapConfig = newConfig;
1458
+ // Store config and destination in session if sessionId is provided
1459
+ if (sessionId) {
1460
+ const clientKeyForSession = this.streamableSessionIndex.get(sessionId);
1461
+ const session = clientKeyForSession ? this.streamableHttpSessions.get(clientKeyForSession) : undefined;
1462
+ if (session) {
1463
+ session.sapConfig = newConfig;
1464
+ if (destination) {
1465
+ session.destination = destination;
1466
+ }
1467
+ }
1468
+ }
1469
+ // Force connection cache invalidation (for backward compatibility)
1470
+ const { invalidateConnectionCache } = require('./lib/utils');
1471
+ try {
1472
+ invalidateConnectionCache();
1473
+ }
1474
+ catch (error) {
1475
+ logger_1.logger?.debug("Connection cache invalidation failed", {
1476
+ type: "CONNECTION_CACHE_INVALIDATION_FAILED",
1477
+ error: error instanceof Error ? error.message : String(error),
1478
+ });
1479
+ }
1480
+ logger_1.logger?.info("Updated SAP configuration from HTTP headers (JWT)", {
1481
+ type: "SAP_CONFIG_UPDATED",
1482
+ urlChanged: Boolean(urlChanged),
1483
+ authTypeChanged: Boolean(authTypeChanged),
1484
+ tokenChanged: Boolean(tokenChanged),
1485
+ hasRefreshToken: Boolean(refreshToken),
1486
+ jwtPreview: sanitizeToken(jwtToken),
1487
+ sessionId: sessionId?.substring(0, 8),
1488
+ });
1489
+ }
1490
+ /**
1491
+ * Process JWT config update using AuthBroker for destination-based authentication
1492
+ * @private
1493
+ */
1494
+ async processJwtConfigUpdateWithAuthBroker(sapUrl, destination, sessionId) {
1495
+ // Get or create AuthBroker for this destination (lazy initialization)
1496
+ const authBroker = await this.getOrCreateAuthBroker(destination, sessionId);
1497
+ if (!authBroker) {
1498
+ logger_1.logger?.warn("AuthBroker not available, falling back to direct token", {
1499
+ type: "AUTH_BROKER_NOT_AVAILABLE",
1500
+ destination,
1501
+ transport: this.transportConfig.type,
1502
+ });
1503
+ return;
1504
+ }
1505
+ try {
1506
+ // Get token from AuthBroker (will load from .env, validate, and refresh if needed)
1507
+ const jwtToken = await authBroker.getToken(destination);
1508
+ // Load refresh token from .env if available (AuthBroker doesn't return it, but we can load it)
1509
+ // For now, we'll just use the access token - refresh will be handled by AuthBroker automatically
1510
+ const refreshToken = undefined; // AuthBroker handles refresh internally
1511
+ // Process JWT config update with the token from AuthBroker
1512
+ this.processJwtConfigUpdate(sapUrl, jwtToken, refreshToken, destination, sessionId);
1513
+ logger_1.logger?.info("Updated SAP configuration using AuthBroker (destination-based)", {
1514
+ type: "SAP_CONFIG_UPDATED_AUTH_BROKER",
1515
+ destination,
1516
+ url: sapUrl,
1517
+ sessionId: sessionId?.substring(0, 8),
1518
+ });
1519
+ }
1520
+ catch (error) {
1521
+ logger_1.logger?.error("Failed to get token from AuthBroker", {
1522
+ type: "AUTH_BROKER_ERROR",
1523
+ destination,
1524
+ error: error instanceof Error ? error.message : String(error),
1525
+ });
1526
+ // Don't throw - let the request continue with existing config or fail later
1527
+ }
1528
+ }
1529
+ processBasicAuthConfigUpdate(sapUrl, username, password, sessionId) {
1530
+ let baseConfig = this.sapConfig;
1531
+ if (!baseConfig || baseConfig.url === "http://placeholder") {
1532
+ try {
1533
+ baseConfig = (0, config_js_1.getConfig)();
1534
+ }
1535
+ catch (error) {
1536
+ logger_1.logger?.warn("Failed to load base SAP config when applying headers", {
1537
+ type: "SAP_CONFIG_HEADER_APPLY_FAILED",
1538
+ error: error instanceof Error ? error.message : String(error),
1539
+ });
1540
+ baseConfig = {
1541
+ url: sapUrl,
1542
+ authType: "basic",
1543
+ };
1544
+ }
1545
+ }
1546
+ // Check if any configuration changed
1547
+ const urlChanged = sapUrl !== baseConfig.url;
1548
+ const authTypeChanged = "basic" !== baseConfig.authType;
1549
+ const credentialsChanged = baseConfig.username !== username ||
1550
+ baseConfig.password !== password;
1551
+ if (!urlChanged && !authTypeChanged && !credentialsChanged) {
1552
+ return;
1553
+ }
1554
+ const newConfig = {
1555
+ ...baseConfig,
1556
+ url: sapUrl,
1557
+ authType: "basic",
1558
+ username,
1559
+ password,
1560
+ };
1561
+ (0, config_js_1.setSapConfigOverride)(newConfig);
1562
+ this.sapConfig = newConfig;
1563
+ // Store config in session if sessionId is provided
1564
+ if (sessionId) {
1565
+ const clientKeyForSession = this.streamableSessionIndex.get(sessionId);
1566
+ const session = clientKeyForSession ? this.streamableHttpSessions.get(clientKeyForSession) : undefined;
1567
+ if (session) {
1568
+ session.sapConfig = newConfig;
1569
+ }
1570
+ }
1571
+ // Force connection cache invalidation (for backward compatibility)
1572
+ const { invalidateConnectionCache } = require('./lib/utils');
1573
+ try {
1574
+ invalidateConnectionCache();
1575
+ }
1576
+ catch (error) {
1577
+ logger_1.logger?.debug("Connection cache invalidation failed", {
1578
+ type: "CONNECTION_CACHE_INVALIDATION_FAILED",
1579
+ error: error instanceof Error ? error.message : String(error),
1580
+ });
1581
+ }
1582
+ logger_1.logger?.info("Updated SAP configuration from HTTP headers (Basic)", {
1583
+ type: "SAP_CONFIG_UPDATED",
1584
+ urlChanged: Boolean(urlChanged),
1585
+ authTypeChanged: Boolean(authTypeChanged),
1586
+ credentialsChanged: Boolean(credentialsChanged),
1587
+ hasUsername: Boolean(username),
1588
+ sessionId: sessionId?.substring(0, 8),
1589
+ });
1590
+ }
1591
+ /**
1592
+ * Check if connection is from localhost
1593
+ */
1594
+ isLocalConnection(remoteAddress) {
1595
+ if (!remoteAddress) {
1596
+ return false;
1597
+ }
1598
+ // Check for IPv4 localhost
1599
+ if (remoteAddress === "127.0.0.1" || remoteAddress === "localhost") {
1600
+ return true;
1601
+ }
1602
+ // Check for IPv6 localhost
1603
+ if (remoteAddress === "::1" || remoteAddress === "::ffff:127.0.0.1") {
1604
+ return true;
1605
+ }
1606
+ // Check if it's a loopback interface
1607
+ if (remoteAddress.startsWith("127.") || remoteAddress.startsWith("::1")) {
1608
+ return true;
1609
+ }
1610
+ return false;
1611
+ }
1612
+ /**
1613
+ * Check if server is listening on non-local interface (0.0.0.0)
1614
+ * When listening on 0.0.0.0, we don't use default destination - client must provide all headers
1615
+ */
1616
+ isListeningOnNonLocalInterface() {
1617
+ if (this.transportConfig.type === "streamable-http" || this.transportConfig.type === "sse") {
1618
+ const host = this.transportConfig.host;
1619
+ // If host is 0.0.0.0, ::, or empty, it accepts connections from all interfaces
1620
+ return host === "0.0.0.0" || host === "::" || host === "" || !host;
1621
+ }
1622
+ // stdio is always local
1623
+ return false;
1624
+ }
1625
+ /**
1626
+ * Check if request has SAP connection headers
1627
+ */
1628
+ hasSapHeaders(headers) {
1629
+ if (!headers) {
1630
+ return false;
1631
+ }
1632
+ // Check for destination-based auth headers
1633
+ if (headers[interfaces_1.HEADER_SAP_DESTINATION_SERVICE] || headers[interfaces_1.HEADER_MCP_DESTINATION]) {
1634
+ return true;
1635
+ }
1636
+ // Check for direct auth headers
1637
+ const sapUrl = headers[interfaces_1.HEADER_SAP_URL];
1638
+ const sapAuthType = headers[interfaces_1.HEADER_SAP_AUTH_TYPE];
1639
+ return !!(sapUrl && sapAuthType);
1640
+ }
1641
+ /**
1642
+ * Constructor for the mcp_abap_adt_server class.
1643
+ */
1644
+ constructor(options) {
1645
+ this.allowProcessExit = options?.allowProcessExit ?? true;
1646
+ this.registerSignalHandlers = options?.registerSignalHandlers ?? true;
1647
+ this.defaultMcpDestination = defaultMcpDestination;
1648
+ this.envFilePath = envFilePath; // Store .env file path for SessionStore creation
1649
+ // Initialize AuthBroker factory
1650
+ // Pass useAuthBroker to ensure .env is not used when --auth-broker is specified
1651
+ this.authBrokerFactory = new authBrokerFactory_1.AuthBrokerFactory({
1652
+ defaultMcpDestination: this.defaultMcpDestination,
1653
+ defaultDestination: this.defaultDestination,
1654
+ envFilePath: this.envFilePath,
1655
+ authBrokerPath,
1656
+ unsafe,
1657
+ transportType: transportType,
1658
+ useAuthBroker: useAuthBroker, // Important: prevents .env from being used when --auth-broker is specified
1659
+ logger: // Important: prevents .env from being used when --auth-broker is specified
1660
+ logger_1.logger,
1661
+ });
1662
+ this.mcpHandlers = new mcp_handlers_1.McpHandlers();
1663
+ // Check if .env file exists (was loaded at startup)
1664
+ // This is used to determine if we should restrict non-local connections
1665
+ this.hasEnvFile = fs_1.default.existsSync(envFilePath || path_1.default.resolve(process.cwd(), ".env"));
1666
+ if (options?.connection) {
1667
+ setAbapConnectionOverride(options.connection);
1668
+ }
1669
+ else {
1670
+ setAbapConnectionOverride(undefined);
1671
+ }
1672
+ if (!options?.connection) {
1673
+ (0, config_js_1.setSapConfigOverride)(options?.sapConfig);
1674
+ }
1675
+ // CHANGED: Don't validate config in constructor - will validate on actual ABAP requests
1676
+ // This allows creating server instance without .env file when using runtime config (e.g., from HTTP headers)
1677
+ try {
1678
+ if (options?.sapConfig) {
1679
+ this.sapConfig = options.sapConfig;
1680
+ }
1681
+ else if (!options?.connection) {
1682
+ // Don't load .env config if using auth-broker (--mcp or --auth-broker)
1683
+ // Connection will be created via auth-broker instead
1684
+ if (this.defaultMcpDestination || useAuthBroker) {
1685
+ // Using auth-broker, don't load .env config
1686
+ this.sapConfig = {
1687
+ url: "http://placeholder",
1688
+ authType: "basic",
1689
+ };
1690
+ logger_1.logger?.debug("Skipping .env config load (using auth-broker)", {
1691
+ type: "SKIP_ENV_CONFIG_FOR_AUTH_BROKER",
1692
+ defaultMcpDestination: this.defaultMcpDestination,
1693
+ useAuthBroker,
1694
+ });
1695
+ }
1696
+ else {
1697
+ // Try to get config from .env, but don't fail if it's invalid - server should still initialize
1698
+ // Invalid config will be caught when handlers try to use it
1699
+ try {
1700
+ this.sapConfig = (0, config_js_1.getConfig)();
1701
+ }
1702
+ catch (configError) {
1703
+ // For stdio mode, we want the server to initialize even with invalid config
1704
+ // The error will be shown when user tries to use a tool
1705
+ // Check stdio mode using environment variable or stdin check (transportConfig not set yet)
1706
+ const isStdioMode = process.env.MCP_TRANSPORT === "stdio" || !process.stdin.isTTY;
1707
+ if (isStdioMode) {
1708
+ // In stdio mode, write error to stderr (safe for MCP protocol)
1709
+ process.stderr.write(`[MCP] ⚠ WARNING: Invalid SAP configuration: ${configError instanceof Error ? configError.message : String(configError)}\n`);
1710
+ process.stderr.write(`[MCP] Server will start, but tools will fail until configuration is fixed.\n`);
1711
+ }
1712
+ logger_1.logger?.warn("SAP config invalid at initialization, will use placeholder", {
1713
+ type: "CONFIG_INVALID",
1714
+ error: configError instanceof Error ? configError.message : String(configError),
1715
+ });
1716
+ // Set a placeholder that will be replaced when valid config is provided
1717
+ this.sapConfig = { url: "http://placeholder", authType: "jwt", jwtToken: "placeholder" };
1718
+ }
1719
+ }
1720
+ }
1721
+ else {
1722
+ this.sapConfig = { url: "http://injected-connection", authType: "jwt", jwtToken: "injected" };
1723
+ }
1724
+ }
1725
+ catch (error) {
1726
+ // If config is not available yet, that's OK - it will be provided later via setSapConfigOverride or DI
1727
+ logger_1.logger?.warn("SAP config not available at initialization, will use runtime config", {
1728
+ type: "CONFIG_DEFERRED",
1729
+ error: error instanceof Error ? error.message : String(error),
1730
+ });
1731
+ // Set a placeholder that will be replaced
1732
+ this.sapConfig = { url: "http://placeholder", authType: "jwt", jwtToken: "placeholder" };
1733
+ }
1734
+ try {
1735
+ this.transportConfig = options?.transportConfig ?? parseTransportConfig();
1736
+ }
1737
+ catch (error) {
1738
+ const message = error instanceof Error ? error.message : String(error);
1739
+ // Always write error to stderr (stderr is safe even in stdio mode)
1740
+ logger_1.logger?.error("Failed to parse transport configuration", {
1741
+ type: "TRANSPORT_CONFIG_ERROR",
1742
+ error: message,
1743
+ });
1744
+ process.stderr.write(`[MCP] ✗ ERROR: Failed to parse transport configuration: ${message}\n`);
1745
+ if (this.allowProcessExit) {
1746
+ // On Windows, add a small delay before exit to allow error message to be visible
1747
+ if (process.platform === 'win32') {
1748
+ setTimeout(() => process.exit(1), 100);
1749
+ }
1750
+ else {
1751
+ process.exit(1);
1752
+ }
1753
+ }
1754
+ throw error instanceof Error ? error : new Error(message);
1755
+ }
1756
+ // Create McpServer (for all transports)
1757
+ this.mcpServer = new mcp_js_1.McpServer({
1758
+ name: "mcp-abap-adt",
1759
+ version: "0.1.0"
1760
+ });
1761
+ // AuthBroker will be initialized lazily when needed (per destination)
1762
+ // Only for HTTP/streamable-http transport (not for stdio or SSE)
1763
+ const isHttpTransport = this.transportConfig.type === "streamable-http";
1764
+ if (isHttpTransport && !useAuthBroker) {
1765
+ // Only initialize if --auth-broker flag is NOT set (for stdio/SSE, ignore the flag)
1766
+ // For stdio/SSE, --auth-broker flag is ignored
1767
+ }
1768
+ else if (isHttpTransport && useAuthBroker) {
1769
+ // Support DEBUG_AUTH_BROKER as alias for DEBUG_AUTH_LOG
1770
+ // If DEBUG_AUTH_BROKER is set, ensure DEBUG_AUTH_LOG is also set for auth-broker package
1771
+ if (process.env.DEBUG_AUTH_BROKER === "true" && !process.env.DEBUG_AUTH_LOG) {
1772
+ process.env.DEBUG_AUTH_LOG = "true";
1773
+ }
1774
+ // Get paths for service keys and sessions
1775
+ // Use authBrokerPath if provided, otherwise use default platform paths
1776
+ // If authBrokerPath is provided, it's the base path - service-keys and sessions will be subdirectories
1777
+ const customPath = authBrokerPath ? path_1.default.resolve(authBrokerPath.replace(/^~/, os.homedir())) : undefined;
1778
+ const serviceKeysPaths = (0, platformPaths_1.getPlatformPaths)(customPath, 'service-keys');
1779
+ const sessionsPaths = (0, platformPaths_1.getPlatformPaths)(customPath, 'sessions');
1780
+ // Create directories if they don't exist
1781
+ const fs = require('fs');
1782
+ const serviceKeysDir = serviceKeysPaths[0]; // First path is where we save
1783
+ const sessionsDir = sessionsPaths[0]; // First path is where we save
1784
+ if (!fs.existsSync(serviceKeysDir)) {
1785
+ fs.mkdirSync(serviceKeysDir, { recursive: true });
1786
+ logger_1.logger?.info("Created service keys directory", {
1787
+ type: "SERVICE_KEYS_DIR_CREATED",
1788
+ path: serviceKeysDir,
1789
+ });
1790
+ }
1791
+ if (!fs.existsSync(sessionsDir)) {
1792
+ fs.mkdirSync(sessionsDir, { recursive: true });
1793
+ logger_1.logger?.info("Created sessions directory", {
1794
+ type: "SESSIONS_DIR_CREATED",
1795
+ path: sessionsDir,
1796
+ });
1797
+ }
1798
+ logger_1.logger?.info("AuthBroker will be initialized lazily when destination is needed", {
1799
+ type: "AUTH_BROKER_LAZY_INIT",
1800
+ transport: this.transportConfig.type,
1801
+ useAuthBrokerFlag: useAuthBroker,
1802
+ hasEnvFile: !!envFilePath,
1803
+ authBrokerPath: customPath || 'default',
1804
+ });
1805
+ // Print paths information with stars and empty lines
1806
+ console.log('');
1807
+ console.log('********************************************************************************');
1808
+ console.log('* AuthBroker Storage Paths:');
1809
+ console.log('*');
1810
+ console.log('* Service Keys (searched in order):');
1811
+ serviceKeysPaths.forEach((p, i) => {
1812
+ console.log(`* ${i + 1}. ${p}`);
1813
+ });
1814
+ console.log('*');
1815
+ console.log('* Sessions (saved to):');
1816
+ sessionsPaths.forEach((p, i) => {
1817
+ console.log(`* ${i + 1}. ${p}`);
1818
+ });
1819
+ console.log('********************************************************************************');
1820
+ console.log('');
1821
+ }
1822
+ else {
1823
+ logger_1.logger?.info("AuthBroker not available - not needed for this transport type", {
1824
+ type: "AUTH_BROKER_SKIPPED",
1825
+ transport: this.transportConfig.type,
1826
+ reason: "AuthBroker is only used for HTTP/streamable-http transport. For stdio/SSE, use .env file instead.",
1827
+ });
1828
+ }
1829
+ if (this.transportConfig.type === "streamable-http") {
1830
+ logger_1.logger?.info("Transport configured", {
1831
+ type: "TRANSPORT_CONFIG",
1832
+ transport: this.transportConfig.type,
1833
+ host: this.transportConfig.host,
1834
+ port: this.transportConfig.port,
1835
+ enableJsonResponse: this.transportConfig.enableJsonResponse,
1836
+ allowedOrigins: this.transportConfig.allowedOrigins ?? [],
1837
+ allowedHosts: this.transportConfig.allowedHosts ?? [],
1838
+ enableDnsRebindingProtection: this.transportConfig.enableDnsRebindingProtection,
1839
+ });
1840
+ }
1841
+ else if (this.transportConfig.type === "sse") {
1842
+ logger_1.logger?.info("Transport configured", {
1843
+ type: "TRANSPORT_CONFIG",
1844
+ transport: this.transportConfig.type,
1845
+ host: this.transportConfig.host,
1846
+ port: this.transportConfig.port,
1847
+ allowedOrigins: this.transportConfig.allowedOrigins ?? [],
1848
+ allowedHosts: this.transportConfig.allowedHosts ?? [],
1849
+ enableDnsRebindingProtection: this.transportConfig.enableDnsRebindingProtection,
1850
+ });
1851
+ }
1852
+ else {
1853
+ logger_1.logger?.info("Transport configured", {
1854
+ type: "TRANSPORT_CONFIG",
1855
+ transport: this.transportConfig.type,
1856
+ });
1857
+ }
1858
+ }
1859
+ /**
1860
+ * Creates AbapConnection from available sources (headers, broker, or .env)
1861
+ * @param headers - Optional HTTP headers containing connection info
1862
+ * @param sessionId - Optional session ID
1863
+ * @param destination - Optional destination name for broker-based auth
1864
+ * @returns AbapConnection instance
1865
+ * @private
1866
+ */
1867
+ async getOrCreateConnectionForServer(headers, sessionId, destination) {
1868
+ // Extract destination from headers if not provided
1869
+ let actualDestination = destination;
1870
+ if (!actualDestination && headers) {
1871
+ actualDestination = headers[interfaces_1.HEADER_MCP_DESTINATION] ||
1872
+ headers['X-MCP-Destination'] ||
1873
+ headers['x-mcp-destination'];
1874
+ }
1875
+ // Use default destination if still not set
1876
+ if (!actualDestination && this.defaultDestination) {
1877
+ actualDestination = this.defaultDestination;
1878
+ logger_1.logger?.debug('Using default destination for connection', {
1879
+ destination: actualDestination,
1880
+ type: 'DEFAULT_DESTINATION_USED',
1881
+ });
1882
+ }
1883
+ logger_1.logger?.info("Creating connection for server", {
1884
+ type: "CONNECTION_CREATION_START",
1885
+ hasHeaders: !!headers,
1886
+ sessionId: sessionId || 'not-provided',
1887
+ destination: actualDestination || 'not-provided',
1888
+ });
1889
+ // Try to get connection from session context first
1890
+ const context = utils_1.sessionContext.getStore();
1891
+ if (context?.sapConfig) {
1892
+ logger_1.logger?.info("Using connection from session context", {
1893
+ type: "CONNECTION_FROM_SESSION_CONTEXT",
1894
+ sessionId: sessionId || 'not-provided',
1895
+ url: context.sapConfig.url,
1896
+ authType: context.sapConfig.authType,
1897
+ });
1898
+ const sessionConnection = (0, connection_1.createAbapConnection)(context.sapConfig, loggerAdapter_1.loggerAdapter, sessionId || `mcp-server-${(0, crypto_1.randomUUID)()}`);
1899
+ return sessionConnection;
1900
+ }
1901
+ // If headers provided, create connection from headers
1902
+ if (headers && this.hasSapHeaders(headers)) {
1903
+ // Get config from headers (already processed by applyAuthHeaders)
1904
+ const config = this.sapConfig;
1905
+ if (config && config.url !== "http://placeholder" && config.url !== "http://injected-connection") {
1906
+ // If destination is known, try to refresh token via auth broker before creating connection
1907
+ if (actualDestination) {
1908
+ try {
1909
+ const brokerForHeaders = await this.getOrCreateAuthBroker(actualDestination, sessionId || 'global');
1910
+ if (brokerForHeaders) {
1911
+ const freshToken = await brokerForHeaders.getToken(actualDestination);
1912
+ if (freshToken && config) {
1913
+ config.authorizationToken = freshToken;
1914
+ }
1915
+ const { registerAuthBroker } = require('./lib/utils');
1916
+ registerAuthBroker(actualDestination, brokerForHeaders);
1917
+ }
1918
+ }
1919
+ catch (e) {
1920
+ logger_1.logger?.warn?.("Auth broker refresh failed for header connection", {
1921
+ type: "CONNECTION_FROM_HEADERS_BROKER_WARN",
1922
+ error: e instanceof Error ? e.message : String(e),
1923
+ });
1924
+ }
1925
+ }
1926
+ logger_1.logger?.info("Using connection from headers", {
1927
+ type: "CONNECTION_FROM_HEADERS",
1928
+ sessionId: sessionId || 'not-provided',
1929
+ url: config.url,
1930
+ authType: config.authType,
1931
+ });
1932
+ let connection = (0, connection_1.createAbapConnection)(config, loggerAdapter_1.loggerAdapter, sessionId || `mcp-server-${(0, crypto_1.randomUUID)()}`);
1933
+ if (actualDestination) {
1934
+ const { createDestinationAwareConnection } = require('./lib/utils');
1935
+ connection = createDestinationAwareConnection(connection, actualDestination);
1936
+ }
1937
+ return connection;
1938
+ }
1939
+ }
1940
+ // If destination provided, create connection via broker
1941
+ if (actualDestination) {
1942
+ try {
1943
+ logger_1.logger?.info("Attempting to create connection via auth broker", {
1944
+ type: "CONNECTION_VIA_BROKER_ATTEMPT",
1945
+ destination: actualDestination,
1946
+ sessionId: sessionId || 'not-provided',
1947
+ });
1948
+ const authBroker = await this.getOrCreateAuthBroker(actualDestination, sessionId || 'global');
1949
+ if (authBroker) {
1950
+ const connConfig = await authBroker.getConnectionConfig(actualDestination);
1951
+ if (connConfig?.serviceUrl) {
1952
+ // Get fresh token now to ensure connection can be created
1953
+ const jwtToken = await authBroker.getToken(actualDestination);
1954
+ if (jwtToken) {
1955
+ // Register AuthBroker in global registry for connection to use during token refresh
1956
+ const { registerAuthBroker } = require('./lib/utils');
1957
+ registerAuthBroker(actualDestination, authBroker);
1958
+ const config = {
1959
+ url: connConfig.serviceUrl,
1960
+ authType: "jwt",
1961
+ jwtToken,
1962
+ client: connConfig.sapClient,
1963
+ };
1964
+ logger_1.logger?.info("Using connection from auth broker", {
1965
+ type: "CONNECTION_FROM_BROKER",
1966
+ destination: actualDestination,
1967
+ sessionId: sessionId || 'not-provided',
1968
+ url: config.url,
1969
+ authType: config.authType,
1970
+ });
1971
+ // Create connection and wrap it to intercept refreshToken()/makeAdtRequest for destination-based authentication
1972
+ let connection = (0, connection_1.createAbapConnection)(config, loggerAdapter_1.loggerAdapter, sessionId || `mcp-server-${(0, crypto_1.randomUUID)()}`);
1973
+ // Wrap connection to intercept refreshToken()/makeAdtRequest
1974
+ const { createDestinationAwareConnection } = require('./lib/utils');
1975
+ connection = createDestinationAwareConnection(connection, actualDestination);
1976
+ return connection;
1977
+ }
1978
+ }
1979
+ }
1980
+ }
1981
+ catch (error) {
1982
+ logger_1.logger?.warn("Failed to create connection from destination", {
1983
+ type: "CONNECTION_FROM_DESTINATION_FAILED",
1984
+ destination: actualDestination,
1985
+ error: error instanceof Error ? error.message : String(error),
1986
+ });
1987
+ }
1988
+ }
1989
+ // Fallback to default config (from .env or constructor)
1990
+ // BUT: Don't use .env fallback if we have defaultDestination (means we're using auth-broker)
1991
+ // This prevents .env from being used when --mcp is specified
1992
+ if (this.sapConfig &&
1993
+ this.sapConfig.url !== "http://placeholder" &&
1994
+ this.sapConfig.url !== "http://injected-connection" &&
1995
+ !this.defaultDestination) { // Don't fallback to .env if using auth-broker
1996
+ logger_1.logger?.info("Using connection from default config (.env or constructor)", {
1997
+ type: "CONNECTION_FROM_DEFAULT_CONFIG",
1998
+ sessionId: sessionId || 'not-provided',
1999
+ url: this.sapConfig.url,
2000
+ authType: this.sapConfig.authType,
2001
+ });
2002
+ return (0, connection_1.createAbapConnection)(this.sapConfig, loggerAdapter_1.loggerAdapter, sessionId || `mcp-server-${(0, crypto_1.randomUUID)()}`);
2003
+ }
2004
+ logger_1.logger?.error("Unable to create connection: no valid configuration available", {
2005
+ type: "CONNECTION_CREATION_FAILED",
2006
+ hasHeaders: !!headers,
2007
+ sessionId: sessionId || 'not-provided',
2008
+ destination: actualDestination || 'not-provided',
2009
+ defaultDestination: this.defaultDestination || 'not-set',
2010
+ hasSapConfig: !!this.sapConfig,
2011
+ sapConfigUrl: this.sapConfig?.url || 'not-set',
2012
+ });
2013
+ throw new Error("Unable to create connection: no valid configuration available");
2014
+ }
2015
+ /**
2016
+ * Creates a new McpServer instance with all handlers registered
2017
+ * Used for SSE sessions where each session needs its own server instance
2018
+ * @param context - HandlerContext instance to use for handlers
2019
+ * @private
2020
+ */
2021
+ createMcpServerForSession(context) {
2022
+ const server = new mcp_js_1.McpServer({
2023
+ name: "mcp-abap-adt",
2024
+ version: "0.1.0"
2025
+ });
2026
+ // Register all tools using McpHandlers
2027
+ const handlers = new mcp_handlers_1.McpHandlers();
2028
+ handlers.RegisterAllToolsOnServer(server, context);
2029
+ return server;
2030
+ }
2031
+ /**
2032
+ * Sets up handlers for new McpServer using registerTool (recommended API)
2033
+ * @param context - HandlerContext with connection and logger
2034
+ * @private
2035
+ */
2036
+ setupMcpServerHandlers(context) {
2037
+ // Connection is already wrapped with token refresh in run() method
2038
+ // Just register handlers with the provided context
2039
+ this.mcpHandlers.RegisterAllToolsOnServer(this.mcpServer, context);
2040
+ }
2041
+ /**
2042
+ * Creates a wrapper connection that refreshes token before each request
2043
+ * @private
2044
+ */
2045
+ createConnectionWithTokenRefresh(connection, destination) {
2046
+ const connectionWithRefresh = connection;
2047
+ // Wrap makeAdtRequest to refresh token before each request
2048
+ if (connectionWithRefresh.makeAdtRequest) {
2049
+ const originalMakeAdtRequest = connectionWithRefresh.makeAdtRequest.bind(connection);
2050
+ connectionWithRefresh.makeAdtRequest = async function (options) {
2051
+ // Always refresh token via AuthBroker before request (AuthBroker will refresh if needed)
2052
+ const { getAuthBroker } = require('./lib/utils');
2053
+ const authBroker = getAuthBroker(destination);
2054
+ logger_1.logger?.debug('makeAdtRequest called, checking AuthBroker', {
2055
+ destination,
2056
+ hasAuthBroker: !!authBroker,
2057
+ method: options?.method,
2058
+ url: options?.url,
2059
+ });
2060
+ if (!authBroker) {
2061
+ logger_1.logger?.warn('AuthBroker not found for destination, cannot refresh token', {
2062
+ destination,
2063
+ type: 'AUTH_BROKER_NOT_FOUND',
2064
+ });
2065
+ // Continue without refresh - will use existing token
2066
+ return await originalMakeAdtRequest(options);
2067
+ }
2068
+ try {
2069
+ // Get fresh token from AuthBroker (will refresh automatically if expired)
2070
+ logger_1.logger?.debug('Requesting fresh token from AuthBroker', {
2071
+ destination,
2072
+ type: 'TOKEN_REFRESH_REQUEST',
2073
+ });
2074
+ const freshToken = await authBroker.getToken(destination);
2075
+ const config = connectionWithRefresh.getConfig();
2076
+ if (config) {
2077
+ // Always update token (AuthBroker.getToken() returns fresh token)
2078
+ const tokenChanged = config.jwtToken !== freshToken;
2079
+ const oldToken = config.jwtToken;
2080
+ logger_1.logger?.debug('Updating connection config with fresh token', {
2081
+ destination,
2082
+ tokenChanged,
2083
+ oldTokenPreview: oldToken ? oldToken.substring(0, 20) + '...' : 'none',
2084
+ newTokenPreview: freshToken.substring(0, 20) + '...',
2085
+ type: 'TOKEN_UPDATE',
2086
+ });
2087
+ config.jwtToken = freshToken;
2088
+ // Verify token was updated
2089
+ const verifyConfig = connectionWithRefresh.getConfig();
2090
+ if (verifyConfig.jwtToken !== freshToken) {
2091
+ logger_1.logger?.error('Token update failed - config.jwtToken does not match fresh token', {
2092
+ destination,
2093
+ expectedPreview: freshToken.substring(0, 20) + '...',
2094
+ actualPreview: verifyConfig.jwtToken ? verifyConfig.jwtToken.substring(0, 20) + '...' : 'none',
2095
+ type: 'TOKEN_UPDATE_FAILED',
2096
+ });
2097
+ }
2098
+ // Get refresh token from session store if available
2099
+ try {
2100
+ const { authorizationConfig } = await authBroker.getConnectionConfig(destination);
2101
+ if (authorizationConfig?.refreshToken) {
2102
+ config.refreshToken = authorizationConfig.refreshToken;
2103
+ }
2104
+ }
2105
+ catch (configError) {
2106
+ // Ignore errors getting refresh token (not critical)
2107
+ logger_1.logger?.debug('Could not get refresh token from AuthBroker', {
2108
+ destination,
2109
+ error: configError instanceof Error ? configError.message : String(configError),
2110
+ });
2111
+ }
2112
+ // Reset connection only if token changed to ensure fresh token is used
2113
+ // This ensures that buildAuthorizationHeader() will use the new token
2114
+ // Don't reset if token didn't change to preserve CSRF token and cookies
2115
+ if (tokenChanged) {
2116
+ connectionWithRefresh.reset();
2117
+ logger_1.logger?.info('Token refreshed via AuthBroker before request', {
2118
+ destination,
2119
+ tokenPreview: freshToken.substring(0, 20) + '...',
2120
+ oldTokenPreview: oldToken ? oldToken.substring(0, 20) + '...' : 'none',
2121
+ type: 'TOKEN_REFRESHED_BEFORE_REQUEST',
2122
+ });
2123
+ }
2124
+ else {
2125
+ // Token didn't change - no need to reset, connection can reuse CSRF token and cookies
2126
+ logger_1.logger?.debug('Token verified via AuthBroker before request (no change, keeping session)', {
2127
+ destination,
2128
+ tokenPreview: freshToken.substring(0, 20) + '...',
2129
+ type: 'TOKEN_VERIFIED_BEFORE_REQUEST',
2130
+ });
2131
+ }
2132
+ }
2133
+ else {
2134
+ logger_1.logger?.warn('Connection config not available, cannot update token', {
2135
+ destination,
2136
+ type: 'CONNECTION_CONFIG_NOT_AVAILABLE',
2137
+ });
2138
+ }
2139
+ }
2140
+ catch (error) {
2141
+ logger_1.logger?.error('Failed to refresh token before request', {
2142
+ error: error instanceof Error ? error.message : String(error),
2143
+ destination,
2144
+ errorStack: error instanceof Error ? error.stack : undefined,
2145
+ type: 'TOKEN_REFRESH_FAILED',
2146
+ });
2147
+ // Continue with existing token - will try to use it, but may fail if expired
2148
+ }
2149
+ // Call original makeAdtRequest (with potentially refreshed token)
2150
+ try {
2151
+ return await originalMakeAdtRequest(options);
2152
+ }
2153
+ catch (error) {
2154
+ // If we still get 401/403 or "JWT token has expired" error, try to refresh token again
2155
+ const isAuthError = error?.response?.status === 401 || error?.response?.status === 403;
2156
+ const isExpiredTokenError = error?.message?.includes('JWT token has expired') ||
2157
+ error?.message?.includes('Please re-authenticate');
2158
+ if ((isAuthError || isExpiredTokenError) && authBroker) {
2159
+ // Check if this is a permissions error, not an auth error
2160
+ const responseData = error?.response?.data;
2161
+ const responseText = typeof responseData === "string" ? responseData : JSON.stringify(responseData || "");
2162
+ if (responseText.includes("ExceptionResourceNoAccess") ||
2163
+ responseText.includes("No authorization") ||
2164
+ responseText.includes("Missing authorization")) {
2165
+ // Not an auth token issue, re-throw
2166
+ throw error;
2167
+ }
2168
+ // Try to refresh token again and retry
2169
+ try {
2170
+ logger_1.logger?.info('Got 401/403 after token refresh, attempting to refresh token again', {
2171
+ destination,
2172
+ error: error?.message,
2173
+ type: 'TOKEN_REFRESH_RETRY',
2174
+ });
2175
+ const freshToken = await authBroker.getToken(destination);
2176
+ const config = connectionWithRefresh.getConfig();
2177
+ if (config) {
2178
+ config.jwtToken = freshToken;
2179
+ // Get refresh token from session store if available
2180
+ try {
2181
+ const { authorizationConfig } = await authBroker.getConnectionConfig(destination);
2182
+ if (authorizationConfig?.refreshToken) {
2183
+ config.refreshToken = authorizationConfig.refreshToken;
2184
+ }
2185
+ }
2186
+ catch (configError) {
2187
+ // Ignore errors getting refresh token
2188
+ }
2189
+ // Reset connection to use new token
2190
+ connectionWithRefresh.reset();
2191
+ logger_1.logger?.info('Token refreshed again, retrying request', {
2192
+ destination,
2193
+ tokenPreview: freshToken.substring(0, 20) + '...',
2194
+ type: 'TOKEN_REFRESHED_RETRY',
2195
+ });
2196
+ // Retry the request with new token
2197
+ return await originalMakeAdtRequest(options);
2198
+ }
2199
+ }
2200
+ catch (refreshError) {
2201
+ logger_1.logger?.error('Failed to refresh token on retry', {
2202
+ destination,
2203
+ error: refreshError instanceof Error ? refreshError.message : String(refreshError),
2204
+ type: 'TOKEN_REFRESH_RETRY_FAILED',
2205
+ });
2206
+ // Re-throw original error if refresh fails
2207
+ throw error;
2208
+ }
2209
+ }
2210
+ // Re-throw error if not handled
2211
+ throw error;
2212
+ }
2213
+ };
2214
+ }
2215
+ return connectionWithRefresh;
2216
+ }
2217
+ setupSignalHandlers() {
2218
+ const signals = ["SIGINT", "SIGTERM"];
2219
+ for (const signal of signals) {
2220
+ process.on(signal, () => {
2221
+ if (this.shuttingDown) {
2222
+ return;
2223
+ }
2224
+ this.shuttingDown = true;
2225
+ logger_1.logger?.info("Received shutdown signal", {
2226
+ type: "SERVER_SHUTDOWN_SIGNAL",
2227
+ signal,
2228
+ transport: this.transportConfig.type,
2229
+ });
2230
+ void this.shutdown().finally(() => {
2231
+ if (this.allowProcessExit) {
2232
+ process.exit(0);
2233
+ }
2234
+ });
2235
+ });
2236
+ }
2237
+ }
2238
+ async shutdown() {
2239
+ try {
2240
+ await this.mcpServer.close();
2241
+ }
2242
+ catch (error) {
2243
+ logger_1.logger?.error("Failed to close MCP server", {
2244
+ type: "SERVER_SHUTDOWN_ERROR",
2245
+ error: error instanceof Error ? error.message : String(error),
2246
+ });
2247
+ }
2248
+ // Close all SSE sessions
2249
+ for (const [sessionId, session] of this.sseSessions.entries()) {
2250
+ try {
2251
+ await session.transport.close();
2252
+ session.server.server.close();
2253
+ logger_1.logger?.debug("SSE session closed during shutdown", {
2254
+ type: "SSE_SESSION_SHUTDOWN",
2255
+ sessionId,
2256
+ });
2257
+ }
2258
+ catch (error) {
2259
+ logger_1.logger?.error("Failed to close SSE session", {
2260
+ type: "SSE_SHUTDOWN_ERROR",
2261
+ error: error instanceof Error ? error.message : String(error),
2262
+ sessionId,
2263
+ });
2264
+ }
2265
+ }
2266
+ this.sseSessions.clear();
2267
+ if (this.httpServer) {
2268
+ await new Promise((resolve) => {
2269
+ this.httpServer?.close((closeError) => {
2270
+ if (closeError) {
2271
+ logger_1.logger?.error("Failed to close HTTP server", {
2272
+ type: "HTTP_SERVER_SHUTDOWN_ERROR",
2273
+ error: closeError instanceof Error ? closeError.message : String(closeError),
2274
+ });
2275
+ }
2276
+ resolve();
2277
+ });
2278
+ });
2279
+ this.httpServer = undefined;
2280
+ }
2281
+ }
2282
+ /**
2283
+ * Starts the MCP server and connects it to the transport.
2284
+ */
2285
+ async run() {
2286
+ if (this.transportConfig.type === "stdio") {
2287
+ // For stdio: must have destination or .env file (error if neither exists)
2288
+ if (!this.defaultMcpDestination && !this.hasEnvFile) {
2289
+ const errorMsg = "stdio transport requires either --mcp parameter or .env file";
2290
+ logger_1.logger?.error(errorMsg, {
2291
+ type: "STDIO_NO_DESTINATION_ERROR",
2292
+ });
2293
+ process.stderr.write(`[MCP] ✗ ERROR: ${errorMsg}\n`);
2294
+ process.stderr.write(`[MCP] Please provide --mcp=<destination> parameter or ensure .env file exists in current directory.\n`);
2295
+ if (this.allowProcessExit) {
2296
+ process.exit(1);
2297
+ }
2298
+ throw new Error(errorMsg);
2299
+ }
2300
+ // Initialize default broker (creates broker with default destination from --mcp or .env)
2301
+ await this.initializeDefaultBroker();
2302
+ // Create connection for stdio mode
2303
+ // Note: getOrCreateConnectionForServer already wraps connection via createDestinationAwareConnection
2304
+ // which handles token refresh on 401/403 errors. We also wrap it to refresh token before each request.
2305
+ const connection = await this.getOrCreateConnectionForServer(undefined, // no headers in stdio
2306
+ 'stdio-session', this.defaultDestination);
2307
+ // Additional wrapper to refresh token BEFORE each request (not just on error)
2308
+ // This ensures token is always fresh, preventing 401/403 errors
2309
+ const wrappedConnection = this.defaultDestination
2310
+ ? this.createConnectionWithTokenRefresh(connection, this.defaultDestination)
2311
+ : connection;
2312
+ logger_1.logger?.debug('Connection created for stdio', {
2313
+ destination: this.defaultDestination || 'none',
2314
+ hasTokenRefresh: !!this.defaultDestination,
2315
+ });
2316
+ // Setup handlers with connection
2317
+ this.setupMcpServerHandlers({ connection: wrappedConnection, logger: loggerAdapter_1.loggerAdapter });
2318
+ // Simple stdio setup like reference implementation
2319
+ const transport = new stdio_js_1.StdioServerTransport();
2320
+ await this.mcpServer.server.connect(transport);
2321
+ // Process stays alive waiting for messages from stdin
2322
+ return;
2323
+ }
2324
+ if (this.transportConfig.type === "streamable-http") {
2325
+ const httpConfig = this.transportConfig;
2326
+ // Initialize default broker for HTTP transport (creates broker with default destination from --mcp)
2327
+ await this.initializeDefaultBroker();
2328
+ // HTTP Server wrapper for StreamableHTTP transport (like the SDK example)
2329
+ const httpServer = (0, http_1.createServer)(async (req, res) => {
2330
+ // Log incoming HTTP request (if debug enabled)
2331
+ const debugHttpEnabled = process.env.DEBUG_HTTP_REQUESTS === "true" || process.env.DEBUG_CONNECTORS === "true";
2332
+ if (debugHttpEnabled) {
2333
+ const sanitizedHeaders = {};
2334
+ const sensitiveKeys = [interfaces_1.HEADER_AUTHORIZATION, interfaces_1.HEADER_SAP_JWT_TOKEN, interfaces_1.HEADER_SAP_REFRESH_TOKEN, interfaces_1.HEADER_SAP_PASSWORD, interfaces_1.HEADER_SAP_UAA_CLIENT_SECRET];
2335
+ for (const [key, value] of Object.entries(req.headers)) {
2336
+ if (sensitiveKeys.includes(key.toLowerCase())) {
2337
+ sanitizedHeaders[key] = '[REDACTED]';
2338
+ }
2339
+ else {
2340
+ sanitizedHeaders[key] = Array.isArray(value) ? value.join(', ') : (value || '');
2341
+ }
2342
+ }
2343
+ logger_1.logger?.info("HTTP Request received", {
2344
+ type: "HTTP_REQUEST",
2345
+ method: req.method,
2346
+ url: req.url,
2347
+ headers: sanitizedHeaders,
2348
+ remoteAddress: req.socket.remoteAddress,
2349
+ remotePort: req.socket.remotePort,
2350
+ });
2351
+ }
2352
+ // Only handle POST requests (like the example)
2353
+ if (req.method !== "POST") {
2354
+ if (debugHttpEnabled) {
2355
+ logger_1.logger?.warn("HTTP Method not allowed", {
2356
+ type: "HTTP_METHOD_NOT_ALLOWED",
2357
+ method: req.method,
2358
+ url: req.url,
2359
+ });
2360
+ }
2361
+ res.writeHead(405, { "Content-Type": "text/plain" });
2362
+ res.end("Method not allowed");
2363
+ return;
2364
+ }
2365
+ // HTTP: Restrict non-local connections if .env file exists and no SAP headers provided
2366
+ const remoteAddress = req.socket.remoteAddress;
2367
+ if (this.hasEnvFile && !this.hasSapHeaders(req.headers)) {
2368
+ if (!this.isLocalConnection(remoteAddress)) {
2369
+ logger_1.logger?.warn("HTTP: Non-local connection rejected (has .env but no SAP headers)", {
2370
+ type: "HTTP_NON_LOCAL_REJECTED",
2371
+ remoteAddress,
2372
+ hasEnvFile: this.hasEnvFile,
2373
+ });
2374
+ res.writeHead(403, { "Content-Type": "text/plain" });
2375
+ res.end(`Forbidden: Non-local connections require SAP connection headers (${interfaces_1.HEADER_SAP_URL}, ${interfaces_1.HEADER_SAP_AUTH_TYPE})`);
2376
+ return;
2377
+ }
2378
+ }
2379
+ // Track client (like the example)
2380
+ const clientID = `${req.socket.remoteAddress}:${req.socket.remotePort}`;
2381
+ logger_1.logger?.debug("Client connected", {
2382
+ type: "STREAMABLE_HTTP_CLIENT_CONNECTED",
2383
+ clientID,
2384
+ });
2385
+ // Extract session ID from headers (like the example)
2386
+ const clientSessionId = (req.headers["x-session-id"] || req.headers["mcp-session-id"]);
2387
+ let session = this.streamableHttpSessions.get(clientID);
2388
+ if (session && !this.streamableSessionIndex.has(session.sessionId)) {
2389
+ this.streamableSessionIndex.set(session.sessionId, clientID);
2390
+ }
2391
+ // If client sent session ID, try to find existing session
2392
+ if (clientSessionId && !session) {
2393
+ // Search for existing session by sessionId (client might have new IP:PORT)
2394
+ for (const [key, sess] of this.streamableHttpSessions.entries()) {
2395
+ if (sess.sessionId === clientSessionId) {
2396
+ session = sess;
2397
+ // Update clientID (port might have changed)
2398
+ this.streamableHttpSessions.delete(key);
2399
+ this.streamableHttpSessions.set(clientID, session);
2400
+ this.streamableSessionIndex.set(session.sessionId, clientID);
2401
+ logger_1.logger?.debug("Existing session restored", {
2402
+ type: "STREAMABLE_HTTP_SESSION_RESTORED",
2403
+ sessionId: session.sessionId,
2404
+ clientID,
2405
+ });
2406
+ break;
2407
+ }
2408
+ }
2409
+ }
2410
+ // If no session found, create new one
2411
+ if (!session) {
2412
+ session = {
2413
+ sessionId: (0, crypto_1.randomUUID)(),
2414
+ clientIP: req.socket.remoteAddress || "unknown",
2415
+ connectedAt: new Date(),
2416
+ requestCount: 0,
2417
+ };
2418
+ this.streamableHttpSessions.set(clientID, session);
2419
+ this.streamableSessionIndex.set(session.sessionId, clientID);
2420
+ logger_1.logger?.debug("New session created", {
2421
+ type: "STREAMABLE_HTTP_SESSION_CREATED",
2422
+ sessionId: session.sessionId,
2423
+ clientID,
2424
+ totalSessions: this.streamableHttpSessions.size,
2425
+ });
2426
+ }
2427
+ session.requestCount++;
2428
+ logger_1.logger?.debug("Request received", {
2429
+ type: "STREAMABLE_HTTP_REQUEST",
2430
+ sessionId: session.sessionId,
2431
+ requestNumber: session.requestCount,
2432
+ clientID,
2433
+ });
2434
+ // Handle client disconnect (like the example)
2435
+ req.on("close", () => {
2436
+ const closedSession = this.streamableHttpSessions.get(clientID);
2437
+ if (closedSession) {
2438
+ // Clean up connection cache for this session
2439
+ // Include destination to ensure correct connection is removed
2440
+ if (closedSession.sapConfig) {
2441
+ (0, utils_1.removeConnectionForSession)(closedSession.sessionId, closedSession.sapConfig, closedSession.destination);
2442
+ }
2443
+ this.streamableHttpSessions.delete(clientID);
2444
+ this.streamableSessionIndex.delete(closedSession.sessionId);
2445
+ logger_1.logger?.debug("Session closed", {
2446
+ type: "STREAMABLE_HTTP_SESSION_CLOSED",
2447
+ sessionId: closedSession.sessionId,
2448
+ requestCount: closedSession.requestCount,
2449
+ totalSessions: this.streamableHttpSessions.size,
2450
+ });
2451
+ }
2452
+ });
2453
+ try {
2454
+ // Read request body first to check if this request requires SAP config
2455
+ let body = null;
2456
+ const chunks = [];
2457
+ for await (const chunk of req) {
2458
+ chunks.push(chunk);
2459
+ }
2460
+ if (chunks.length > 0) {
2461
+ const bodyString = Buffer.concat(chunks).toString('utf-8');
2462
+ try {
2463
+ body = JSON.parse(bodyString);
2464
+ // Log MCP request (if debug enabled)
2465
+ const debugHttpEnabled = process.env.DEBUG_HTTP_REQUESTS === "true" || process.env.DEBUG_CONNECTORS === "true";
2466
+ if (debugHttpEnabled && body && typeof body === 'object') {
2467
+ const mcpMethod = body.method || body.jsonrpc ? 'JSON-RPC' : 'Unknown';
2468
+ const sanitizedParams = {};
2469
+ if (body.params && typeof body.params === 'object') {
2470
+ // Sanitize sensitive params
2471
+ for (const [key, value] of Object.entries(body.params)) {
2472
+ const lowerKey = key.toLowerCase();
2473
+ if (lowerKey.includes('password') || lowerKey.includes('token') || lowerKey.includes('secret')) {
2474
+ sanitizedParams[key] = '[REDACTED]';
2475
+ }
2476
+ else {
2477
+ sanitizedParams[key] = value;
2478
+ }
2479
+ }
2480
+ }
2481
+ logger_1.logger?.info("MCP Request", {
2482
+ type: "MCP_REQUEST",
2483
+ method: mcpMethod,
2484
+ jsonrpc: body.jsonrpc,
2485
+ id: body.id,
2486
+ params: sanitizedParams,
2487
+ sessionId: session.sessionId,
2488
+ });
2489
+ }
2490
+ }
2491
+ catch (parseError) {
2492
+ // If body is not JSON, pass as string or null
2493
+ body = bodyString || null;
2494
+ if (debugHttpEnabled) {
2495
+ logger_1.logger?.warn("Failed to parse request body as JSON", {
2496
+ type: "HTTP_BODY_PARSE_ERROR",
2497
+ error: parseError instanceof Error ? parseError.message : String(parseError),
2498
+ });
2499
+ }
2500
+ }
2501
+ }
2502
+ // Check if this is a request that requires SAP connection
2503
+ // Only tools/call requires SAP config - all other methods (tools/list, tools/get, initialize, ping, etc.) don't
2504
+ const isMethodRequest = body && typeof body === 'object' && body.method;
2505
+ const methodName = isMethodRequest ? String(body.method) : '';
2506
+ const requiresSapConfig = methodName === 'tools/call';
2507
+ // Apply auth headers before processing and store config in session
2508
+ // Apply headers if:
2509
+ // 1. This request requires SAP config (tools/call)
2510
+ // 2. AND (auth headers are present OR defaultMcpDestination is set)
2511
+ // If no headers are present and no defaultMcpDestination, fall back to .env file or base config
2512
+ if (requiresSapConfig && (this.hasSapHeaders(req.headers) || this.defaultMcpDestination)) {
2513
+ await this.applyAuthHeaders(req.headers, session.sessionId, clientID);
2514
+ }
2515
+ // Get SAP config for this session (from headers or existing session)
2516
+ // Re-read session config after applyAuthHeaders (it may have been updated)
2517
+ const updatedSession = this.streamableHttpSessions.get(clientID);
2518
+ let sessionSapConfig = updatedSession?.sapConfig || session.sapConfig || this.sapConfig;
2519
+ // If no headers provided and no session config, try to reload from .env file
2520
+ // This handles the case when .env file exists but config wasn't loaded properly
2521
+ // Skip .env reload if defaultMcpDestination is set (use auth-broker instead)
2522
+ if (requiresSapConfig && !this.hasSapHeaders(req.headers) && !this.defaultMcpDestination && (!sessionSapConfig || sessionSapConfig.url === "http://placeholder" || sessionSapConfig.url === "http://injected-connection")) {
2523
+ try {
2524
+ // Try to reload config from .env file
2525
+ const envConfig = (0, config_js_1.getConfig)();
2526
+ if (envConfig && envConfig.url && envConfig.url !== "http://placeholder" && envConfig.url !== "http://injected-connection") {
2527
+ // URL from getConfig() is already cleaned (it uses cleanUrl internally)
2528
+ // Just use it as-is
2529
+ const cleanedUrl = envConfig.url;
2530
+ // Validate URL format
2531
+ if (/^https?:\/\//.test(cleanedUrl)) {
2532
+ // Update config with cleaned URL
2533
+ const cleanedConfig = { ...envConfig, url: cleanedUrl };
2534
+ this.sapConfig = cleanedConfig;
2535
+ sessionSapConfig = cleanedConfig;
2536
+ logger_1.logger?.debug("Reloaded SAP config from .env file for HTTP request", {
2537
+ type: "SAP_CONFIG_RELOADED_FROM_ENV",
2538
+ sessionId: session.sessionId,
2539
+ url: cleanedUrl,
2540
+ });
2541
+ }
2542
+ else {
2543
+ logger_1.logger?.warn("Invalid URL format after cleaning", {
2544
+ type: "INVALID_URL_AFTER_CLEANING",
2545
+ url: cleanedUrl,
2546
+ });
2547
+ }
2548
+ }
2549
+ }
2550
+ catch (configError) {
2551
+ // Config reload failed, continue with existing config
2552
+ logger_1.logger?.debug("Failed to reload config from .env file", {
2553
+ type: "SAP_CONFIG_RELOAD_FAILED",
2554
+ error: configError instanceof Error ? configError.message : String(configError),
2555
+ });
2556
+ }
2557
+ }
2558
+ // Validate that we have a real config (not placeholder) only for requests that need it
2559
+ if (requiresSapConfig && (!sessionSapConfig || sessionSapConfig.url === "http://placeholder" || sessionSapConfig.url === "http://injected-connection")) {
2560
+ // Check what headers were provided
2561
+ const providedHeaders = {};
2562
+ if (req.headers[interfaces_1.HEADER_SAP_DESTINATION_SERVICE])
2563
+ providedHeaders[interfaces_1.HEADER_SAP_DESTINATION_SERVICE] = String(req.headers[interfaces_1.HEADER_SAP_DESTINATION_SERVICE]);
2564
+ if (req.headers[interfaces_1.HEADER_MCP_DESTINATION])
2565
+ providedHeaders[interfaces_1.HEADER_MCP_DESTINATION] = String(req.headers[interfaces_1.HEADER_MCP_DESTINATION]);
2566
+ if (req.headers[interfaces_1.HEADER_SAP_URL])
2567
+ providedHeaders[interfaces_1.HEADER_SAP_URL] = '[present]';
2568
+ if (req.headers[interfaces_1.HEADER_SAP_AUTH_TYPE])
2569
+ providedHeaders[interfaces_1.HEADER_SAP_AUTH_TYPE] = String(req.headers[interfaces_1.HEADER_SAP_AUTH_TYPE]);
2570
+ const errorMessage = `No valid SAP configuration available. Please provide authentication headers (${interfaces_1.HEADER_SAP_URL}, ${interfaces_1.HEADER_SAP_AUTH_TYPE}, etc.) or ensure destination is configured correctly.`;
2571
+ logger_1.logger?.error("No valid SAP configuration available for request", {
2572
+ type: "NO_VALID_SAP_CONFIG",
2573
+ sessionId: session.sessionId,
2574
+ method: methodName,
2575
+ hasSessionConfig: !!updatedSession?.sapConfig,
2576
+ hasBaseConfig: !!this.sapConfig,
2577
+ baseConfigUrl: this.sapConfig?.url,
2578
+ providedHeaders: Object.keys(providedHeaders).length > 0 ? providedHeaders : 'none',
2579
+ hasAuthBroker: this.transportConfig.type === "streamable-http",
2580
+ hint: "For destination-based auth, ensure service key file exists and destination name matches exactly (case-sensitive). For direct auth, provide x-sap-url and x-sap-auth-type headers.",
2581
+ });
2582
+ // Return error response to client
2583
+ if (!res.headersSent) {
2584
+ res.writeHead(400, { "Content-Type": "application/json" });
2585
+ res.end(JSON.stringify({
2586
+ jsonrpc: "2.0",
2587
+ id: null,
2588
+ error: {
2589
+ code: -32603,
2590
+ message: errorMessage,
2591
+ data: {
2592
+ type: "NO_VALID_SAP_CONFIG",
2593
+ hint: "Provide authentication headers or configure destination correctly",
2594
+ },
2595
+ },
2596
+ }));
2597
+ }
2598
+ return;
2599
+ }
2600
+ // Create connection for this request
2601
+ const connection = await this.getOrCreateConnectionForServer(req.headers, session.sessionId, updatedSession?.destination || this.defaultDestination);
2602
+ // Create new McpServer instance for this request
2603
+ const requestServer = new mcp_js_1.McpServer({
2604
+ name: "mcp-abap-adt",
2605
+ version: "0.1.0"
2606
+ });
2607
+ // Register all tools using McpHandlers
2608
+ const handlers = new mcp_handlers_1.McpHandlers();
2609
+ handlers.RegisterAllToolsOnServer(requestServer, { connection, logger: loggerAdapter_1.loggerAdapter });
2610
+ // KEY MOMENT: Create new StreamableHTTP transport for each request (like the SDK example)
2611
+ // SDK automatically handles:
2612
+ // - Chunked transfer encoding
2613
+ // - Session tracking
2614
+ // - JSON-RPC protocol
2615
+ const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
2616
+ sessionIdGenerator: undefined, // Stateless mode (like the SDK example)
2617
+ enableJsonResponse: httpConfig.enableJsonResponse,
2618
+ allowedOrigins: httpConfig.allowedOrigins,
2619
+ allowedHosts: httpConfig.allowedHosts,
2620
+ enableDnsRebindingProtection: httpConfig.enableDnsRebindingProtection,
2621
+ });
2622
+ // Close transport when response closes (like the SDK example)
2623
+ res.on("close", () => {
2624
+ transport.close();
2625
+ });
2626
+ // Connect transport to new McpServer (like the SDK example)
2627
+ await requestServer.connect(transport);
2628
+ logger_1.logger?.debug("Transport connected", {
2629
+ type: "STREAMABLE_HTTP_TRANSPORT_CONNECTED",
2630
+ sessionId: session.sessionId,
2631
+ clientID,
2632
+ });
2633
+ // Run handlers in AsyncLocalStorage context with session info
2634
+ // This allows getManagedConnection() to access sessionId, config, and destination
2635
+ const sessionDestination = updatedSession?.destination;
2636
+ await utils_1.sessionContext.run({
2637
+ sessionId: session.sessionId,
2638
+ sapConfig: sessionSapConfig,
2639
+ destination: sessionDestination,
2640
+ }, async () => {
2641
+ // Handle HTTP request through transport (like the SDK example)
2642
+ // Pass body as third parameter if available (like the SDK example)
2643
+ await transport.handleRequest(req, res, body);
2644
+ });
2645
+ logger_1.logger?.debug("Request completed", {
2646
+ type: "STREAMABLE_HTTP_REQUEST_COMPLETED",
2647
+ sessionId: session.sessionId,
2648
+ clientID,
2649
+ });
2650
+ }
2651
+ catch (error) {
2652
+ logger_1.logger?.error("Failed to handle HTTP request", {
2653
+ type: "HTTP_REQUEST_ERROR",
2654
+ error: error instanceof Error ? error.message : String(error),
2655
+ sessionId: session.sessionId,
2656
+ clientID,
2657
+ });
2658
+ if (!res.headersSent) {
2659
+ res.writeHead(500).end("Internal Server Error");
2660
+ }
2661
+ else {
2662
+ res.end();
2663
+ }
2664
+ }
2665
+ });
2666
+ httpServer.on("clientError", (err, socket) => {
2667
+ logger_1.logger?.error("HTTP client error", {
2668
+ type: "HTTP_CLIENT_ERROR",
2669
+ error: err instanceof Error ? err.message : String(err),
2670
+ });
2671
+ socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
2672
+ });
2673
+ await new Promise((resolve, reject) => {
2674
+ const onError = (error) => {
2675
+ logger_1.logger?.error("HTTP server failed to start", {
2676
+ type: "HTTP_SERVER_ERROR",
2677
+ error: error.message,
2678
+ });
2679
+ httpServer.off("error", onError);
2680
+ reject(error);
2681
+ };
2682
+ httpServer.once("error", onError);
2683
+ httpServer.listen(httpConfig.port, httpConfig.host, () => {
2684
+ httpServer.off("error", onError);
2685
+ logger_1.logger?.info("HTTP server listening", {
2686
+ type: "HTTP_SERVER_LISTENING",
2687
+ host: httpConfig.host,
2688
+ port: httpConfig.port,
2689
+ enableJsonResponse: httpConfig.enableJsonResponse,
2690
+ });
2691
+ resolve();
2692
+ });
2693
+ });
2694
+ this.httpServer = httpServer;
2695
+ return;
2696
+ }
2697
+ const sseConfig = this.transportConfig;
2698
+ // Initialize default broker if destination/.env exists (for SSE: optional, wait for headers if not)
2699
+ if (this.defaultMcpDestination || this.hasEnvFile) {
2700
+ await this.initializeDefaultBroker();
2701
+ // If default destination exists, connect to ABAP automatically
2702
+ if (this.defaultDestination) {
2703
+ try {
2704
+ const authBroker = await this.getOrCreateAuthBroker(this.defaultDestination, 'global');
2705
+ if (authBroker) {
2706
+ const connConfig = await authBroker.getConnectionConfig(this.defaultDestination);
2707
+ if (connConfig?.serviceUrl) {
2708
+ const jwtToken = await authBroker.getToken(this.defaultDestination);
2709
+ if (jwtToken) {
2710
+ // Register AuthBroker in global registry for connection to use during token refresh
2711
+ const { registerAuthBroker } = require('./lib/utils');
2712
+ registerAuthBroker(this.defaultDestination, authBroker);
2713
+ this.processJwtConfigUpdate(connConfig.serviceUrl, jwtToken, undefined, this.defaultDestination);
2714
+ logger_1.logger?.info("SAP configuration initialized for SSE transport", {
2715
+ type: "SSE_DESTINATION_INIT",
2716
+ destination: this.defaultDestination,
2717
+ url: connConfig.serviceUrl,
2718
+ });
2719
+ }
2720
+ }
2721
+ }
2722
+ }
2723
+ catch (error) {
2724
+ logger_1.logger?.warn("Failed to initialize connection with default destination for SSE transport", {
2725
+ type: "SSE_DESTINATION_INIT_FAILED",
2726
+ destination: this.defaultDestination,
2727
+ error: error instanceof Error ? error.message : String(error),
2728
+ });
2729
+ }
2730
+ }
2731
+ }
2732
+ else {
2733
+ logger_1.logger?.debug("No default destination for SSE transport, will wait for headers", {
2734
+ type: "SSE_NO_DEFAULT_DESTINATION",
2735
+ });
2736
+ }
2737
+ const streamPathMap = new Map([
2738
+ ["/", "/messages"],
2739
+ ["/mcp/events", "/mcp/messages"],
2740
+ ["/sse", "/messages"],
2741
+ ]);
2742
+ const streamPaths = Array.from(streamPathMap.keys());
2743
+ const postPathSet = new Set(streamPathMap.values());
2744
+ postPathSet.add("/messages");
2745
+ postPathSet.add("/mcp/messages");
2746
+ const httpServer = (0, http_1.createServer)(async (req, res) => {
2747
+ // SSE: Always restrict to local connections only
2748
+ const remoteAddress = req.socket.remoteAddress;
2749
+ if (!this.isLocalConnection(remoteAddress)) {
2750
+ logger_1.logger?.warn("SSE: Non-local connection rejected", {
2751
+ type: "SSE_NON_LOCAL_REJECTED",
2752
+ remoteAddress,
2753
+ });
2754
+ res.writeHead(403, { "Content-Type": "text/plain" });
2755
+ res.end("Forbidden: SSE transport only accepts local connections");
2756
+ return;
2757
+ }
2758
+ const requestUrl = req.url ? new URL(req.url, `http://${req.headers.host ?? `${sseConfig.host}:${sseConfig.port}`}`) : undefined;
2759
+ let pathname = requestUrl?.pathname ?? "/";
2760
+ if (pathname.length > 1 && pathname.endsWith("/")) {
2761
+ pathname = pathname.slice(0, -1);
2762
+ }
2763
+ await this.applyAuthHeaders(req.headers);
2764
+ logger_1.logger?.debug("SSE request received", {
2765
+ type: "SSE_HTTP_REQUEST",
2766
+ method: req.method,
2767
+ pathname,
2768
+ originalUrl: req.url,
2769
+ headers: {
2770
+ accept: req.headers.accept,
2771
+ "content-type": req.headers["content-type"],
2772
+ },
2773
+ });
2774
+ // GET /sse, /mcp/events, or / - establish SSE connection
2775
+ if (req.method === "GET" && streamPathMap.has(pathname)) {
2776
+ const postEndpoint = streamPathMap.get(pathname) ?? "/messages";
2777
+ logger_1.logger?.debug("SSE client connecting", {
2778
+ type: "SSE_CLIENT_CONNECTING",
2779
+ pathname,
2780
+ postEndpoint,
2781
+ });
2782
+ // Create connection for this session
2783
+ const connection = await this.getOrCreateConnectionForServer(req.headers, undefined, // sessionId will be set after transport creation
2784
+ this.defaultDestination);
2785
+ // Create new McpServer instance for this session (like the working example)
2786
+ const server = this.createMcpServerForSession({ connection, logger: loggerAdapter_1.loggerAdapter });
2787
+ // Create SSE transport
2788
+ const transport = new sse_js_1.SSEServerTransport(postEndpoint, res, {
2789
+ allowedHosts: sseConfig.allowedHosts,
2790
+ allowedOrigins: sseConfig.allowedOrigins,
2791
+ enableDnsRebindingProtection: sseConfig.enableDnsRebindingProtection,
2792
+ });
2793
+ const sessionId = transport.sessionId;
2794
+ logger_1.logger?.info("New SSE session created", {
2795
+ type: "SSE_SESSION_CREATED",
2796
+ sessionId,
2797
+ pathname,
2798
+ });
2799
+ // Store transport and server for this session
2800
+ this.sseSessions.set(sessionId, {
2801
+ server,
2802
+ transport,
2803
+ });
2804
+ // Connect transport to server (using server.server like in the example)
2805
+ try {
2806
+ await server.server.connect(transport);
2807
+ logger_1.logger?.info("SSE transport connected", {
2808
+ type: "SSE_CONNECTION_READY",
2809
+ sessionId,
2810
+ pathname,
2811
+ postEndpoint,
2812
+ });
2813
+ }
2814
+ catch (error) {
2815
+ logger_1.logger?.error("Failed to connect SSE transport", {
2816
+ type: "SSE_CONNECT_ERROR",
2817
+ error: error instanceof Error ? error.message : String(error),
2818
+ sessionId,
2819
+ });
2820
+ this.sseSessions.delete(sessionId);
2821
+ if (!res.headersSent) {
2822
+ res.writeHead(500).end("Internal Server Error");
2823
+ }
2824
+ else {
2825
+ res.end();
2826
+ }
2827
+ return;
2828
+ }
2829
+ // Cleanup on connection close
2830
+ res.on("close", () => {
2831
+ logger_1.logger?.info("SSE connection closed", {
2832
+ type: "SSE_CONNECTION_CLOSED",
2833
+ sessionId,
2834
+ pathname,
2835
+ });
2836
+ this.sseSessions.delete(sessionId);
2837
+ server.server.close();
2838
+ });
2839
+ transport.onerror = (error) => {
2840
+ logger_1.logger?.error("SSE transport error", {
2841
+ type: "SSE_TRANSPORT_ERROR",
2842
+ error: error instanceof Error ? error.message : String(error),
2843
+ sessionId,
2844
+ });
2845
+ };
2846
+ return;
2847
+ }
2848
+ // POST /messages or /mcp/messages - handle client messages
2849
+ if (req.method === "POST" && postPathSet.has(pathname)) {
2850
+ // Extract sessionId from query string or header
2851
+ let sessionId;
2852
+ if (requestUrl) {
2853
+ sessionId = requestUrl.searchParams.get("sessionId") || undefined;
2854
+ }
2855
+ if (!sessionId) {
2856
+ sessionId = req.headers["x-session-id"];
2857
+ }
2858
+ logger_1.logger?.debug("SSE POST request received", {
2859
+ type: "SSE_POST_REQUEST",
2860
+ sessionId,
2861
+ pathname,
2862
+ });
2863
+ if (!sessionId || !this.sseSessions.has(sessionId)) {
2864
+ logger_1.logger?.error("Invalid or missing SSE session", {
2865
+ type: "SSE_INVALID_SESSION",
2866
+ sessionId,
2867
+ });
2868
+ res.writeHead(400, { "Content-Type": "application/json" }).end(JSON.stringify({
2869
+ jsonrpc: "2.0",
2870
+ error: {
2871
+ code: -32000,
2872
+ message: "Invalid or missing sessionId",
2873
+ },
2874
+ id: null,
2875
+ }));
2876
+ return;
2877
+ }
2878
+ const session = this.sseSessions.get(sessionId);
2879
+ const { transport } = session;
2880
+ try {
2881
+ // Read request body
2882
+ let body = null;
2883
+ const chunks = [];
2884
+ for await (const chunk of req) {
2885
+ chunks.push(chunk);
2886
+ }
2887
+ if (chunks.length > 0) {
2888
+ const bodyString = Buffer.concat(chunks).toString('utf-8');
2889
+ try {
2890
+ body = JSON.parse(bodyString);
2891
+ }
2892
+ catch (parseError) {
2893
+ body = bodyString || null;
2894
+ }
2895
+ }
2896
+ // Handle POST message through transport (like the working example)
2897
+ await transport.handlePostMessage(req, res, body);
2898
+ logger_1.logger?.debug("SSE POST request processed", {
2899
+ type: "SSE_POST_PROCESSED",
2900
+ sessionId,
2901
+ });
2902
+ }
2903
+ catch (error) {
2904
+ logger_1.logger?.error("Failed to handle SSE POST message", {
2905
+ type: "SSE_POST_ERROR",
2906
+ error: error instanceof Error ? error.message : String(error),
2907
+ sessionId,
2908
+ });
2909
+ if (!res.headersSent) {
2910
+ res.writeHead(500).end("Internal Server Error");
2911
+ }
2912
+ else {
2913
+ res.end();
2914
+ }
2915
+ }
2916
+ return;
2917
+ }
2918
+ // OPTIONS - CORS preflight
2919
+ if (req.method === "OPTIONS" && (streamPathMap.has(pathname) || postPathSet.has(pathname))) {
2920
+ res.writeHead(204, {
2921
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
2922
+ "Access-Control-Allow-Headers": "Content-Type",
2923
+ }).end();
2924
+ return;
2925
+ }
2926
+ res.writeHead(404, { "Content-Type": "application/json" }).end(JSON.stringify({ error: "Not Found" }));
2927
+ });
2928
+ httpServer.on("clientError", (err, socket) => {
2929
+ logger_1.logger?.error("SSE HTTP client error", {
2930
+ type: "SSE_HTTP_CLIENT_ERROR",
2931
+ error: err instanceof Error ? err.message : String(err),
2932
+ });
2933
+ socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
2934
+ });
2935
+ await new Promise((resolve, reject) => {
2936
+ const onError = (error) => {
2937
+ logger_1.logger?.error("SSE HTTP server failed to start", {
2938
+ type: "SSE_HTTP_SERVER_ERROR",
2939
+ error: error.message,
2940
+ });
2941
+ httpServer.off("error", onError);
2942
+ reject(error);
2943
+ };
2944
+ httpServer.once("error", onError);
2945
+ httpServer.listen(sseConfig.port, sseConfig.host, () => {
2946
+ httpServer.off("error", onError);
2947
+ logger_1.logger?.info("SSE HTTP server listening", {
2948
+ type: "SSE_HTTP_SERVER_LISTENING",
2949
+ host: sseConfig.host,
2950
+ port: sseConfig.port,
2951
+ streamPaths,
2952
+ postPaths: Array.from(postPathSet.values()),
2953
+ });
2954
+ resolve();
2955
+ });
2956
+ });
2957
+ this.httpServer = httpServer;
2958
+ }
2959
+ }
2960
+ exports.mcp_abap_adt_server = mcp_abap_adt_server;
2961
+ if (process.env.MCP_SKIP_AUTO_START !== "true") {
2962
+ const server = new mcp_abap_adt_server();
2963
+ server.run().catch((error) => {
2964
+ logger_1.logger?.error("Fatal error while running MCP server", {
2965
+ type: "SERVER_FATAL_ERROR",
2966
+ error: error instanceof Error ? error.message : String(error),
2967
+ });
2968
+ // Always write to stderr (safe even in stdio mode)
2969
+ process.stderr.write(`[MCP] ✗ Fatal error: ${error instanceof Error ? error.message : String(error)}\n`);
2970
+ // On Windows, add a small delay before exit to allow error message to be visible
2971
+ if (process.platform === 'win32') {
2972
+ setTimeout(() => process.exit(1), 100);
2973
+ }
2974
+ else {
2975
+ process.exit(1);
2976
+ }
2977
+ });
2978
+ }
2979
+ //# sourceMappingURL=index.js.map