@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.
- package/CHANGELOG.md +18 -0
- package/dist/bin/help.d.ts +6 -0
- package/dist/bin/help.d.ts.map +1 -0
- package/dist/bin/help.js +59 -0
- package/dist/bin/mcp-abap-adt-v2.d.ts +9 -0
- package/dist/bin/mcp-abap-adt-v2.d.ts.map +1 -0
- package/dist/bin/mcp-abap-adt-v2.js +247 -0
- package/dist/handlers/behavior_definition/readonly/handleGetBdef.d.ts +21 -0
- package/dist/handlers/behavior_definition/readonly/handleGetBdef.d.ts.map +1 -0
- package/dist/handlers/behavior_definition/readonly/handleGetBdef.js +24 -0
- package/dist/handlers/behavior_definition/readonly/handleGetBdef.js.map +1 -0
- package/dist/handlers/class/high/handleCreateLocalTestClass.d.ts.map +1 -1
- package/dist/handlers/class/high/handleCreateLocalTestClass.js +6 -1
- package/dist/handlers/class/high/handleCreateLocalTestClass.js.map +1 -1
- package/dist/handlers/class/high/handleGetUnitTest.d.ts +38 -0
- package/dist/handlers/class/high/handleGetUnitTest.d.ts.map +1 -0
- package/dist/handlers/class/high/handleGetUnitTest.js +73 -0
- package/dist/handlers/class/high/handleGetUnitTest.js.map +1 -0
- package/dist/handlers/class/high/handleRunUnitTest.d.ts +122 -0
- package/dist/handlers/class/high/handleRunUnitTest.d.ts.map +1 -0
- package/dist/handlers/class/high/handleRunUnitTest.js +133 -0
- package/dist/handlers/class/high/handleRunUnitTest.js.map +1 -0
- package/dist/handlers/class/readonly/handleGetClass.d.ts +23 -0
- package/dist/handlers/class/readonly/handleGetClass.d.ts.map +1 -0
- package/dist/handlers/class/readonly/handleGetClass.js +77 -0
- package/dist/handlers/class/readonly/handleGetClass.js.map +1 -0
- package/dist/handlers/data_element/readonly/handleGetDataElement.d.ts +23 -0
- package/dist/handlers/data_element/readonly/handleGetDataElement.d.ts.map +1 -0
- package/dist/handlers/data_element/readonly/handleGetDataElement.js +79 -0
- package/dist/handlers/data_element/readonly/handleGetDataElement.js.map +1 -0
- package/dist/handlers/domain/readonly/handleGetDomain.d.ts +23 -0
- package/dist/handlers/domain/readonly/handleGetDomain.d.ts.map +1 -0
- package/dist/handlers/domain/readonly/handleGetDomain.js +79 -0
- package/dist/handlers/domain/readonly/handleGetDomain.js.map +1 -0
- package/dist/handlers/enhancement/readonly/handleGetEnhancements.d.ts.map +1 -1
- package/dist/handlers/enhancement/readonly/handleGetEnhancements.js +7 -7
- package/dist/handlers/enhancement/readonly/handleGetEnhancements.js.map +1 -1
- package/dist/handlers/function/readonly/handleGetFunctionGroup.d.ts +23 -0
- package/dist/handlers/function/readonly/handleGetFunctionGroup.d.ts.map +1 -0
- package/dist/handlers/function/readonly/handleGetFunctionGroup.js +77 -0
- package/dist/handlers/function/readonly/handleGetFunctionGroup.js.map +1 -0
- package/dist/handlers/include/readonly/handleGetIncludesList.d.ts.map +1 -1
- package/dist/handlers/include/readonly/handleGetIncludesList.js +8 -5
- package/dist/handlers/include/readonly/handleGetIncludesList.js.map +1 -1
- package/dist/handlers/interface/readonly/handleGetInterface.d.ts +23 -0
- package/dist/handlers/interface/readonly/handleGetInterface.d.ts.map +1 -0
- package/dist/handlers/interface/readonly/handleGetInterface.js +77 -0
- package/dist/handlers/interface/readonly/handleGetInterface.js.map +1 -0
- package/dist/handlers/package/readonly/handleGetPackage.d.ts +22 -0
- package/dist/handlers/package/readonly/handleGetPackage.d.ts.map +1 -0
- package/dist/handlers/package/readonly/handleGetPackage.js +108 -0
- package/dist/handlers/package/readonly/handleGetPackage.js.map +1 -0
- package/dist/handlers/program/readonly/handleGetProgram.d.ts +23 -0
- package/dist/handlers/program/readonly/handleGetProgram.d.ts.map +1 -0
- package/dist/handlers/program/readonly/handleGetProgram.js +77 -0
- package/dist/handlers/program/readonly/handleGetProgram.js.map +1 -0
- package/dist/handlers/search/readonly/handleGetObjectsByType.d.ts.map +1 -1
- package/dist/handlers/search/readonly/handleGetObjectsByType.js +5 -1
- package/dist/handlers/search/readonly/handleGetObjectsByType.js.map +1 -1
- package/dist/handlers/search/readonly/handleGetObjectsList.d.ts.map +1 -1
- package/dist/handlers/search/readonly/handleGetObjectsList.js +8 -4
- package/dist/handlers/search/readonly/handleGetObjectsList.js.map +1 -1
- package/dist/handlers/service_definition/readonly/handleGetServiceDefinition.d.ts +18 -0
- package/dist/handlers/service_definition/readonly/handleGetServiceDefinition.d.ts.map +1 -0
- package/dist/handlers/service_definition/readonly/handleGetServiceDefinition.js +109 -0
- package/dist/handlers/service_definition/readonly/handleGetServiceDefinition.js.map +1 -0
- package/dist/handlers/structure/readonly/handleGetStructure.d.ts +23 -0
- package/dist/handlers/structure/readonly/handleGetStructure.d.ts.map +1 -0
- package/dist/handlers/structure/readonly/handleGetStructure.js +77 -0
- package/dist/handlers/structure/readonly/handleGetStructure.js.map +1 -0
- package/dist/handlers/system/readonly/handleGetObjectInfo.d.ts.map +1 -1
- package/dist/handlers/system/readonly/handleGetObjectInfo.js +8 -14
- package/dist/handlers/system/readonly/handleGetObjectInfo.js.map +1 -1
- package/dist/handlers/table/readonly/handleGetTable.d.ts +23 -0
- package/dist/handlers/table/readonly/handleGetTable.d.ts.map +1 -0
- package/dist/handlers/table/readonly/handleGetTable.js +77 -0
- package/dist/handlers/table/readonly/handleGetTable.js.map +1 -0
- package/dist/handlers/view/readonly/handleGetView.d.ts +33 -0
- package/dist/handlers/view/readonly/handleGetView.d.ts.map +1 -0
- package/dist/handlers/view/readonly/handleGetView.js +90 -0
- package/dist/handlers/view/readonly/handleGetView.js.map +1 -0
- package/dist/index.d.ts +126 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2979 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/ServerConfigManager.d.ts +90 -0
- package/dist/lib/ServerConfigManager.d.ts.map +1 -0
- package/dist/lib/ServerConfigManager.js +237 -0
- package/dist/lib/ServerConfigManager.js.map +1 -0
- package/dist/lib/activationUtils.d.ts +59 -0
- package/dist/lib/activationUtils.d.ts.map +1 -0
- package/dist/lib/activationUtils.js +168 -0
- package/dist/lib/authBrokerFactory.d.ts +96 -0
- package/dist/lib/authBrokerFactory.d.ts.map +1 -0
- package/dist/lib/authBrokerFactory.js +531 -0
- package/dist/lib/authBrokerFactory.js.map +1 -0
- package/dist/lib/config/ServerConfig.d.ts +17 -0
- package/dist/lib/config/ServerConfig.d.ts.map +1 -0
- package/dist/lib/config/ServerConfig.js +7 -0
- package/dist/lib/config/ServerConfig.js.map +1 -0
- package/dist/lib/getFullCodeCache.d.ts +3 -0
- package/dist/lib/getFullCodeCache.d.ts.map +1 -0
- package/dist/lib/getFullCodeCache.js +56 -0
- package/dist/lib/handlers/handlers/base/BaseHandlerGroup.d.ts +36 -0
- package/dist/lib/handlers/handlers/base/BaseHandlerGroup.d.ts.map +1 -0
- package/dist/lib/handlers/handlers/base/BaseHandlerGroup.js +268 -0
- package/dist/lib/handlers/handlers/base/BaseHandlerGroup.js.map +1 -0
- package/dist/lib/handlers/handlers/examples/usage-example.d.ts +29 -0
- package/dist/lib/handlers/handlers/examples/usage-example.d.ts.map +1 -0
- package/dist/lib/handlers/handlers/examples/usage-example.js +119 -0
- package/dist/lib/handlers/handlers/examples/usage-example.js.map +1 -0
- package/dist/lib/handlers/handlers/groups/HighLevelHandlersGroup.d.ts +14 -0
- package/dist/lib/handlers/handlers/groups/HighLevelHandlersGroup.d.ts.map +1 -0
- package/dist/lib/handlers/handlers/groups/HighLevelHandlersGroup.js +322 -0
- package/dist/lib/handlers/handlers/groups/HighLevelHandlersGroup.js.map +1 -0
- package/dist/lib/handlers/handlers/groups/LowLevelHandlersGroup.d.ts +14 -0
- package/dist/lib/handlers/handlers/groups/LowLevelHandlersGroup.d.ts.map +1 -0
- package/dist/lib/handlers/handlers/groups/LowLevelHandlersGroup.js +1214 -0
- package/dist/lib/handlers/handlers/groups/LowLevelHandlersGroup.js.map +1 -0
- package/dist/lib/handlers/handlers/groups/ReadOnlyHandlersGroup.d.ts +14 -0
- package/dist/lib/handlers/handlers/groups/ReadOnlyHandlersGroup.d.ts.map +1 -0
- package/dist/lib/handlers/handlers/groups/ReadOnlyHandlersGroup.js +232 -0
- package/dist/lib/handlers/handlers/groups/ReadOnlyHandlersGroup.js.map +1 -0
- package/dist/lib/handlers/handlers/groups/SearchHandlersGroup.d.ts +14 -0
- package/dist/lib/handlers/handlers/groups/SearchHandlersGroup.d.ts.map +1 -0
- package/dist/lib/handlers/handlers/groups/SearchHandlersGroup.js +57 -0
- package/dist/lib/handlers/handlers/groups/SearchHandlersGroup.js.map +1 -0
- package/dist/lib/handlers/handlers/groups/SystemHandlersGroup.d.ts +14 -0
- package/dist/lib/handlers/handlers/groups/SystemHandlersGroup.d.ts.map +1 -0
- package/dist/lib/handlers/handlers/groups/SystemHandlersGroup.js +176 -0
- package/dist/lib/handlers/handlers/groups/SystemHandlersGroup.js.map +1 -0
- package/dist/lib/handlers/handlers/groups/index.d.ts +13 -0
- package/dist/lib/handlers/handlers/groups/index.d.ts.map +1 -0
- package/dist/lib/handlers/handlers/groups/index.js +21 -0
- package/dist/lib/handlers/handlers/groups/index.js.map +1 -0
- package/dist/lib/handlers/handlers/index.d.ts +14 -0
- package/dist/lib/handlers/handlers/index.d.ts.map +1 -0
- package/dist/lib/handlers/handlers/index.js +37 -0
- package/dist/lib/handlers/handlers/index.js.map +1 -0
- package/dist/lib/handlers/handlers/interfaces.d.ts +67 -0
- package/dist/lib/handlers/handlers/interfaces.d.ts.map +1 -0
- package/dist/lib/handlers/handlers/interfaces.js +3 -0
- package/dist/lib/handlers/handlers/interfaces.js.map +1 -0
- package/dist/lib/handlers/handlers/registry/CompositeHandlersRegistry.d.ts +49 -0
- package/dist/lib/handlers/handlers/registry/CompositeHandlersRegistry.d.ts.map +1 -0
- package/dist/lib/handlers/handlers/registry/CompositeHandlersRegistry.js +88 -0
- package/dist/lib/handlers/handlers/registry/CompositeHandlersRegistry.js.map +1 -0
- package/dist/lib/runtimeConfig.d.ts +20 -0
- package/dist/lib/runtimeConfig.d.ts.map +1 -0
- package/dist/lib/runtimeConfig.js +130 -0
- package/dist/lib/runtimeConfig.js.map +1 -0
- package/dist/lib/servers/BaseMcpServer.d.ts +56 -0
- package/dist/lib/servers/BaseMcpServer.d.ts.map +1 -0
- package/dist/lib/servers/BaseMcpServer.js +198 -0
- package/dist/lib/servers/BaseMcpServer.js.map +1 -0
- package/dist/lib/servers/ConnectionContext.d.ts +20 -0
- package/dist/lib/servers/ConnectionContext.d.ts.map +1 -0
- package/dist/lib/servers/ConnectionContext.js +3 -0
- package/dist/lib/servers/ConnectionContext.js.map +1 -0
- package/dist/lib/servers/SseServer.d.ts +32 -0
- package/dist/lib/servers/SseServer.d.ts.map +1 -0
- package/dist/lib/servers/SseServer.js +139 -0
- package/dist/lib/servers/SseServer.js.map +1 -0
- package/dist/lib/servers/StdioServer.d.ts +20 -0
- package/dist/lib/servers/StdioServer.d.ts.map +1 -0
- package/dist/lib/servers/StdioServer.js +32 -0
- package/dist/lib/servers/StdioServer.js.map +1 -0
- package/dist/lib/servers/StreamableHttpServer.d.ts +30 -0
- package/dist/lib/servers/StreamableHttpServer.d.ts.map +1 -0
- package/dist/lib/servers/StreamableHttpServer.js +83 -0
- package/dist/lib/servers/StreamableHttpServer.js.map +1 -0
- package/dist/lib/servers/handlers/base/BaseHandlerGroup.d.ts +36 -0
- package/dist/lib/servers/handlers/base/BaseHandlerGroup.d.ts.map +1 -0
- package/dist/lib/servers/handlers/base/BaseHandlerGroup.js +268 -0
- package/dist/lib/servers/handlers/base/BaseHandlerGroup.js.map +1 -0
- package/dist/lib/servers/handlers/examples/usage-example.d.ts +29 -0
- package/dist/lib/servers/handlers/examples/usage-example.d.ts.map +1 -0
- package/dist/lib/servers/handlers/examples/usage-example.js +119 -0
- package/dist/lib/servers/handlers/examples/usage-example.js.map +1 -0
- package/dist/lib/servers/handlers/groups/HighLevelHandlersGroup.d.ts +14 -0
- package/dist/lib/servers/handlers/groups/HighLevelHandlersGroup.d.ts.map +1 -0
- package/dist/lib/servers/handlers/groups/HighLevelHandlersGroup.js +322 -0
- package/dist/lib/servers/handlers/groups/HighLevelHandlersGroup.js.map +1 -0
- package/dist/lib/servers/handlers/groups/LowLevelHandlersGroup.d.ts +14 -0
- package/dist/lib/servers/handlers/groups/LowLevelHandlersGroup.d.ts.map +1 -0
- package/dist/lib/servers/handlers/groups/LowLevelHandlersGroup.js +1214 -0
- package/dist/lib/servers/handlers/groups/LowLevelHandlersGroup.js.map +1 -0
- package/dist/lib/servers/handlers/groups/ReadOnlyHandlersGroup.d.ts +14 -0
- package/dist/lib/servers/handlers/groups/ReadOnlyHandlersGroup.d.ts.map +1 -0
- package/dist/lib/servers/handlers/groups/ReadOnlyHandlersGroup.js +232 -0
- package/dist/lib/servers/handlers/groups/ReadOnlyHandlersGroup.js.map +1 -0
- package/dist/lib/servers/handlers/groups/SearchHandlersGroup.d.ts +14 -0
- package/dist/lib/servers/handlers/groups/SearchHandlersGroup.d.ts.map +1 -0
- package/dist/lib/servers/handlers/groups/SearchHandlersGroup.js +57 -0
- package/dist/lib/servers/handlers/groups/SearchHandlersGroup.js.map +1 -0
- package/dist/lib/servers/handlers/groups/SystemHandlersGroup.d.ts +14 -0
- package/dist/lib/servers/handlers/groups/SystemHandlersGroup.d.ts.map +1 -0
- package/dist/lib/servers/handlers/groups/SystemHandlersGroup.js +176 -0
- package/dist/lib/servers/handlers/groups/SystemHandlersGroup.js.map +1 -0
- package/dist/lib/servers/handlers/groups/index.d.ts +13 -0
- package/dist/lib/servers/handlers/groups/index.d.ts.map +1 -0
- package/dist/lib/servers/handlers/groups/index.js +21 -0
- package/dist/lib/servers/handlers/groups/index.js.map +1 -0
- package/dist/lib/servers/handlers/index.d.ts +14 -0
- package/dist/lib/servers/handlers/index.d.ts.map +1 -0
- package/dist/lib/servers/handlers/index.js +37 -0
- package/dist/lib/servers/handlers/index.js.map +1 -0
- package/dist/lib/servers/handlers/interfaces.d.ts +67 -0
- package/dist/lib/servers/handlers/interfaces.d.ts.map +1 -0
- package/dist/lib/servers/handlers/interfaces.js +3 -0
- package/dist/lib/servers/handlers/interfaces.js.map +1 -0
- package/dist/lib/servers/handlers/registry/CompositeHandlersRegistry.d.ts +49 -0
- package/dist/lib/servers/handlers/registry/CompositeHandlersRegistry.d.ts.map +1 -0
- package/dist/lib/servers/handlers/registry/CompositeHandlersRegistry.js +88 -0
- package/dist/lib/servers/handlers/registry/CompositeHandlersRegistry.js.map +1 -0
- package/dist/lib/servers/index.d.ts +5 -0
- package/dist/lib/servers/index.d.ts.map +1 -0
- package/dist/lib/servers/index.js +21 -0
- package/dist/lib/servers/index.js.map +1 -0
- package/dist/lib/servers/launcher.d.ts +2 -0
- package/dist/lib/servers/launcher.d.ts.map +1 -0
- package/dist/lib/servers/launcher.js +123 -0
- package/dist/lib/servers/launcher.js.map +1 -0
- package/dist/lib/servers/mcp_handlers.d.ts +25 -0
- package/dist/lib/servers/mcp_handlers.d.ts.map +1 -0
- package/dist/lib/servers/mcp_handlers.js +822 -0
- package/dist/lib/servers/mcp_handlers.js.map +1 -0
- package/dist/lib/servers/mcp_server.d.ts +7 -0
- package/dist/lib/servers/mcp_server.d.ts.map +1 -0
- package/dist/lib/servers/mcp_server.js +12 -0
- package/dist/lib/servers/utils.d.ts +42 -0
- package/dist/lib/servers/utils.d.ts.map +1 -0
- package/dist/lib/servers/utils.js +620 -0
- package/dist/lib/servers/utils.js.map +1 -0
- package/dist/lib/servers/v2/__tests__/unit/McpServer.test.d.ts +7 -0
- package/dist/lib/servers/v2/__tests__/unit/McpServer.test.d.ts.map +1 -0
- package/dist/lib/servers/v2/__tests__/unit/McpServer.test.js +218 -0
- package/dist/lib/servers/v2/connection/LocalConnectionProvider.d.ts +20 -0
- package/dist/lib/servers/v2/connection/LocalConnectionProvider.d.ts.map +1 -0
- package/dist/lib/servers/v2/connection/LocalConnectionProvider.js +70 -0
- package/dist/lib/servers/v2/connection/RemoteConnectionProvider.d.ts +21 -0
- package/dist/lib/servers/v2/connection/RemoteConnectionProvider.d.ts.map +1 -0
- package/dist/lib/servers/v2/connection/RemoteConnectionProvider.js +36 -0
- package/dist/lib/servers/v2/connection/index.d.ts +6 -0
- package/dist/lib/servers/v2/connection/index.d.ts.map +1 -0
- package/dist/lib/servers/v2/connection/index.js +10 -0
- package/dist/lib/servers/v2/factory/AuthBrokerFactory.d.ts +29 -0
- package/dist/lib/servers/v2/factory/AuthBrokerFactory.d.ts.map +1 -0
- package/dist/lib/servers/v2/factory/AuthBrokerFactory.js +79 -0
- package/dist/lib/servers/v2/factory/LocalModeFactory.d.ts +38 -0
- package/dist/lib/servers/v2/factory/LocalModeFactory.d.ts.map +1 -0
- package/dist/lib/servers/v2/factory/LocalModeFactory.js +38 -0
- package/dist/lib/servers/v2/factory/index.d.ts +6 -0
- package/dist/lib/servers/v2/factory/index.d.ts.map +1 -0
- package/dist/lib/servers/v2/factory/index.js +10 -0
- package/dist/lib/servers/v2/index.d.ts +9 -0
- package/dist/lib/servers/v2/index.d.ts.map +1 -0
- package/dist/lib/servers/v2/index.js +27 -0
- package/dist/lib/servers/v2/index.js.map +1 -0
- package/dist/lib/servers/v2/interfaces/connection.d.ts +188 -0
- package/dist/lib/servers/v2/interfaces/connection.d.ts.map +1 -0
- package/dist/lib/servers/v2/interfaces/connection.js +7 -0
- package/dist/lib/servers/v2/interfaces/index.d.ts +10 -0
- package/dist/lib/servers/v2/interfaces/index.d.ts.map +1 -0
- package/dist/lib/servers/v2/interfaces/index.js +25 -0
- package/dist/lib/servers/v2/interfaces/protocol.d.ts +30 -0
- package/dist/lib/servers/v2/interfaces/protocol.d.ts.map +1 -0
- package/dist/lib/servers/v2/interfaces/protocol.js +7 -0
- package/dist/lib/servers/v2/interfaces/session.d.ts +98 -0
- package/dist/lib/servers/v2/interfaces/session.d.ts.map +1 -0
- package/dist/lib/servers/v2/interfaces/session.js +7 -0
- package/dist/lib/servers/v2/interfaces/transport.d.ts +88 -0
- package/dist/lib/servers/v2/interfaces/transport.d.ts.map +1 -0
- package/dist/lib/servers/v2/interfaces/transport.js +7 -0
- package/dist/lib/servers/v2/protocol/ProtocolHandler.d.ts +21 -0
- package/dist/lib/servers/v2/protocol/ProtocolHandler.d.ts.map +1 -0
- package/dist/lib/servers/v2/protocol/ProtocolHandler.js +77 -0
- package/dist/lib/servers/v2/protocol/index.d.ts +5 -0
- package/dist/lib/servers/v2/protocol/index.d.ts.map +1 -0
- package/dist/lib/servers/v2/protocol/index.js +8 -0
- package/dist/lib/servers/v2/server/McpServer.d.ts +76 -0
- package/dist/lib/servers/v2/server/McpServer.d.ts.map +1 -0
- package/dist/lib/servers/v2/server/McpServer.js +243 -0
- package/dist/lib/servers/v2/server/index.d.ts +5 -0
- package/dist/lib/servers/v2/server/index.d.ts.map +1 -0
- package/dist/lib/servers/v2/server/index.js +8 -0
- package/dist/lib/servers/v2/session/SessionManager.d.ts +40 -0
- package/dist/lib/servers/v2/session/SessionManager.d.ts.map +1 -0
- package/dist/lib/servers/v2/session/SessionManager.js +136 -0
- package/dist/lib/servers/v2/session/index.d.ts +5 -0
- package/dist/lib/servers/v2/session/index.d.ts.map +1 -0
- package/dist/lib/servers/v2/session/index.js +8 -0
- package/dist/lib/servers/v2/transports/SseTransport.d.ts +49 -0
- package/dist/lib/servers/v2/transports/SseTransport.d.ts.map +1 -0
- package/dist/lib/servers/v2/transports/SseTransport.js +89 -0
- package/dist/lib/servers/v2/transports/StdioTransport.d.ts +31 -0
- package/dist/lib/servers/v2/transports/StdioTransport.d.ts.map +1 -0
- package/dist/lib/servers/v2/transports/StdioTransport.js +91 -0
- package/dist/lib/servers/v2/transports/StreamableHttpTransport.d.ts +51 -0
- package/dist/lib/servers/v2/transports/StreamableHttpTransport.d.ts.map +1 -0
- package/dist/lib/servers/v2/transports/StreamableHttpTransport.js +147 -0
- package/dist/lib/servers/v2/transports/index.d.ts +7 -0
- package/dist/lib/servers/v2/transports/index.d.ts.map +1 -0
- package/dist/lib/servers/v2/transports/index.js +12 -0
- package/dist/lib/servers/v2/types/common.d.ts +17 -0
- package/dist/lib/servers/v2/types/common.d.ts.map +1 -0
- package/dist/lib/servers/v2/types/common.js +6 -0
- package/dist/lib/servers/v2/types/common.js.map +1 -0
- package/dist/lib/servers/v2/types/index.d.ts +8 -0
- package/dist/lib/servers/v2/types/index.d.ts.map +1 -0
- package/dist/lib/servers/v2/types/index.js +24 -0
- package/dist/lib/servers/v2/types/index.js.map +1 -0
- package/dist/lib/servers/v2/types/transport.d.ts +28 -0
- package/dist/lib/servers/v2/types/transport.d.ts.map +1 -0
- package/dist/lib/servers/v2/types/transport.js +8 -0
- package/dist/lib/servers/v2/types/transport.js.map +1 -0
- package/dist/lib/servers/v2/utils/StdioLogger.d.ts +17 -0
- package/dist/lib/servers/v2/utils/StdioLogger.d.ts.map +1 -0
- package/dist/lib/servers/v2/utils/StdioLogger.js +33 -0
- package/dist/lib/servers/v2/utils/StdioLogger.js.map +1 -0
- package/dist/lib/stores/UnixFileServiceKeyStore.d.ts +42 -0
- package/dist/lib/stores/UnixFileServiceKeyStore.d.ts.map +1 -0
- package/dist/lib/stores/UnixFileServiceKeyStore.js +53 -0
- package/dist/lib/stores/UnixFileSessionStore.d.ts +54 -0
- package/dist/lib/stores/UnixFileSessionStore.d.ts.map +1 -0
- package/dist/lib/stores/UnixFileSessionStore.js +70 -0
- package/dist/lib/stores/WindowsFileServiceKeyStore.d.ts +42 -0
- package/dist/lib/stores/WindowsFileServiceKeyStore.d.ts.map +1 -0
- package/dist/lib/stores/WindowsFileServiceKeyStore.js +53 -0
- package/dist/lib/stores/WindowsFileSessionStore.d.ts +54 -0
- package/dist/lib/stores/WindowsFileSessionStore.d.ts.map +1 -0
- package/dist/lib/stores/WindowsFileSessionStore.js +70 -0
- package/dist/lib/toolsRegistry.d.ts +13 -0
- package/dist/lib/toolsRegistry.d.ts.map +1 -0
- package/dist/lib/toolsRegistry.js +410 -0
- package/dist/lib/transportConfig.d.ts +27 -0
- package/dist/lib/transportConfig.d.ts.map +1 -0
- package/dist/lib/transportConfig.js +124 -0
- package/dist/lib/transports/stdio.d.ts +10 -0
- package/dist/lib/transports/stdio.d.ts.map +1 -0
- package/dist/lib/transports/stdio.js +73 -0
- package/dist/lib/utils.d.ts +37 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +83 -0
- package/dist/lib/utils.js.map +1 -1
- package/dist/lib/yamlConfig.d.ts +59 -0
- package/dist/lib/yamlConfig.d.ts.map +1 -0
- package/dist/lib/yamlConfig.js +349 -0
- package/dist/lib/yamlConfig.js.map +1 -0
- package/dist/server/launcher.js +49 -0
- package/dist/server/launcher.js.map +1 -1
- package/dist/server/v1/AuthBrokerConfig.d.ts +19 -0
- package/dist/server/v1/AuthBrokerConfig.d.ts.map +1 -0
- package/dist/server/v1/AuthBrokerConfig.js +44 -0
- package/dist/server/v1/AuthBrokerConfig.js.map +1 -0
- package/dist/server/v1/IServerConfig.d.ts +17 -0
- package/dist/server/v1/IServerConfig.d.ts.map +1 -0
- package/dist/server/v1/IServerConfig.js +7 -0
- package/dist/server/v1/IServerConfig.js.map +1 -0
- package/dist/server/v1/ServerConfig.d.ts +16 -0
- package/dist/server/v1/ServerConfig.d.ts.map +1 -0
- package/dist/server/v1/ServerConfig.js +6 -0
- package/dist/server/v1/ServerConfig.js.map +1 -0
- package/dist/server/v1/embeddable-server.d.ts +89 -0
- package/dist/server/v1/embeddable-server.d.ts.map +1 -0
- package/dist/server/v1/embeddable-server.js +83 -0
- package/dist/server/v1/embeddable-server.js.map +1 -0
- package/dist/server/v1/index.d.ts +30 -0
- package/dist/server/v1/index.d.ts.map +1 -0
- package/dist/server/v1/index.js +65 -0
- package/dist/server/v1/index.js.map +1 -0
- package/dist/server/v1/legacy-server.d.ts +141 -0
- package/dist/server/v1/legacy-server.d.ts.map +1 -0
- package/dist/server/v1/legacy-server.js +2099 -0
- package/dist/server/v1/legacy-server.js.map +1 -0
- package/dist/server/v1/mcp_handlers.d.ts +19 -0
- package/dist/server/v1/mcp_handlers.d.ts.map +1 -0
- package/dist/server/v1/mcp_handlers.js +85 -0
- package/dist/server/v1/mcp_handlers.js.map +1 -0
- package/dist/server/v2/AuthBrokerConfig.d.ts +22 -0
- package/dist/server/v2/AuthBrokerConfig.d.ts.map +1 -0
- package/dist/server/v2/AuthBrokerConfig.js +46 -0
- package/dist/server/v2/AuthBrokerConfig.js.map +1 -0
- package/dist/server/v2/BaseMcpServer.d.ts +67 -0
- package/dist/server/v2/BaseMcpServer.d.ts.map +1 -0
- package/dist/server/v2/BaseMcpServer.js +273 -0
- package/dist/server/v2/BaseMcpServer.js.map +1 -0
- package/dist/server/v2/ConnectionContext.d.ts +20 -0
- package/dist/server/v2/ConnectionContext.d.ts.map +1 -0
- package/dist/server/v2/ConnectionContext.js +3 -0
- package/dist/server/v2/ConnectionContext.js.map +1 -0
- package/dist/server/v2/IHttpApplication.d.ts +42 -0
- package/dist/server/v2/IHttpApplication.d.ts.map +1 -0
- package/dist/server/v2/IHttpApplication.js +3 -0
- package/dist/server/v2/IHttpApplication.js.map +1 -0
- package/dist/server/v2/IServerConfig.d.ts +15 -0
- package/dist/server/v2/IServerConfig.d.ts.map +1 -0
- package/dist/server/v2/IServerConfig.js +7 -0
- package/dist/server/v2/IServerConfig.js.map +1 -0
- package/dist/server/v2/ServerConfig.d.ts +16 -0
- package/dist/server/v2/ServerConfig.d.ts.map +1 -0
- package/dist/server/v2/ServerConfig.js +6 -0
- package/dist/server/v2/ServerConfig.js.map +1 -0
- package/dist/server/v2/SseServer.d.ts +95 -0
- package/dist/server/v2/SseServer.d.ts.map +1 -0
- package/dist/server/v2/SseServer.js +217 -0
- package/dist/server/v2/SseServer.js.map +1 -0
- package/dist/server/v2/StdioServer.d.ts +20 -0
- package/dist/server/v2/StdioServer.d.ts.map +1 -0
- package/dist/server/v2/StdioServer.js +32 -0
- package/dist/server/v2/StdioServer.js.map +1 -0
- package/dist/server/v2/StreamableHttpServer.d.ts +94 -0
- package/dist/server/v2/StreamableHttpServer.d.ts.map +1 -0
- package/dist/server/v2/StreamableHttpServer.js +170 -0
- package/dist/server/v2/StreamableHttpServer.js.map +1 -0
- package/dist/server/v2/index.d.ts +6 -0
- package/dist/server/v2/index.d.ts.map +1 -0
- package/dist/server/v2/index.js +22 -0
- package/dist/server/v2/index.js.map +1 -0
- package/dist/server/v2/launcher.d.ts +2 -0
- package/dist/server/v2/launcher.d.ts.map +1 -0
- package/dist/server/v2/launcher.js +181 -0
- package/dist/server/v2/launcher.js.map +1 -0
- package/dist/server/v2/mcp_handlers.d.ts +25 -0
- package/dist/server/v2/mcp_handlers.d.ts.map +1 -0
- package/dist/server/v2/mcp_handlers.js +828 -0
- package/dist/server/v2/mcp_handlers.js.map +1 -0
- package/dist/server/v2/utils.d.ts +42 -0
- package/dist/server/v2/utils.d.ts.map +1 -0
- package/dist/server/v2/utils.js +620 -0
- package/dist/server/v2/utils.js.map +1 -0
- package/dist/tools/test-v2-server-stdio-compiled.js +132 -0
- package/dist/utils/lockStateManager.d.ts +2 -12
- package/dist/utils/lockStateManager.d.ts.map +1 -1
- package/dist/utils/lockStateManager.js +4 -63
- package/dist/utils/lockStateManager.js.map +1 -1
- package/docs/user-guide/AVAILABLE_TOOLS.md +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,2099 @@
|
|
|
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.createDefaultHandlerExporter = exports.HandlerExporter = exports.getConfig = exports.setSapConfigOverride = void 0;
|
|
46
|
+
exports.setAbapConnectionOverride = setAbapConnectionOverride;
|
|
47
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
48
|
+
const mcp_handlers_1 = require("./mcp_handlers");
|
|
49
|
+
const path_1 = __importDefault(require("path"));
|
|
50
|
+
const os = __importStar(require("os"));
|
|
51
|
+
const crypto_1 = require("crypto");
|
|
52
|
+
const header_validator_1 = require("@mcp-abap-adt/header-validator");
|
|
53
|
+
const interfaces_1 = require("@mcp-abap-adt/interfaces");
|
|
54
|
+
const index_1 = require("../../lib/auth/index");
|
|
55
|
+
const AuthBrokerConfig_js_1 = require("./AuthBrokerConfig.js");
|
|
56
|
+
const platformPaths_1 = require("../../lib/stores/platformPaths");
|
|
57
|
+
const runtimeConfig_1 = require("../../lib/config/runtimeConfig");
|
|
58
|
+
const yamlConfig_1 = require("../../lib/config/yamlConfig");
|
|
59
|
+
const index_2 = require("../../lib/config/index");
|
|
60
|
+
// Import handler functions
|
|
61
|
+
// Import handler functions
|
|
62
|
+
// New low-level handlers imports
|
|
63
|
+
// Import shared utility functions and types
|
|
64
|
+
const utils_1 = require("../../lib/utils");
|
|
65
|
+
const config_js_1 = require("../../lib/config.js");
|
|
66
|
+
Object.defineProperty(exports, "getConfig", { enumerable: true, get: function () { return config_js_1.getConfig; } });
|
|
67
|
+
Object.defineProperty(exports, "setSapConfigOverride", { enumerable: true, get: function () { return config_js_1.setSapConfigOverride; } });
|
|
68
|
+
const connection_1 = require("@mcp-abap-adt/connection");
|
|
69
|
+
const loggerAdapter_1 = require("../../lib/loggerAdapter");
|
|
70
|
+
// Import logger
|
|
71
|
+
const logger_1 = require("../../lib/logger");
|
|
72
|
+
// import { defaultLogger as logger } from "@mcp-abap-adt/logger";
|
|
73
|
+
// Import tool registry
|
|
74
|
+
// Import TOOL_DEFINITION from handlers
|
|
75
|
+
// New low-level handlers TOOL_DEFINITION imports
|
|
76
|
+
// --- ENV FILE LOADING LOGIC ---
|
|
77
|
+
const fs_1 = __importDefault(require("fs"));
|
|
78
|
+
/**
|
|
79
|
+
* Additional help sections specific to v1 server
|
|
80
|
+
*/
|
|
81
|
+
const V1_HELP_SECTIONS = `
|
|
82
|
+
ENVIRONMENT VARIABLES:
|
|
83
|
+
DEBUG_AUTH_LOG Enable debug logging for auth-broker (true|false)
|
|
84
|
+
Default: false (only info messages shown)
|
|
85
|
+
DEBUG_AUTH_BROKER Alias for DEBUG_AUTH_LOG
|
|
86
|
+
DEBUG_HTTP_REQUESTS Enable logging of HTTP requests (true|false)
|
|
87
|
+
DEBUG_CONNECTORS Enable debug logging for connection layer (true|false)
|
|
88
|
+
DEBUG_HANDLERS Enable debug logging for MCP handlers (true|false)
|
|
89
|
+
AUTH_BROKER_PATH Custom paths for service keys and sessions
|
|
90
|
+
Unix: colon-separated (e.g., /path1:/path2)
|
|
91
|
+
Windows: semicolon-separated (e.g., C:\\\\path1;C:\\\\path2)
|
|
92
|
+
|
|
93
|
+
SAP CONNECTION (.env file):
|
|
94
|
+
SAP_URL SAP system URL (required)
|
|
95
|
+
SAP_CLIENT SAP client number (required)
|
|
96
|
+
SAP_AUTH_TYPE Authentication type: basic|jwt (default: basic)
|
|
97
|
+
SAP_USERNAME SAP username (required for basic auth)
|
|
98
|
+
SAP_PASSWORD SAP password (required for basic auth)
|
|
99
|
+
SAP_JWT_TOKEN JWT token (required for jwt auth)
|
|
100
|
+
|
|
101
|
+
GENERATING .ENV FROM SERVICE KEY:
|
|
102
|
+
Install connection package: npm install -g @mcp-abap-adt/connection
|
|
103
|
+
Generate .env: sap-abap-auth auth -k path/to/service-key.json
|
|
104
|
+
|
|
105
|
+
SERVICE KEYS (Destination-Based Authentication):
|
|
106
|
+
Store service keys in platform-specific locations:
|
|
107
|
+
Linux/macOS: ~/.config/mcp-abap-adt/service-keys/{destination}.json
|
|
108
|
+
Windows: %USERPROFILE%\\\\Documents\\\\mcp-abap-adt\\\\service-keys\\\\{destination}.json
|
|
109
|
+
|
|
110
|
+
Using Destinations:
|
|
111
|
+
In HTTP headers: x-mcp-destination: TRIAL
|
|
112
|
+
Or via CLI: mcp-abap-adt --mcp=TRIAL
|
|
113
|
+
|
|
114
|
+
CLINE CONFIGURATION EXAMPLES (~/.cline/mcp.json):
|
|
115
|
+
|
|
116
|
+
1. Stdio with .env file:
|
|
117
|
+
{
|
|
118
|
+
"mcpServers": {
|
|
119
|
+
"mcp-abap-adt": {
|
|
120
|
+
"type": "stdio",
|
|
121
|
+
"command": "mcp-abap-adt",
|
|
122
|
+
"args": ["--env=/path/to/.env"]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
2. Stdio with MCP destination (service key):
|
|
128
|
+
{
|
|
129
|
+
"mcpServers": {
|
|
130
|
+
"mcp-abap-adt": {
|
|
131
|
+
"type": "stdio",
|
|
132
|
+
"command": "mcp-abap-adt",
|
|
133
|
+
"args": ["--mcp=TRIAL"]
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
3. SSE transport:
|
|
139
|
+
{
|
|
140
|
+
"mcpServers": {
|
|
141
|
+
"mcp-abap-adt": {
|
|
142
|
+
"type": "sse",
|
|
143
|
+
"url": "http://localhost:3001/sse"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
DOCUMENTATION:
|
|
149
|
+
https://github.com/fr0ster/mcp-abap-adt
|
|
150
|
+
Installation: docs/installation/INSTALLATION.md
|
|
151
|
+
Configuration: docs/user-guide/CLIENT_CONFIGURATION.md
|
|
152
|
+
Available Tools: docs/user-guide/AVAILABLE_TOOLS.md
|
|
153
|
+
`;
|
|
154
|
+
/**
|
|
155
|
+
* Display help message using unified ServerConfigManager
|
|
156
|
+
*/
|
|
157
|
+
function showHelp() {
|
|
158
|
+
console.log(index_2.ServerConfigManager.generateHelp(V1_HELP_SECTIONS));
|
|
159
|
+
process.exit(0);
|
|
160
|
+
}
|
|
161
|
+
// Check for version/help flags
|
|
162
|
+
if (process.argv.includes("--version") || process.argv.includes("-v")) {
|
|
163
|
+
const packageJsonPath = path_1.default.join(__dirname, "..", "..", "..", "package.json");
|
|
164
|
+
const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, "utf8"));
|
|
165
|
+
console.log(packageJson.version);
|
|
166
|
+
process.exit(0);
|
|
167
|
+
}
|
|
168
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
169
|
+
showHelp();
|
|
170
|
+
}
|
|
171
|
+
// Load YAML config if --config parameter is provided
|
|
172
|
+
// YAML config values are applied to process.argv (command-line args override YAML)
|
|
173
|
+
const configPath = (0, yamlConfig_1.parseConfigArg)();
|
|
174
|
+
if (configPath) {
|
|
175
|
+
try {
|
|
176
|
+
// Generate template if file doesn't exist
|
|
177
|
+
const templateGenerated = (0, yamlConfig_1.generateConfigTemplateIfNeeded)(configPath);
|
|
178
|
+
// If template was just generated, exit successfully
|
|
179
|
+
// User needs to edit the file before running the server
|
|
180
|
+
if (templateGenerated) {
|
|
181
|
+
if (process.platform === 'win32') {
|
|
182
|
+
setTimeout(() => process.exit(0), 100);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
process.exit(0);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Load and apply YAML config (only if file exists)
|
|
189
|
+
const yamlConfig = (0, yamlConfig_1.loadYamlConfig)(configPath);
|
|
190
|
+
if (yamlConfig) {
|
|
191
|
+
(0, yamlConfig_1.applyYamlConfigToArgs)(yamlConfig);
|
|
192
|
+
// Only write to stderr if not in stdio mode (stdio mode requires clean JSON only)
|
|
193
|
+
const isStdioMode = process.env.MCP_TRANSPORT === "stdio" || !process.stdin.isTTY;
|
|
194
|
+
if (!isStdioMode) {
|
|
195
|
+
process.stderr.write(`[MCP-CONFIG] Loaded and validated configuration from: ${configPath}\n`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
const errorMsg = `Failed to load YAML config: ${error instanceof Error ? error.message : String(error)}`;
|
|
201
|
+
process.stderr.write(`[MCP-CONFIG] ✗ ERROR: ${errorMsg}\n`);
|
|
202
|
+
if (process.platform === 'win32') {
|
|
203
|
+
setTimeout(() => process.exit(1), 100);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Runtime config (shared, no side-effects)
|
|
211
|
+
const { useAuthBroker, isTestEnv, authBrokerPath, defaultMcpDestination, browser, unsafe, explicitTransportType, transportType, isHttp, isSse, isStdio, isEnvMandatory, envFilePath: initialEnvFilePath, } = (0, runtimeConfig_1.buildRuntimeConfig)();
|
|
212
|
+
// Skip .env autoload under explicit instructions, auth-broker, mcp default, or test env
|
|
213
|
+
const skipEnvAutoload = process.env.MCP_SKIP_ENV_LOAD === "true" ||
|
|
214
|
+
process.env.MCP_ENV_LOADED_BY_LAUNCHER === "true" ||
|
|
215
|
+
useAuthBroker ||
|
|
216
|
+
!!defaultMcpDestination ||
|
|
217
|
+
isTestEnv;
|
|
218
|
+
// If using auth-broker (--mcp or --auth-broker), clear SAP_* env vars to prevent .env from being used
|
|
219
|
+
// This ensures that even if .env was loaded earlier or vars are set in system, they won't be used
|
|
220
|
+
if (skipEnvAutoload && (useAuthBroker || !!defaultMcpDestination)) {
|
|
221
|
+
const sapEnvKeys = Object.keys(process.env).filter(key => key.startsWith('SAP_'));
|
|
222
|
+
for (const key of sapEnvKeys) {
|
|
223
|
+
delete process.env[key];
|
|
224
|
+
}
|
|
225
|
+
if (sapEnvKeys.length > 0 && !isStdio) {
|
|
226
|
+
process.stderr.write(`[MCP-ENV] Cleared ${sapEnvKeys.length} SAP_* environment variables (using auth-broker)\n`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
let envFilePath = initialEnvFilePath;
|
|
230
|
+
// Debug: Always log on Windows to help diagnose issues
|
|
231
|
+
if (process.platform === 'win32' && !isStdio) {
|
|
232
|
+
process.stderr.write(`[MCP-ENV] parseEnvArg() returned: ${envFilePath || '(undefined)'}\n`);
|
|
233
|
+
process.stderr.write(`[MCP-ENV] MCP_ENV_PATH: ${process.env.MCP_ENV_PATH || '(not set)'}\n`);
|
|
234
|
+
process.stderr.write(`[MCP-ENV] Final envFilePath: ${envFilePath || '(will search for .env)'}\n`);
|
|
235
|
+
}
|
|
236
|
+
if (!skipEnvAutoload) {
|
|
237
|
+
if (!envFilePath) {
|
|
238
|
+
// Default behavior: search in current working directory (where user runs the command)
|
|
239
|
+
// If .env exists, use it; otherwise will use auth-broker
|
|
240
|
+
const cwdEnvPath = path_1.default.resolve(process.cwd(), ".env");
|
|
241
|
+
if (fs_1.default.existsSync(cwdEnvPath)) {
|
|
242
|
+
envFilePath = cwdEnvPath;
|
|
243
|
+
// Only write to stderr if not in stdio mode (stdio mode requires clean JSON only)
|
|
244
|
+
if (!isStdio) {
|
|
245
|
+
process.stderr.write(`[MCP-ENV] Found .env file: ${envFilePath}\n`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
// Only write to stderr if not in stdio mode
|
|
251
|
+
if (!isStdio) {
|
|
252
|
+
process.stderr.write(`[MCP-ENV] Using .env from argument/env: ${envFilePath}\n`);
|
|
253
|
+
// On Windows, also log the resolved path for debugging
|
|
254
|
+
if (process.platform === 'win32') {
|
|
255
|
+
const resolvedPath = path_1.default.isAbsolute(envFilePath)
|
|
256
|
+
? envFilePath
|
|
257
|
+
: path_1.default.resolve(process.cwd(), envFilePath);
|
|
258
|
+
process.stderr.write(`[MCP-ENV] Will resolve to: ${resolvedPath}\n`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (envFilePath) {
|
|
263
|
+
// Normalize path separators for Windows compatibility
|
|
264
|
+
// On Windows, backslashes in paths need special handling
|
|
265
|
+
// path.resolve() and path.normalize() should handle this, but let's be explicit
|
|
266
|
+
// First, normalize all backslashes to forward slashes for consistent processing
|
|
267
|
+
// Then use path methods which handle platform-specific separators correctly
|
|
268
|
+
const normalizedPath = envFilePath.replace(/\\/g, '/');
|
|
269
|
+
if (!path_1.default.isAbsolute(normalizedPath)) {
|
|
270
|
+
// For relative paths, use path.resolve which handles .\ and ./ correctly on all platforms
|
|
271
|
+
// path.resolve automatically handles both .\mdd.env and ./mdd.env formats
|
|
272
|
+
envFilePath = path_1.default.resolve(process.cwd(), normalizedPath);
|
|
273
|
+
// Only write to stderr if not in stdio mode
|
|
274
|
+
if (!isStdio) {
|
|
275
|
+
process.stderr.write(`[MCP-ENV] Resolved relative path to: ${envFilePath}\n`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
// For absolute paths, normalize using path.normalize
|
|
280
|
+
envFilePath = path_1.default.normalize(envFilePath);
|
|
281
|
+
}
|
|
282
|
+
// Verify file exists before attempting to load
|
|
283
|
+
if (!fs_1.default.existsSync(envFilePath)) {
|
|
284
|
+
const errorMsg = `[MCP-ENV] ✗ ERROR: .env file not found at: ${envFilePath}\n` +
|
|
285
|
+
`[MCP-ENV] Current working directory: ${process.cwd()}\n` +
|
|
286
|
+
`[MCP-ENV] Please check the path and try again.\n`;
|
|
287
|
+
process.stderr.write(errorMsg);
|
|
288
|
+
if (process.platform === 'win32') {
|
|
289
|
+
setTimeout(() => process.exit(1), 100);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (fs_1.default.existsSync(envFilePath)) {
|
|
296
|
+
// For stdio mode, load .env manually to avoid any output from dotenv library
|
|
297
|
+
if (isStdio) {
|
|
298
|
+
// Manual .env parsing for stdio mode (no library output)
|
|
299
|
+
try {
|
|
300
|
+
const envContent = fs_1.default.readFileSync(envFilePath, "utf8");
|
|
301
|
+
const lines = envContent.split(/\r?\n/);
|
|
302
|
+
for (const line of lines) {
|
|
303
|
+
const trimmed = line.trim();
|
|
304
|
+
// Skip empty lines and comments
|
|
305
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
// Parse KEY=VALUE format
|
|
309
|
+
const eqIndex = trimmed.indexOf("=");
|
|
310
|
+
if (eqIndex === -1) {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
const key = trimmed.substring(0, eqIndex).trim();
|
|
314
|
+
let value = trimmed.substring(eqIndex + 1);
|
|
315
|
+
// Remove inline comments (everything after #)
|
|
316
|
+
// This handles cases like: KEY=value # comment or KEY=value#comment
|
|
317
|
+
// Find first # and remove everything after it (including the #)
|
|
318
|
+
const commentIndex = value.indexOf('#');
|
|
319
|
+
if (commentIndex !== -1) {
|
|
320
|
+
// Remove everything from # onwards, then trim trailing whitespace
|
|
321
|
+
const beforeComment = value.substring(0, commentIndex);
|
|
322
|
+
value = beforeComment.trim();
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
value = value.trim();
|
|
326
|
+
}
|
|
327
|
+
// Parse value: remove quotes and trim
|
|
328
|
+
let unquotedValue = value.trim();
|
|
329
|
+
unquotedValue = unquotedValue.replace(/^["']+|["']+$/g, '').trim();
|
|
330
|
+
// URLs from .env files are expected to be clean - just use as-is
|
|
331
|
+
if (key === 'SAP_URL') {
|
|
332
|
+
// No special processing needed
|
|
333
|
+
// Debug logging for Windows
|
|
334
|
+
if (process.platform === 'win32' && !isStdio) {
|
|
335
|
+
process.stderr.write(`[MCP-ENV] Parsed SAP_URL: "${unquotedValue}" (length: ${unquotedValue.length})\n`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// Only set if not already in process.env (don't override launcher's cleaned values)
|
|
339
|
+
if (key && !process.env[key]) {
|
|
340
|
+
process.env[key] = unquotedValue;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
// Silent fail for stdio mode - just exit
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
// For non-stdio modes, use manual parsing (dotenv removed to avoid stdout pollution)
|
|
351
|
+
try {
|
|
352
|
+
const envContent = fs_1.default.readFileSync(envFilePath, "utf8");
|
|
353
|
+
const lines = envContent.split(/\r?\n/);
|
|
354
|
+
for (const line of lines) {
|
|
355
|
+
const trimmed = line.trim();
|
|
356
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
const eqIndex = trimmed.indexOf("=");
|
|
360
|
+
if (eqIndex === -1) {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
const key = trimmed.substring(0, eqIndex).trim();
|
|
364
|
+
let value = trimmed.substring(eqIndex + 1);
|
|
365
|
+
// Remove inline comments (everything after #)
|
|
366
|
+
// This handles cases like: KEY=value # comment or KEY=value#comment
|
|
367
|
+
// Find first # and remove everything after it (including the #)
|
|
368
|
+
const commentIndex = value.indexOf('#');
|
|
369
|
+
if (commentIndex !== -1) {
|
|
370
|
+
// Remove everything from # onwards, then trim trailing whitespace
|
|
371
|
+
const beforeComment = value.substring(0, commentIndex);
|
|
372
|
+
value = beforeComment.trim();
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
value = value.trim();
|
|
376
|
+
}
|
|
377
|
+
// Parse value: remove quotes and trim
|
|
378
|
+
let unquotedValue = value.trim();
|
|
379
|
+
unquotedValue = unquotedValue.replace(/^["']+|["']+$/g, '').trim();
|
|
380
|
+
// URLs from .env files are expected to be clean - just use as-is
|
|
381
|
+
if (key === 'SAP_URL') {
|
|
382
|
+
// No special processing needed
|
|
383
|
+
// Debug logging for Windows
|
|
384
|
+
if (process.platform === 'win32' && !isStdio) {
|
|
385
|
+
process.stderr.write(`[MCP-ENV] Parsed SAP_URL: "${unquotedValue}" (length: ${unquotedValue.length})\n`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Only set if not already in process.env (don't override launcher's cleaned values)
|
|
389
|
+
if (key && !process.env[key]) {
|
|
390
|
+
process.env[key] = unquotedValue;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
process.stderr.write(`[MCP-ENV] ✓ Successfully loaded: ${envFilePath}\n`);
|
|
394
|
+
// Debug: log SAP_URL if loaded (for troubleshooting on Windows)
|
|
395
|
+
if (process.env.SAP_URL) {
|
|
396
|
+
const urlHex = Buffer.from(process.env.SAP_URL, 'utf8').toString('hex');
|
|
397
|
+
process.stderr.write(`[MCP-ENV] SAP_URL loaded: "${process.env.SAP_URL}" (length: ${process.env.SAP_URL.length})\n`);
|
|
398
|
+
if (process.platform === 'win32') {
|
|
399
|
+
process.stderr.write(`[MCP-ENV] SAP_URL (hex): ${urlHex.substring(0, 60)}...\n`);
|
|
400
|
+
// Check for comments
|
|
401
|
+
if (process.env.SAP_URL.includes('#')) {
|
|
402
|
+
process.stderr.write(`[MCP-ENV] ⚠ WARNING: SAP_URL contains # character (comment not removed?)\n`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
process.stderr.write(`[MCP-ENV] ⚠ WARNING: SAP_URL not found in .env file\n`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
process.stderr.write(`[MCP-ENV] ✗ Failed to load: ${envFilePath}\n`);
|
|
412
|
+
if (error instanceof Error) {
|
|
413
|
+
process.stderr.write(`[MCP-ENV] Error: ${error.message}\n`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
// .env file specified but not found
|
|
420
|
+
if (isEnvMandatory) {
|
|
421
|
+
// Always write error to stderr (stderr is safe even in stdio mode, unlike stdout)
|
|
422
|
+
logger_1.logger?.error(".env file not found", { path: envFilePath });
|
|
423
|
+
process.stderr.write(`[MCP-ENV] ✗ ERROR: .env file not found at: ${envFilePath}\n`);
|
|
424
|
+
process.stderr.write(`[MCP-ENV] Current working directory: ${process.cwd()}\n`);
|
|
425
|
+
process.stderr.write(`[MCP-ENV] Transport mode '${transportType}' requires .env file.\n`);
|
|
426
|
+
process.stderr.write(`[MCP-ENV] Use --env=/path/to/.env to specify custom location\n`);
|
|
427
|
+
// On Windows, add a small delay before exit to allow error message to be visible
|
|
428
|
+
if (process.platform === 'win32') {
|
|
429
|
+
setTimeout(() => process.exit(1), 100);
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
process.exit(1);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
// Always write error to stderr (stderr is safe even in stdio mode)
|
|
437
|
+
process.stderr.write(`[MCP-ENV] ✗ ERROR: .env file not found at: ${envFilePath}\n`);
|
|
438
|
+
process.stderr.write(`[MCP-ENV] Transport mode '${transportType}' was explicitly specified but .env file is missing.\n`);
|
|
439
|
+
process.stderr.write(`[MCP-ENV] Use --env=/path/to/.env to specify custom location\n`);
|
|
440
|
+
// On Windows, add a small delay before exit to allow error message to be visible
|
|
441
|
+
if (process.platform === 'win32') {
|
|
442
|
+
setTimeout(() => process.exit(1), 100);
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
process.exit(1);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
// No .env file found and none specified
|
|
452
|
+
if (isEnvMandatory) {
|
|
453
|
+
// Transport explicitly set to stdio/sse but no .env found
|
|
454
|
+
const cwdEnvPath = path_1.default.resolve(process.cwd(), ".env");
|
|
455
|
+
// Always write error to stderr (stderr is safe even in stdio mode)
|
|
456
|
+
logger_1.logger?.error(".env file not found", { path: cwdEnvPath });
|
|
457
|
+
process.stderr.write(`[MCP-ENV] ✗ ERROR: .env file not found in current directory: ${process.cwd()}\n`);
|
|
458
|
+
process.stderr.write(`[MCP-ENV] Transport mode '${transportType}' requires .env file.\n`);
|
|
459
|
+
process.stderr.write(`[MCP-ENV] Use --env=/path/to/.env to specify custom location\n`);
|
|
460
|
+
// On Windows, add a small delay before exit to allow error message to be visible
|
|
461
|
+
if (process.platform === 'win32') {
|
|
462
|
+
setTimeout(() => process.exit(1), 100);
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
process.exit(1);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
// No .env found, but transport is stdio (default) - this is OK for HTTP/SSE, but stdio requires .env or --mcp
|
|
470
|
+
if (explicitTransportType === null) {
|
|
471
|
+
// Transport not specified, using default (stdio)
|
|
472
|
+
// For stdio mode, don't write to stderr
|
|
473
|
+
if (!isStdio) {
|
|
474
|
+
process.stderr.write(`[MCP-ENV] NOTE: No .env file found in current directory: ${process.cwd()}\n`);
|
|
475
|
+
process.stderr.write(`[MCP-ENV] Starting in HTTP mode (no .env file required)\n`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
// Transport explicitly set to HTTP - this is OK
|
|
480
|
+
// For stdio mode, don't write to stderr
|
|
481
|
+
if (!isStdio) {
|
|
482
|
+
process.stderr.write(`[MCP-ENV] NOTE: No .env file found, continuing in ${transportType} mode\n`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
else if (envFilePath) {
|
|
489
|
+
if (!path_1.default.isAbsolute(envFilePath)) {
|
|
490
|
+
envFilePath = path_1.default.resolve(process.cwd(), envFilePath);
|
|
491
|
+
}
|
|
492
|
+
// For stdio mode, don't write to stderr
|
|
493
|
+
if (!isStdio) {
|
|
494
|
+
process.stderr.write(`[MCP-ENV] Environment autoload skipped; using provided path reference: ${envFilePath}\n`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
// For stdio mode, don't write to stderr
|
|
499
|
+
if (!isStdio) {
|
|
500
|
+
process.stderr.write(`[MCP-ENV] Environment autoload skipped (MCP_SKIP_ENV_LOAD=true).\n`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
// --- END ENV FILE LOADING LOGIC ---
|
|
504
|
+
// Debug: Log loaded SAP_URL and SAP_CLIENT using the MCP-compatible logger
|
|
505
|
+
// Skip logging in stdio mode (MCP protocol requires clean JSON only)
|
|
506
|
+
if (!isStdio) {
|
|
507
|
+
const envLogPath = envFilePath ?? "(skipped)";
|
|
508
|
+
logger_1.logger?.info("SAP configuration loaded", {
|
|
509
|
+
type: "CONFIG_INFO",
|
|
510
|
+
SAP_URL: process.env.SAP_URL,
|
|
511
|
+
SAP_CLIENT: process.env.SAP_CLIENT || "(not set)",
|
|
512
|
+
SAP_AUTH_TYPE: process.env.SAP_AUTH_TYPE || "(not set)",
|
|
513
|
+
SAP_JWT_TOKEN: process.env.SAP_JWT_TOKEN ? "[set]" : "(not set)",
|
|
514
|
+
ENV_PATH: envLogPath,
|
|
515
|
+
CWD: process.cwd(),
|
|
516
|
+
DIRNAME: __dirname,
|
|
517
|
+
TRANSPORT: transportType
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
function getArgValue(name) {
|
|
521
|
+
const args = process.argv;
|
|
522
|
+
for (let i = 0; i < args.length; i++) {
|
|
523
|
+
const arg = args[i];
|
|
524
|
+
if (arg.startsWith(`${name}=`)) {
|
|
525
|
+
return arg.slice(name.length + 1);
|
|
526
|
+
}
|
|
527
|
+
if (arg === name && i + 1 < args.length) {
|
|
528
|
+
return args[i + 1];
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return undefined;
|
|
532
|
+
}
|
|
533
|
+
function hasFlag(name) {
|
|
534
|
+
return process.argv.includes(name);
|
|
535
|
+
}
|
|
536
|
+
function parseBoolean(value) {
|
|
537
|
+
if (!value) {
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
const normalized = value.trim().toLowerCase();
|
|
541
|
+
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
|
|
542
|
+
}
|
|
543
|
+
function resolvePortOption(argName, envName, defaultValue) {
|
|
544
|
+
const rawValue = getArgValue(argName) ?? process.env[envName];
|
|
545
|
+
if (!rawValue) {
|
|
546
|
+
return defaultValue;
|
|
547
|
+
}
|
|
548
|
+
const port = Number.parseInt(rawValue, 10);
|
|
549
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
550
|
+
throw new Error(`Invalid port value for ${argName}: ${rawValue}`);
|
|
551
|
+
}
|
|
552
|
+
return port;
|
|
553
|
+
}
|
|
554
|
+
function resolveBooleanOption(argName, envName, defaultValue) {
|
|
555
|
+
const argValue = getArgValue(argName);
|
|
556
|
+
if (argValue !== undefined) {
|
|
557
|
+
return parseBoolean(argValue);
|
|
558
|
+
}
|
|
559
|
+
if (hasFlag(argName)) {
|
|
560
|
+
return true;
|
|
561
|
+
}
|
|
562
|
+
const envValue = process.env[envName];
|
|
563
|
+
if (envValue !== undefined) {
|
|
564
|
+
return parseBoolean(envValue);
|
|
565
|
+
}
|
|
566
|
+
return defaultValue;
|
|
567
|
+
}
|
|
568
|
+
function resolveListOption(argName, envName) {
|
|
569
|
+
const rawValue = getArgValue(argName) ?? process.env[envName];
|
|
570
|
+
if (!rawValue) {
|
|
571
|
+
return undefined;
|
|
572
|
+
}
|
|
573
|
+
const items = rawValue
|
|
574
|
+
.split(",")
|
|
575
|
+
.map((entry) => entry.trim())
|
|
576
|
+
.filter((entry) => entry.length > 0);
|
|
577
|
+
return items.length > 0 ? items : undefined;
|
|
578
|
+
}
|
|
579
|
+
function parseTransportConfig() {
|
|
580
|
+
// Use the transport type we already determined (handles explicit args, env vars, and defaults)
|
|
581
|
+
const normalized = transportType;
|
|
582
|
+
if (normalized &&
|
|
583
|
+
normalized !== "stdio" &&
|
|
584
|
+
normalized !== "http" &&
|
|
585
|
+
normalized !== "streamable-http" &&
|
|
586
|
+
normalized !== "server" &&
|
|
587
|
+
normalized !== "sse") {
|
|
588
|
+
throw new Error(`Unsupported transport: ${normalized}`);
|
|
589
|
+
}
|
|
590
|
+
const sseRequested = normalized === "sse" ||
|
|
591
|
+
hasFlag("--sse");
|
|
592
|
+
if (sseRequested) {
|
|
593
|
+
const port = resolvePortOption("--sse-port", "MCP_SSE_PORT", 3001);
|
|
594
|
+
// Default to localhost (127.0.0.1) for security - only accepts local connections
|
|
595
|
+
// Use 0.0.0.0 to accept connections from all interfaces (less secure)
|
|
596
|
+
const host = getArgValue("--sse-host") ?? process.env.MCP_SSE_HOST ?? "127.0.0.1";
|
|
597
|
+
const allowedOrigins = resolveListOption("--sse-allowed-origins", "MCP_SSE_ALLOWED_ORIGINS");
|
|
598
|
+
const allowedHosts = resolveListOption("--sse-allowed-hosts", "MCP_SSE_ALLOWED_HOSTS");
|
|
599
|
+
const enableDnsRebindingProtection = resolveBooleanOption("--sse-enable-dns-protection", "MCP_SSE_ENABLE_DNS_PROTECTION", false);
|
|
600
|
+
return {
|
|
601
|
+
type: "sse",
|
|
602
|
+
host,
|
|
603
|
+
port,
|
|
604
|
+
allowedOrigins,
|
|
605
|
+
allowedHosts,
|
|
606
|
+
enableDnsRebindingProtection,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
const httpRequested = normalized === "http" ||
|
|
610
|
+
normalized === "streamable-http" ||
|
|
611
|
+
normalized === "server" ||
|
|
612
|
+
hasFlag("--http") ||
|
|
613
|
+
// Note: Default is stdio (set in runtimeConfig), so this only applies if explicitly requested
|
|
614
|
+
(!sseRequested && normalized !== "stdio");
|
|
615
|
+
if (httpRequested) {
|
|
616
|
+
const port = resolvePortOption("--http-port", "MCP_HTTP_PORT", 3000);
|
|
617
|
+
// Default to localhost (127.0.0.1) for security - only accepts local connections
|
|
618
|
+
// Use 0.0.0.0 to accept connections from all interfaces (less secure)
|
|
619
|
+
const host = getArgValue("--http-host") ?? process.env.MCP_HTTP_HOST ?? "127.0.0.1";
|
|
620
|
+
const enableJsonResponse = resolveBooleanOption("--http-json-response", "MCP_HTTP_ENABLE_JSON_RESPONSE", false);
|
|
621
|
+
const allowedOrigins = resolveListOption("--http-allowed-origins", "MCP_HTTP_ALLOWED_ORIGINS");
|
|
622
|
+
const allowedHosts = resolveListOption("--http-allowed-hosts", "MCP_HTTP_ALLOWED_HOSTS");
|
|
623
|
+
const enableDnsRebindingProtection = resolveBooleanOption("--http-enable-dns-protection", "MCP_HTTP_ENABLE_DNS_PROTECTION", false);
|
|
624
|
+
return {
|
|
625
|
+
type: "streamable-http",
|
|
626
|
+
host,
|
|
627
|
+
port,
|
|
628
|
+
enableJsonResponse,
|
|
629
|
+
allowedOrigins,
|
|
630
|
+
allowedHosts,
|
|
631
|
+
enableDnsRebindingProtection,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
return { type: "stdio" };
|
|
635
|
+
}
|
|
636
|
+
function setAbapConnectionOverride(connection) {
|
|
637
|
+
(0, utils_1.setConnectionOverride)(connection);
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Retrieves SAP configuration from environment variables.
|
|
641
|
+
* Reads configuration from process.env (caller is responsible for loading .env file if needed).
|
|
642
|
+
*
|
|
643
|
+
* @returns {SapConfig} The SAP configuration object.
|
|
644
|
+
* @throws {Error} If any required environment variable is missing.
|
|
645
|
+
*/
|
|
646
|
+
// Helper function for Windows-compatible logging
|
|
647
|
+
// Only logs when DEBUG_CONNECTORS, DEBUG_TESTS, or DEBUG_ADT_TESTS is enabled
|
|
648
|
+
function debugLog(message) {
|
|
649
|
+
const debugEnabled = process.env.DEBUG_CONNECTORS === 'true' ||
|
|
650
|
+
process.env.DEBUG_TESTS === 'true' ||
|
|
651
|
+
process.env.DEBUG_ADT_TESTS === 'true';
|
|
652
|
+
if (!debugEnabled) {
|
|
653
|
+
return; // Suppress debug logs when not in debug mode
|
|
654
|
+
}
|
|
655
|
+
// Try stderr first
|
|
656
|
+
try {
|
|
657
|
+
process.stderr.write(message);
|
|
658
|
+
}
|
|
659
|
+
catch (e) {
|
|
660
|
+
// Fallback to console.error for Windows
|
|
661
|
+
console.error(message.trim());
|
|
662
|
+
}
|
|
663
|
+
// Also try to write to a debug file on Windows
|
|
664
|
+
if (process.platform === 'win32') {
|
|
665
|
+
try {
|
|
666
|
+
const fs = require('fs');
|
|
667
|
+
const path = require('path');
|
|
668
|
+
const debugFile = path.join(process.cwd(), 'mcp-debug.log');
|
|
669
|
+
fs.appendFileSync(debugFile, `${new Date().toISOString()} ${message}`, 'utf8');
|
|
670
|
+
}
|
|
671
|
+
catch (e) {
|
|
672
|
+
// Ignore file write errors
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
// Re-export header constants from interfaces package
|
|
677
|
+
__exportStar(require("@mcp-abap-adt/interfaces"), exports);
|
|
678
|
+
// Re-export handler exporter for external server integration
|
|
679
|
+
var index_js_1 = require("../../lib/handlers/index.js");
|
|
680
|
+
Object.defineProperty(exports, "HandlerExporter", { enumerable: true, get: function () { return index_js_1.HandlerExporter; } });
|
|
681
|
+
Object.defineProperty(exports, "createDefaultHandlerExporter", { enumerable: true, get: function () { return index_js_1.createDefaultHandlerExporter; } });
|
|
682
|
+
/**
|
|
683
|
+
* Server class for interacting with ABAP systems via ADT.
|
|
684
|
+
*/
|
|
685
|
+
class mcp_abap_adt_server {
|
|
686
|
+
allowProcessExit;
|
|
687
|
+
registerSignalHandlers;
|
|
688
|
+
mcpServer; // MCP server for all transports
|
|
689
|
+
sapConfig; // SAP configuration
|
|
690
|
+
hasEnvFile; // Track if .env file was found at startup
|
|
691
|
+
transportConfig;
|
|
692
|
+
httpServer;
|
|
693
|
+
shuttingDown = false;
|
|
694
|
+
defaultMcpDestination; // Default MCP destination from --mcp parameter
|
|
695
|
+
defaultDestination; // Default destination (from --mcp or .env, used when client doesn't specify)
|
|
696
|
+
mcpHandlers;
|
|
697
|
+
exposition = ['readonly', 'high']; // Default exposition
|
|
698
|
+
// Client session tracking for StreamableHTTP (like the example)
|
|
699
|
+
streamableHttpSessions = new Map();
|
|
700
|
+
// Map sessionId -> client key (streamable HTTP) for quick lookups
|
|
701
|
+
streamableSessionIndex = new Map();
|
|
702
|
+
// AuthBroker factory for creating and managing AuthBroker instances
|
|
703
|
+
authBrokerFactory;
|
|
704
|
+
// Path to .env file (used to create SessionStore when --mcp is not specified)
|
|
705
|
+
envFilePath;
|
|
706
|
+
/**
|
|
707
|
+
* Initialize default broker on server startup (for stdio/SSE transports)
|
|
708
|
+
* Creates broker with default destination from --mcp parameter or .env file
|
|
709
|
+
*/
|
|
710
|
+
/**
|
|
711
|
+
* Initialize default broker using unified AuthBrokerFactory logic
|
|
712
|
+
* Sets defaultDestination based on what broker was created
|
|
713
|
+
*/
|
|
714
|
+
async initializeDefaultBroker() {
|
|
715
|
+
// Use unified AuthBrokerFactory logic to initialize default broker
|
|
716
|
+
await this.authBrokerFactory.initializeDefaultBroker();
|
|
717
|
+
// Get default broker to determine default destination
|
|
718
|
+
const defaultBroker = this.authBrokerFactory.getDefaultBroker();
|
|
719
|
+
console.error("[DEBUG v1] initializeDefaultBroker result:", {
|
|
720
|
+
hasBroker: !!defaultBroker,
|
|
721
|
+
defaultMcpDestination: this.defaultMcpDestination,
|
|
722
|
+
envFilePath: this.envFilePath,
|
|
723
|
+
});
|
|
724
|
+
if (defaultBroker) {
|
|
725
|
+
// If we have --mcp, use that as default destination
|
|
726
|
+
if (this.defaultMcpDestination) {
|
|
727
|
+
this.defaultDestination = this.defaultMcpDestination;
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
// Otherwise use 'default' as destination name
|
|
731
|
+
this.defaultDestination = 'default';
|
|
732
|
+
}
|
|
733
|
+
logger_1.logger?.info("Default broker initialized", {
|
|
734
|
+
type: "DEFAULT_BROKER_INITIALIZED",
|
|
735
|
+
destination: this.defaultDestination,
|
|
736
|
+
transport: this.transportConfig.type,
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
logger_1.logger?.debug("No default broker created (no conditions met)", {
|
|
741
|
+
type: "NO_DEFAULT_BROKER",
|
|
742
|
+
transport: this.transportConfig.type,
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Get or create AuthBroker for a specific destination (lazy initialization)
|
|
748
|
+
* If destination is not specified, returns default broker
|
|
749
|
+
*/
|
|
750
|
+
async getOrCreateAuthBroker(destination, clientKey) {
|
|
751
|
+
// clientKey is ignored in unified logic (one broker per destination)
|
|
752
|
+
return this.authBrokerFactory.getOrCreateAuthBroker(destination);
|
|
753
|
+
}
|
|
754
|
+
async applyAuthHeaders(headers, sessionId, clientKey) {
|
|
755
|
+
// Security: If server is listening on non-local interface (0.0.0.0), don't use default destination
|
|
756
|
+
// Client must provide all connection parameters in headers - server only proxies to ABAP
|
|
757
|
+
const isNonLocalInterface = this.isListeningOnNonLocalInterface();
|
|
758
|
+
if (isNonLocalInterface) {
|
|
759
|
+
// Non-local interface: use only what client provides in headers, no default destination
|
|
760
|
+
if (!headers || Object.keys(headers).length === 0) {
|
|
761
|
+
logger_1.logger?.info("No headers provided - server listening on non-local interface requires all headers from client", {
|
|
762
|
+
type: "NO_HEADERS_NON_LOCAL_INTERFACE",
|
|
763
|
+
sessionId: sessionId?.substring(0, 8),
|
|
764
|
+
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.`,
|
|
765
|
+
});
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
// Use headers as-is, no default destination
|
|
769
|
+
logger_1.logger?.debug("Non-local interface: using only client-provided headers", {
|
|
770
|
+
type: "NON_LOCAL_INTERFACE_HEADERS_ONLY",
|
|
771
|
+
sessionId: sessionId?.substring(0, 8),
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
// Local interface (127.0.0.1): can use default destination if no headers
|
|
776
|
+
// If no headers but defaultDestination is set, use it
|
|
777
|
+
// Client values have priority (Q10), but if no headers at all, use default
|
|
778
|
+
if (!headers || Object.keys(headers).length === 0) {
|
|
779
|
+
if (this.defaultDestination) {
|
|
780
|
+
// Use default destination to get connection config from broker
|
|
781
|
+
const authBroker = await this.getOrCreateAuthBroker(this.defaultDestination, clientKey || sessionId);
|
|
782
|
+
if (authBroker) {
|
|
783
|
+
try {
|
|
784
|
+
const connConfig = await authBroker.getConnectionConfig(this.defaultDestination);
|
|
785
|
+
if (connConfig?.serviceUrl) {
|
|
786
|
+
// Check authType from connection config BEFORE calling getToken()
|
|
787
|
+
const isBasicAuth = connConfig.authType === 'basic' || (connConfig.username && connConfig.password);
|
|
788
|
+
if (isBasicAuth) {
|
|
789
|
+
// Basic auth for on-premise
|
|
790
|
+
if (connConfig.username && connConfig.password) {
|
|
791
|
+
this.processBasicAuthConfigUpdate(connConfig.serviceUrl, connConfig.username, connConfig.password, sessionId);
|
|
792
|
+
logger_1.logger?.info("No headers provided, using default destination with basic auth", {
|
|
793
|
+
type: "NO_HEADERS_DEFAULT_DESTINATION_BASIC_AUTH",
|
|
794
|
+
destination: this.defaultDestination,
|
|
795
|
+
sessionId: sessionId?.substring(0, 8),
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
else {
|
|
800
|
+
// JWT auth for cloud
|
|
801
|
+
const jwtToken = await authBroker.getToken(this.defaultDestination);
|
|
802
|
+
if (jwtToken) {
|
|
803
|
+
// Create headers object with default destination
|
|
804
|
+
headers = {
|
|
805
|
+
[interfaces_1.HEADER_MCP_DESTINATION]: this.defaultDestination,
|
|
806
|
+
[interfaces_1.HEADER_SAP_URL]: connConfig.serviceUrl,
|
|
807
|
+
[interfaces_1.HEADER_SAP_JWT_TOKEN]: jwtToken,
|
|
808
|
+
};
|
|
809
|
+
logger_1.logger?.info("No headers provided, using default destination", {
|
|
810
|
+
type: "NO_HEADERS_DEFAULT_DESTINATION_USED",
|
|
811
|
+
destination: this.defaultDestination,
|
|
812
|
+
sessionId: sessionId?.substring(0, 8),
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
catch (error) {
|
|
819
|
+
logger_1.logger?.warn("Failed to get connection config from default destination", {
|
|
820
|
+
type: "DEFAULT_DESTINATION_CONFIG_FAILED",
|
|
821
|
+
destination: this.defaultDestination,
|
|
822
|
+
sessionId: sessionId?.substring(0, 8),
|
|
823
|
+
error: error instanceof Error ? error.message : String(error),
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
// If still no headers after trying default destination, log and return
|
|
829
|
+
if (!headers || Object.keys(headers).length === 0) {
|
|
830
|
+
logger_1.logger?.info("No headers provided in request and no default destination available", {
|
|
831
|
+
type: "NO_HEADERS_PROVIDED",
|
|
832
|
+
sessionId: sessionId?.substring(0, 8),
|
|
833
|
+
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`,
|
|
834
|
+
});
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
// Apply default destination if not provided in headers (client values have priority)
|
|
839
|
+
// Only for local interface
|
|
840
|
+
if (this.defaultDestination && !headers[interfaces_1.HEADER_MCP_DESTINATION] && !headers['X-MCP-Destination']) {
|
|
841
|
+
headers[interfaces_1.HEADER_MCP_DESTINATION] = this.defaultDestination;
|
|
842
|
+
logger_1.logger?.info("Using default destination (client didn't specify)", {
|
|
843
|
+
type: "DEFAULT_DESTINATION_APPLIED",
|
|
844
|
+
destination: this.defaultDestination,
|
|
845
|
+
sessionId: sessionId?.substring(0, 8),
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
// Ensure headers is set for processing below
|
|
850
|
+
if (!headers) {
|
|
851
|
+
headers = {};
|
|
852
|
+
}
|
|
853
|
+
const headersWithDefault = { ...headers };
|
|
854
|
+
// Log which auth headers are present (for debugging)
|
|
855
|
+
// Check headers in both cases (Node.js normalizes to lowercase, but check both for safety)
|
|
856
|
+
const authHeaders = {
|
|
857
|
+
[interfaces_1.HEADER_SAP_DESTINATION_SERVICE]: headersWithDefault[interfaces_1.HEADER_SAP_DESTINATION_SERVICE] || headersWithDefault['X-SAP-Destination'],
|
|
858
|
+
[interfaces_1.HEADER_MCP_DESTINATION]: headersWithDefault[interfaces_1.HEADER_MCP_DESTINATION] || headersWithDefault['X-MCP-Destination'],
|
|
859
|
+
[interfaces_1.HEADER_SAP_URL]: (headersWithDefault[interfaces_1.HEADER_SAP_URL] || headersWithDefault['X-SAP-URL']) ? '[present]' : undefined,
|
|
860
|
+
[interfaces_1.HEADER_SAP_AUTH_TYPE]: headersWithDefault[interfaces_1.HEADER_SAP_AUTH_TYPE] || headersWithDefault['X-SAP-Auth-Type'],
|
|
861
|
+
[interfaces_1.HEADER_SAP_JWT_TOKEN]: (headersWithDefault[interfaces_1.HEADER_SAP_JWT_TOKEN] || headersWithDefault['X-SAP-JWT-Token']) ? '[present]' : undefined,
|
|
862
|
+
[interfaces_1.HEADER_SAP_LOGIN]: (headersWithDefault[interfaces_1.HEADER_SAP_LOGIN] || headersWithDefault['X-SAP-Login']) ? '[present]' : undefined,
|
|
863
|
+
};
|
|
864
|
+
logger_1.logger?.info("Processing authentication headers", {
|
|
865
|
+
type: "AUTH_HEADERS_PROCESSING",
|
|
866
|
+
headers: authHeaders,
|
|
867
|
+
platform: process.platform,
|
|
868
|
+
sessionId: sessionId?.substring(0, 8),
|
|
869
|
+
allHeaderKeys: Object.keys(headersWithDefault).filter(k => {
|
|
870
|
+
const lowerKey = k.toLowerCase();
|
|
871
|
+
return lowerKey.startsWith('x-sap') || lowerKey.startsWith('x-mcp');
|
|
872
|
+
}),
|
|
873
|
+
});
|
|
874
|
+
// Use header validator to validate and prioritize authentication methods
|
|
875
|
+
const validationResult = (0, header_validator_1.validateAuthHeaders)(headersWithDefault);
|
|
876
|
+
// Log warnings if any
|
|
877
|
+
if (validationResult.warnings.length > 0) {
|
|
878
|
+
logger_1.logger?.debug("Header validation warnings", {
|
|
879
|
+
type: "HEADER_VALIDATION_WARNINGS",
|
|
880
|
+
warnings: validationResult.warnings,
|
|
881
|
+
sessionId: sessionId?.substring(0, 8),
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
// If validation failed, log info (not error) and return
|
|
885
|
+
// This is not an error - user may be using .env file or base config
|
|
886
|
+
if (!validationResult.isValid || !validationResult.config) {
|
|
887
|
+
if (validationResult.errors.length > 0) {
|
|
888
|
+
logger_1.logger?.debug("Header validation failed - will use .env file or base config", {
|
|
889
|
+
type: "HEADER_VALIDATION_FAILED",
|
|
890
|
+
errors: validationResult.errors,
|
|
891
|
+
sessionId: sessionId?.substring(0, 8),
|
|
892
|
+
hint: "No valid headers found. Will use .env file or base config if available.",
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
logger_1.logger?.debug("No valid authentication headers found - will use .env file or base config", {
|
|
897
|
+
type: "NO_VALID_AUTH_HEADERS",
|
|
898
|
+
sessionId: sessionId?.substring(0, 8),
|
|
899
|
+
hint: "No headers provided. Will use .env file or base config if available.",
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
const config = validationResult.config;
|
|
905
|
+
// Process based on priority (highest to lowest)
|
|
906
|
+
switch (config.priority) {
|
|
907
|
+
case header_validator_1.AuthMethodPriority.SAP_DESTINATION: {
|
|
908
|
+
// Priority 4: x-sap-destination (uses AuthBroker, URL from destination)
|
|
909
|
+
if (!config.destination) {
|
|
910
|
+
logger_1.logger?.warn("SAP destination auth requires destination name", {
|
|
911
|
+
type: "SAP_DESTINATION_AUTH_MISSING",
|
|
912
|
+
destination: config.destination,
|
|
913
|
+
sessionId: sessionId?.substring(0, 8),
|
|
914
|
+
});
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
// Get or create AuthBroker for this destination (lazy initialization)
|
|
918
|
+
const authBroker = await this.getOrCreateAuthBroker(config.destination, clientKey || sessionId);
|
|
919
|
+
if (!authBroker) {
|
|
920
|
+
const errorMessage = `Failed to initialize AuthBroker for destination "${config.destination}". Auth-broker is only available for HTTP/streamable-http transport.`;
|
|
921
|
+
logger_1.logger?.error(errorMessage, {
|
|
922
|
+
type: "AUTH_BROKER_NOT_INITIALIZED",
|
|
923
|
+
destination: config.destination,
|
|
924
|
+
sessionId: sessionId?.substring(0, 8),
|
|
925
|
+
transport: this.transportConfig.type,
|
|
926
|
+
});
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
try {
|
|
930
|
+
// Get connection config from AuthBroker (loads from .env or service key)
|
|
931
|
+
// Note: destination name must exactly match service key filename (case-sensitive)
|
|
932
|
+
// Example: if file is "sk.json", destination must be "sk" (not "SK")
|
|
933
|
+
const connConfig = await authBroker.getConnectionConfig(config.destination);
|
|
934
|
+
if (!connConfig || !connConfig.serviceUrl) {
|
|
935
|
+
logger_1.logger?.error("Failed to get SAP URL from destination", {
|
|
936
|
+
type: "SAP_DESTINATION_URL_NOT_FOUND",
|
|
937
|
+
destination: config.destination,
|
|
938
|
+
sessionId: sessionId?.substring(0, 8),
|
|
939
|
+
hint: `Service key file "${config.destination}.json" not found or missing URL. Check file name matches destination exactly (case-sensitive).`,
|
|
940
|
+
});
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
const sapUrl = connConfig.serviceUrl;
|
|
944
|
+
logger_1.logger?.info("SAP URL retrieved from destination", {
|
|
945
|
+
type: "SAP_URL_RETRIEVED",
|
|
946
|
+
destination: config.destination,
|
|
947
|
+
url: sapUrl,
|
|
948
|
+
sessionId: sessionId?.substring(0, 8),
|
|
949
|
+
});
|
|
950
|
+
// Check authType from connection config BEFORE calling getToken()
|
|
951
|
+
const isBasicAuth = connConfig.authType === 'basic' || (connConfig.username && connConfig.password);
|
|
952
|
+
if (isBasicAuth) {
|
|
953
|
+
// Basic auth for on-premise
|
|
954
|
+
if (connConfig.username && connConfig.password) {
|
|
955
|
+
this.processBasicAuthConfigUpdate(sapUrl, connConfig.username, connConfig.password, sessionId);
|
|
956
|
+
logger_1.logger?.info("Updated SAP configuration using SAP destination with basic auth", {
|
|
957
|
+
type: "SAP_CONFIG_UPDATED_SAP_DESTINATION_BASIC_AUTH",
|
|
958
|
+
destination: config.destination,
|
|
959
|
+
url: sapUrl,
|
|
960
|
+
sessionId: sessionId?.substring(0, 8),
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
else {
|
|
964
|
+
logger_1.logger?.error("Basic auth requires username and password", {
|
|
965
|
+
type: "BASIC_AUTH_MISSING_CREDENTIALS",
|
|
966
|
+
destination: config.destination,
|
|
967
|
+
sessionId: sessionId?.substring(0, 8),
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
else {
|
|
972
|
+
// JWT auth for cloud
|
|
973
|
+
// Get token from AuthBroker
|
|
974
|
+
const jwtToken = await authBroker.getToken(config.destination);
|
|
975
|
+
// Register AuthBroker in global registry for connection to use during token refresh
|
|
976
|
+
const { registerAuthBroker } = require('./lib/utils');
|
|
977
|
+
registerAuthBroker(config.destination, authBroker);
|
|
978
|
+
this.processJwtConfigUpdate(sapUrl, jwtToken, undefined, config.destination, sessionId);
|
|
979
|
+
logger_1.logger?.info("Updated SAP configuration using SAP destination (AuthBroker)", {
|
|
980
|
+
type: "SAP_CONFIG_UPDATED_SAP_DESTINATION",
|
|
981
|
+
destination: config.destination,
|
|
982
|
+
url: sapUrl,
|
|
983
|
+
sessionId: sessionId?.substring(0, 8),
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
catch (error) {
|
|
988
|
+
logger_1.logger?.error("Failed to get token from AuthBroker for SAP destination", {
|
|
989
|
+
type: "AUTH_BROKER_ERROR_SAP_DESTINATION",
|
|
990
|
+
destination: config.destination,
|
|
991
|
+
error: error instanceof Error ? error.message : String(error),
|
|
992
|
+
sessionId: sessionId?.substring(0, 8),
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
case header_validator_1.AuthMethodPriority.MCP_DESTINATION: {
|
|
998
|
+
// Priority 3: x-mcp-destination (uses AuthBroker, URL from destination or x-sap-url header)
|
|
999
|
+
if (!config.destination) {
|
|
1000
|
+
logger_1.logger?.warn("MCP destination auth requires destination", {
|
|
1001
|
+
type: "MCP_DESTINATION_AUTH_MISSING",
|
|
1002
|
+
destination: config.destination,
|
|
1003
|
+
sessionId: sessionId?.substring(0, 8),
|
|
1004
|
+
});
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
// Get or create AuthBroker for this destination (lazy initialization)
|
|
1008
|
+
const authBroker = await this.getOrCreateAuthBroker(config.destination, clientKey || sessionId);
|
|
1009
|
+
if (!authBroker) {
|
|
1010
|
+
logger_1.logger?.error("Failed to initialize AuthBroker for MCP destination", {
|
|
1011
|
+
type: "AUTH_BROKER_NOT_INITIALIZED",
|
|
1012
|
+
destination: config.destination,
|
|
1013
|
+
sessionId: sessionId?.substring(0, 8),
|
|
1014
|
+
transport: this.transportConfig.type,
|
|
1015
|
+
});
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
try {
|
|
1019
|
+
// Get connection config from AuthBroker (loads from .env or service key)
|
|
1020
|
+
// If x-sap-url was provided in headers, it will be ignored (warning already issued by validator)
|
|
1021
|
+
// Note: destination name must exactly match service key filename (case-sensitive)
|
|
1022
|
+
const connConfig = await authBroker.getConnectionConfig(config.destination);
|
|
1023
|
+
if (!connConfig || !connConfig.serviceUrl) {
|
|
1024
|
+
logger_1.logger?.error("Failed to get SAP URL from destination", {
|
|
1025
|
+
type: "MCP_DESTINATION_URL_NOT_FOUND",
|
|
1026
|
+
destination: config.destination,
|
|
1027
|
+
sessionId: sessionId?.substring(0, 8),
|
|
1028
|
+
hint: `Service key file "${config.destination}.json" not found or missing URL. Check file name matches destination exactly (case-sensitive).`,
|
|
1029
|
+
});
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
const sapUrl = connConfig.serviceUrl;
|
|
1033
|
+
logger_1.logger?.info("SAP URL retrieved from destination", {
|
|
1034
|
+
type: "SAP_URL_RETRIEVED",
|
|
1035
|
+
destination: config.destination,
|
|
1036
|
+
url: sapUrl,
|
|
1037
|
+
sessionId: sessionId?.substring(0, 8),
|
|
1038
|
+
});
|
|
1039
|
+
// Check authType from connection config BEFORE calling getToken()
|
|
1040
|
+
const isBasicAuth = connConfig.authType === 'basic' || (connConfig.username && connConfig.password);
|
|
1041
|
+
if (isBasicAuth) {
|
|
1042
|
+
// Basic auth for on-premise
|
|
1043
|
+
if (connConfig.username && connConfig.password) {
|
|
1044
|
+
this.processBasicAuthConfigUpdate(sapUrl, connConfig.username, connConfig.password, sessionId);
|
|
1045
|
+
logger_1.logger?.info("Updated SAP configuration using MCP destination with basic auth", {
|
|
1046
|
+
type: "SAP_CONFIG_UPDATED_MCP_DESTINATION_BASIC_AUTH",
|
|
1047
|
+
destination: config.destination,
|
|
1048
|
+
url: sapUrl,
|
|
1049
|
+
sessionId: sessionId?.substring(0, 8),
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
else {
|
|
1053
|
+
logger_1.logger?.error("Basic auth requires username and password", {
|
|
1054
|
+
type: "BASIC_AUTH_MISSING_CREDENTIALS",
|
|
1055
|
+
destination: config.destination,
|
|
1056
|
+
sessionId: sessionId?.substring(0, 8),
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
else {
|
|
1061
|
+
// JWT auth for cloud
|
|
1062
|
+
// Get token from AuthBroker
|
|
1063
|
+
const jwtToken = await authBroker.getToken(config.destination);
|
|
1064
|
+
// Register AuthBroker in global registry for connection to use during token refresh
|
|
1065
|
+
const { registerAuthBroker } = require('../../lib/utils.js');
|
|
1066
|
+
registerAuthBroker(config.destination, authBroker);
|
|
1067
|
+
this.processJwtConfigUpdate(sapUrl, jwtToken, undefined, config.destination, sessionId);
|
|
1068
|
+
logger_1.logger?.info("Updated SAP configuration using MCP destination (AuthBroker)", {
|
|
1069
|
+
type: "SAP_CONFIG_UPDATED_MCP_DESTINATION",
|
|
1070
|
+
destination: config.destination,
|
|
1071
|
+
url: sapUrl,
|
|
1072
|
+
sessionId: sessionId?.substring(0, 8),
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
catch (error) {
|
|
1077
|
+
logger_1.logger?.error("Failed to get token from AuthBroker for MCP destination", {
|
|
1078
|
+
type: "AUTH_BROKER_ERROR_MCP_DESTINATION",
|
|
1079
|
+
destination: config.destination,
|
|
1080
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1081
|
+
sessionId: sessionId?.substring(0, 8),
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
case header_validator_1.AuthMethodPriority.DIRECT_JWT: {
|
|
1087
|
+
// Priority 2: x-sap-jwt-token (direct JWT token)
|
|
1088
|
+
if (!config.sapUrl || !config.jwtToken) {
|
|
1089
|
+
logger_1.logger?.warn("Direct JWT auth requires URL and token", {
|
|
1090
|
+
type: "DIRECT_JWT_AUTH_MISSING",
|
|
1091
|
+
sapUrl: config.sapUrl,
|
|
1092
|
+
hasToken: !!config.jwtToken,
|
|
1093
|
+
sessionId: sessionId?.substring(0, 8),
|
|
1094
|
+
});
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
this.processJwtConfigUpdate(config.sapUrl, config.jwtToken, config.refreshToken, sessionId);
|
|
1098
|
+
logger_1.logger?.info("Updated SAP configuration using direct JWT token", {
|
|
1099
|
+
type: "SAP_CONFIG_UPDATED_DIRECT_JWT",
|
|
1100
|
+
url: config.sapUrl,
|
|
1101
|
+
sessionId: sessionId?.substring(0, 8),
|
|
1102
|
+
});
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
case header_validator_1.AuthMethodPriority.BASIC: {
|
|
1106
|
+
// Priority 1: x-sap-login + x-sap-password (basic auth)
|
|
1107
|
+
if (!config.sapUrl || !config.username || !config.password) {
|
|
1108
|
+
logger_1.logger?.warn("Basic auth requires URL, username, and password", {
|
|
1109
|
+
type: "BASIC_AUTH_MISSING",
|
|
1110
|
+
sapUrl: config.sapUrl,
|
|
1111
|
+
hasUsername: !!config.username,
|
|
1112
|
+
hasPassword: !!config.password,
|
|
1113
|
+
sessionId: sessionId?.substring(0, 8),
|
|
1114
|
+
});
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
this.processBasicAuthConfigUpdate(config.sapUrl, config.username, config.password, sessionId);
|
|
1118
|
+
logger_1.logger?.info("Updated SAP configuration using basic auth", {
|
|
1119
|
+
type: "SAP_CONFIG_UPDATED_BASIC",
|
|
1120
|
+
url: config.sapUrl,
|
|
1121
|
+
sessionId: sessionId?.substring(0, 8),
|
|
1122
|
+
});
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
default: {
|
|
1126
|
+
logger_1.logger?.warn("Unknown authentication method priority", {
|
|
1127
|
+
type: "UNKNOWN_AUTH_PRIORITY",
|
|
1128
|
+
priority: config.priority,
|
|
1129
|
+
sessionId: sessionId?.substring(0, 8),
|
|
1130
|
+
});
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
processJwtConfigUpdate(sapUrl, jwtToken, refreshToken, destination, sessionId) {
|
|
1136
|
+
const sanitizeToken = (token) => token.length <= 10 ? token : `${token.substring(0, 6)}…${token.substring(token.length - 4)}`;
|
|
1137
|
+
// URL from auth-broker/service key is already clean and correct, no need to clean it
|
|
1138
|
+
// Only validate format
|
|
1139
|
+
let cleanedUrl = sapUrl.trim();
|
|
1140
|
+
// Ensure URL has protocol
|
|
1141
|
+
if (!/^https?:\/\//.test(cleanedUrl)) {
|
|
1142
|
+
logger_1.logger?.error("Invalid URL format in processJwtConfigUpdate", {
|
|
1143
|
+
type: "INVALID_URL_FORMAT",
|
|
1144
|
+
originalUrl: sapUrl,
|
|
1145
|
+
cleanedUrl: cleanedUrl,
|
|
1146
|
+
sessionId: sessionId?.substring(0, 8),
|
|
1147
|
+
});
|
|
1148
|
+
throw new Error(`Invalid URL format: "${sapUrl}". Expected format: https://your-system.sap.com`);
|
|
1149
|
+
}
|
|
1150
|
+
// Normalize URL using URL object
|
|
1151
|
+
try {
|
|
1152
|
+
const urlObj = new URL(cleanedUrl);
|
|
1153
|
+
cleanedUrl = urlObj.href.replace(/\/$/, ''); // Remove trailing slash
|
|
1154
|
+
}
|
|
1155
|
+
catch (urlError) {
|
|
1156
|
+
logger_1.logger?.error("Failed to parse URL in processJwtConfigUpdate", {
|
|
1157
|
+
type: "URL_PARSE_ERROR",
|
|
1158
|
+
url: cleanedUrl,
|
|
1159
|
+
error: urlError instanceof Error ? urlError.message : String(urlError),
|
|
1160
|
+
sessionId: sessionId?.substring(0, 8),
|
|
1161
|
+
});
|
|
1162
|
+
throw new Error(`Invalid URL: "${sapUrl}". Error: ${urlError instanceof Error ? urlError.message : urlError}`);
|
|
1163
|
+
}
|
|
1164
|
+
// Use cleaned URL
|
|
1165
|
+
sapUrl = cleanedUrl;
|
|
1166
|
+
let baseConfig = this.sapConfig;
|
|
1167
|
+
// Don't load from .env if using auth-broker (--mcp or --auth-broker)
|
|
1168
|
+
// This prevents .env from being used when destination-based auth is specified
|
|
1169
|
+
const isUsingAuthBroker = this.defaultMcpDestination || useAuthBroker;
|
|
1170
|
+
if ((!baseConfig || baseConfig.url === "http://placeholder") && !isUsingAuthBroker) {
|
|
1171
|
+
try {
|
|
1172
|
+
baseConfig = (0, config_js_1.getConfig)();
|
|
1173
|
+
}
|
|
1174
|
+
catch (error) {
|
|
1175
|
+
logger_1.logger?.warn("Failed to load base SAP config when applying headers", {
|
|
1176
|
+
type: "SAP_CONFIG_HEADER_APPLY_FAILED",
|
|
1177
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1178
|
+
});
|
|
1179
|
+
baseConfig = {
|
|
1180
|
+
url: sapUrl,
|
|
1181
|
+
authType: "jwt",
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
else if (!baseConfig || baseConfig.url === "http://placeholder") {
|
|
1186
|
+
// Using auth-broker, don't load from .env
|
|
1187
|
+
baseConfig = {
|
|
1188
|
+
url: sapUrl,
|
|
1189
|
+
authType: "jwt",
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
// Check if any configuration changed
|
|
1193
|
+
const urlChanged = sapUrl !== baseConfig.url;
|
|
1194
|
+
const authTypeChanged = "jwt" !== baseConfig.authType;
|
|
1195
|
+
const tokenChanged = baseConfig.jwtToken !== jwtToken ||
|
|
1196
|
+
(!!refreshToken && refreshToken.trim() !== baseConfig.refreshToken);
|
|
1197
|
+
if (!urlChanged && !authTypeChanged && !tokenChanged) {
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
const newConfig = {
|
|
1201
|
+
...baseConfig,
|
|
1202
|
+
url: sapUrl,
|
|
1203
|
+
authType: "jwt",
|
|
1204
|
+
jwtToken,
|
|
1205
|
+
};
|
|
1206
|
+
if (refreshToken && refreshToken.trim()) {
|
|
1207
|
+
newConfig.refreshToken = refreshToken.trim();
|
|
1208
|
+
}
|
|
1209
|
+
(0, config_js_1.setSapConfigOverride)(newConfig);
|
|
1210
|
+
this.sapConfig = newConfig;
|
|
1211
|
+
// Store config and destination in session if sessionId is provided
|
|
1212
|
+
if (sessionId) {
|
|
1213
|
+
const clientKeyForSession = this.streamableSessionIndex.get(sessionId);
|
|
1214
|
+
const session = clientKeyForSession ? this.streamableHttpSessions.get(clientKeyForSession) : undefined;
|
|
1215
|
+
if (session) {
|
|
1216
|
+
session.sapConfig = newConfig;
|
|
1217
|
+
if (destination) {
|
|
1218
|
+
session.destination = destination;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
// Force connection cache invalidation (for backward compatibility)
|
|
1223
|
+
const { invalidateConnectionCache } = require('../../lib/utils.js');
|
|
1224
|
+
try {
|
|
1225
|
+
invalidateConnectionCache();
|
|
1226
|
+
}
|
|
1227
|
+
catch (error) {
|
|
1228
|
+
logger_1.logger?.debug("Connection cache invalidation failed", {
|
|
1229
|
+
type: "CONNECTION_CACHE_INVALIDATION_FAILED",
|
|
1230
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
logger_1.logger?.info("Updated SAP configuration from HTTP headers (JWT)", {
|
|
1234
|
+
type: "SAP_CONFIG_UPDATED",
|
|
1235
|
+
urlChanged: Boolean(urlChanged),
|
|
1236
|
+
authTypeChanged: Boolean(authTypeChanged),
|
|
1237
|
+
tokenChanged: Boolean(tokenChanged),
|
|
1238
|
+
hasRefreshToken: Boolean(refreshToken),
|
|
1239
|
+
jwtPreview: sanitizeToken(jwtToken),
|
|
1240
|
+
sessionId: sessionId?.substring(0, 8),
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Process JWT config update using AuthBroker for destination-based authentication
|
|
1245
|
+
* @private
|
|
1246
|
+
*/
|
|
1247
|
+
async processJwtConfigUpdateWithAuthBroker(sapUrl, destination, sessionId) {
|
|
1248
|
+
// Get or create AuthBroker for this destination (lazy initialization)
|
|
1249
|
+
const authBroker = await this.getOrCreateAuthBroker(destination, sessionId);
|
|
1250
|
+
if (!authBroker) {
|
|
1251
|
+
logger_1.logger?.warn("AuthBroker not available, falling back to direct token", {
|
|
1252
|
+
type: "AUTH_BROKER_NOT_AVAILABLE",
|
|
1253
|
+
destination,
|
|
1254
|
+
transport: this.transportConfig.type,
|
|
1255
|
+
});
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
try {
|
|
1259
|
+
// Get token from AuthBroker (will load from .env, validate, and refresh if needed)
|
|
1260
|
+
const jwtToken = await authBroker.getToken(destination);
|
|
1261
|
+
// Load refresh token from .env if available (AuthBroker doesn't return it, but we can load it)
|
|
1262
|
+
// For now, we'll just use the access token - refresh will be handled by AuthBroker automatically
|
|
1263
|
+
const refreshToken = undefined; // AuthBroker handles refresh internally
|
|
1264
|
+
// Process JWT config update with the token from AuthBroker
|
|
1265
|
+
this.processJwtConfigUpdate(sapUrl, jwtToken, refreshToken, destination, sessionId);
|
|
1266
|
+
logger_1.logger?.info("Updated SAP configuration using AuthBroker (destination-based)", {
|
|
1267
|
+
type: "SAP_CONFIG_UPDATED_AUTH_BROKER",
|
|
1268
|
+
destination,
|
|
1269
|
+
url: sapUrl,
|
|
1270
|
+
sessionId: sessionId?.substring(0, 8),
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
catch (error) {
|
|
1274
|
+
logger_1.logger?.error("Failed to get token from AuthBroker", {
|
|
1275
|
+
type: "AUTH_BROKER_ERROR",
|
|
1276
|
+
destination,
|
|
1277
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1278
|
+
});
|
|
1279
|
+
// Don't throw - let the request continue with existing config or fail later
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
processBasicAuthConfigUpdate(sapUrl, username, password, sessionId) {
|
|
1283
|
+
let baseConfig = this.sapConfig;
|
|
1284
|
+
if (!baseConfig || baseConfig.url === "http://placeholder") {
|
|
1285
|
+
try {
|
|
1286
|
+
baseConfig = (0, config_js_1.getConfig)();
|
|
1287
|
+
}
|
|
1288
|
+
catch (error) {
|
|
1289
|
+
logger_1.logger?.warn("Failed to load base SAP config when applying headers", {
|
|
1290
|
+
type: "SAP_CONFIG_HEADER_APPLY_FAILED",
|
|
1291
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1292
|
+
});
|
|
1293
|
+
baseConfig = {
|
|
1294
|
+
url: sapUrl,
|
|
1295
|
+
authType: "basic",
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
// Check if any configuration changed
|
|
1300
|
+
const urlChanged = sapUrl !== baseConfig.url;
|
|
1301
|
+
const authTypeChanged = "basic" !== baseConfig.authType;
|
|
1302
|
+
const credentialsChanged = baseConfig.username !== username ||
|
|
1303
|
+
baseConfig.password !== password;
|
|
1304
|
+
if (!urlChanged && !authTypeChanged && !credentialsChanged) {
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
const newConfig = {
|
|
1308
|
+
...baseConfig,
|
|
1309
|
+
url: sapUrl,
|
|
1310
|
+
authType: "basic",
|
|
1311
|
+
username,
|
|
1312
|
+
password,
|
|
1313
|
+
};
|
|
1314
|
+
(0, config_js_1.setSapConfigOverride)(newConfig);
|
|
1315
|
+
this.sapConfig = newConfig;
|
|
1316
|
+
// Store config in session if sessionId is provided
|
|
1317
|
+
if (sessionId) {
|
|
1318
|
+
const clientKeyForSession = this.streamableSessionIndex.get(sessionId);
|
|
1319
|
+
const session = clientKeyForSession ? this.streamableHttpSessions.get(clientKeyForSession) : undefined;
|
|
1320
|
+
if (session) {
|
|
1321
|
+
session.sapConfig = newConfig;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
// Force connection cache invalidation (for backward compatibility)
|
|
1325
|
+
const { invalidateConnectionCache } = require('../../lib/utils.js');
|
|
1326
|
+
try {
|
|
1327
|
+
invalidateConnectionCache();
|
|
1328
|
+
}
|
|
1329
|
+
catch (error) {
|
|
1330
|
+
logger_1.logger?.debug("Connection cache invalidation failed", {
|
|
1331
|
+
type: "CONNECTION_CACHE_INVALIDATION_FAILED",
|
|
1332
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
logger_1.logger?.info("Updated SAP configuration from HTTP headers (Basic)", {
|
|
1336
|
+
type: "SAP_CONFIG_UPDATED",
|
|
1337
|
+
urlChanged: Boolean(urlChanged),
|
|
1338
|
+
authTypeChanged: Boolean(authTypeChanged),
|
|
1339
|
+
credentialsChanged: Boolean(credentialsChanged),
|
|
1340
|
+
hasUsername: Boolean(username),
|
|
1341
|
+
sessionId: sessionId?.substring(0, 8),
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Check if connection is from localhost
|
|
1346
|
+
*/
|
|
1347
|
+
isLocalConnection(remoteAddress) {
|
|
1348
|
+
if (!remoteAddress) {
|
|
1349
|
+
return false;
|
|
1350
|
+
}
|
|
1351
|
+
// Check for IPv4 localhost
|
|
1352
|
+
if (remoteAddress === "127.0.0.1" || remoteAddress === "localhost") {
|
|
1353
|
+
return true;
|
|
1354
|
+
}
|
|
1355
|
+
// Check for IPv6 localhost
|
|
1356
|
+
if (remoteAddress === "::1" || remoteAddress === "::ffff:127.0.0.1") {
|
|
1357
|
+
return true;
|
|
1358
|
+
}
|
|
1359
|
+
// Check if it's a loopback interface
|
|
1360
|
+
if (remoteAddress.startsWith("127.") || remoteAddress.startsWith("::1")) {
|
|
1361
|
+
return true;
|
|
1362
|
+
}
|
|
1363
|
+
return false;
|
|
1364
|
+
}
|
|
1365
|
+
/**
|
|
1366
|
+
* Check if server is listening on non-local interface (0.0.0.0)
|
|
1367
|
+
* When listening on 0.0.0.0, we don't use default destination - client must provide all headers
|
|
1368
|
+
*/
|
|
1369
|
+
isListeningOnNonLocalInterface() {
|
|
1370
|
+
if (this.transportConfig.type === "streamable-http" || this.transportConfig.type === "sse") {
|
|
1371
|
+
const host = this.transportConfig.host;
|
|
1372
|
+
// If host is 0.0.0.0, ::, or empty, it accepts connections from all interfaces
|
|
1373
|
+
return host === "0.0.0.0" || host === "::" || host === "" || !host;
|
|
1374
|
+
}
|
|
1375
|
+
// stdio is always local
|
|
1376
|
+
return false;
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Check if request has SAP connection headers
|
|
1380
|
+
*/
|
|
1381
|
+
hasSapHeaders(headers) {
|
|
1382
|
+
if (!headers) {
|
|
1383
|
+
return false;
|
|
1384
|
+
}
|
|
1385
|
+
// Check for destination-based auth headers
|
|
1386
|
+
if (headers[interfaces_1.HEADER_SAP_DESTINATION_SERVICE] || headers[interfaces_1.HEADER_MCP_DESTINATION]) {
|
|
1387
|
+
return true;
|
|
1388
|
+
}
|
|
1389
|
+
// Check for direct auth headers
|
|
1390
|
+
const sapUrl = headers[interfaces_1.HEADER_SAP_URL];
|
|
1391
|
+
const sapAuthType = headers[interfaces_1.HEADER_SAP_AUTH_TYPE];
|
|
1392
|
+
return !!(sapUrl && sapAuthType);
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Constructor for the mcp_abap_adt_server class.
|
|
1396
|
+
*/
|
|
1397
|
+
constructor(options) {
|
|
1398
|
+
this.allowProcessExit = options?.allowProcessExit ?? true;
|
|
1399
|
+
this.registerSignalHandlers = options?.registerSignalHandlers ?? true;
|
|
1400
|
+
this.defaultMcpDestination = defaultMcpDestination;
|
|
1401
|
+
this.envFilePath = envFilePath; // Store .env file path for SessionStore creation
|
|
1402
|
+
// Parse exposition from CLI args or use default
|
|
1403
|
+
if (options?.exposition) {
|
|
1404
|
+
this.exposition = options.exposition;
|
|
1405
|
+
}
|
|
1406
|
+
else {
|
|
1407
|
+
// Parse --exposition from CLI
|
|
1408
|
+
const expositionArg = process.argv.find(arg => arg.startsWith('--exposition='));
|
|
1409
|
+
if (expositionArg) {
|
|
1410
|
+
const sets = expositionArg.split('=')[1]?.split(',').map(s => s.trim()).filter(Boolean) || [];
|
|
1411
|
+
this.exposition = sets.length > 0 ? sets : ['readonly', 'high'];
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
// Initialize AuthBroker factory
|
|
1415
|
+
const brokerConfig = new AuthBrokerConfig_js_1.AuthBrokerConfig(this.defaultMcpDestination, this.defaultDestination, this.envFilePath, authBrokerPath, unsafe, transportType, useAuthBroker, browser, logger_1.logger);
|
|
1416
|
+
this.authBrokerFactory = new index_1.AuthBrokerFactory(brokerConfig);
|
|
1417
|
+
this.mcpHandlers = new mcp_handlers_1.McpHandlers();
|
|
1418
|
+
// Check if .env file exists (was loaded at startup)
|
|
1419
|
+
// This is used to determine if we should restrict non-local connections
|
|
1420
|
+
this.hasEnvFile = fs_1.default.existsSync(envFilePath || path_1.default.resolve(process.cwd(), ".env"));
|
|
1421
|
+
if (options?.connection) {
|
|
1422
|
+
setAbapConnectionOverride(options.connection);
|
|
1423
|
+
}
|
|
1424
|
+
else {
|
|
1425
|
+
setAbapConnectionOverride(undefined);
|
|
1426
|
+
}
|
|
1427
|
+
if (!options?.connection) {
|
|
1428
|
+
(0, config_js_1.setSapConfigOverride)(options?.sapConfig);
|
|
1429
|
+
}
|
|
1430
|
+
// CHANGED: Don't validate config in constructor - will validate on actual ABAP requests
|
|
1431
|
+
// This allows creating server instance without .env file when using runtime config (e.g., from HTTP headers)
|
|
1432
|
+
try {
|
|
1433
|
+
if (options?.sapConfig) {
|
|
1434
|
+
this.sapConfig = options.sapConfig;
|
|
1435
|
+
}
|
|
1436
|
+
else if (!options?.connection) {
|
|
1437
|
+
// Don't load .env config if using auth-broker (--mcp or --auth-broker)
|
|
1438
|
+
// Connection will be created via auth-broker instead
|
|
1439
|
+
if (this.defaultMcpDestination || useAuthBroker) {
|
|
1440
|
+
// Using auth-broker, don't load .env config
|
|
1441
|
+
this.sapConfig = {
|
|
1442
|
+
url: "http://placeholder",
|
|
1443
|
+
authType: "basic",
|
|
1444
|
+
};
|
|
1445
|
+
logger_1.logger?.debug("Skipping .env config load (using auth-broker)", {
|
|
1446
|
+
type: "SKIP_ENV_CONFIG_FOR_AUTH_BROKER",
|
|
1447
|
+
defaultMcpDestination: this.defaultMcpDestination,
|
|
1448
|
+
useAuthBroker,
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
else {
|
|
1452
|
+
// Try to get config from .env, but don't fail if it's invalid - server should still initialize
|
|
1453
|
+
// Invalid config will be caught when handlers try to use it
|
|
1454
|
+
try {
|
|
1455
|
+
this.sapConfig = (0, config_js_1.getConfig)();
|
|
1456
|
+
}
|
|
1457
|
+
catch (configError) {
|
|
1458
|
+
// For stdio mode, we want the server to initialize even with invalid config
|
|
1459
|
+
// The error will be shown when user tries to use a tool
|
|
1460
|
+
// Check stdio mode using environment variable or stdin check (transportConfig not set yet)
|
|
1461
|
+
const isStdioMode = process.env.MCP_TRANSPORT === "stdio" || !process.stdin.isTTY;
|
|
1462
|
+
if (isStdioMode) {
|
|
1463
|
+
// In stdio mode, write error to stderr (safe for MCP protocol)
|
|
1464
|
+
process.stderr.write(`[MCP] ⚠ WARNING: Invalid SAP configuration: ${configError instanceof Error ? configError.message : String(configError)}\n`);
|
|
1465
|
+
process.stderr.write(`[MCP] Server will start, but tools will fail until configuration is fixed.\n`);
|
|
1466
|
+
}
|
|
1467
|
+
logger_1.logger?.warn("SAP config invalid at initialization, will use placeholder", {
|
|
1468
|
+
type: "CONFIG_INVALID",
|
|
1469
|
+
error: configError instanceof Error ? configError.message : String(configError),
|
|
1470
|
+
});
|
|
1471
|
+
// Set a placeholder that will be replaced when valid config is provided
|
|
1472
|
+
this.sapConfig = { url: "http://placeholder", authType: "jwt", jwtToken: "placeholder" };
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
else {
|
|
1477
|
+
this.sapConfig = { url: "http://injected-connection", authType: "jwt", jwtToken: "injected" };
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
catch (error) {
|
|
1481
|
+
// If config is not available yet, that's OK - it will be provided later via setSapConfigOverride or DI
|
|
1482
|
+
logger_1.logger?.warn("SAP config not available at initialization, will use runtime config", {
|
|
1483
|
+
type: "CONFIG_DEFERRED",
|
|
1484
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1485
|
+
});
|
|
1486
|
+
// Set a placeholder that will be replaced
|
|
1487
|
+
this.sapConfig = { url: "http://placeholder", authType: "jwt", jwtToken: "placeholder" };
|
|
1488
|
+
}
|
|
1489
|
+
try {
|
|
1490
|
+
this.transportConfig = options?.transportConfig ?? parseTransportConfig();
|
|
1491
|
+
}
|
|
1492
|
+
catch (error) {
|
|
1493
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1494
|
+
// Always write error to stderr (stderr is safe even in stdio mode)
|
|
1495
|
+
logger_1.logger?.error("Failed to parse transport configuration", {
|
|
1496
|
+
type: "TRANSPORT_CONFIG_ERROR",
|
|
1497
|
+
error: message,
|
|
1498
|
+
});
|
|
1499
|
+
process.stderr.write(`[MCP] ✗ ERROR: Failed to parse transport configuration: ${message}\n`);
|
|
1500
|
+
if (this.allowProcessExit) {
|
|
1501
|
+
// On Windows, add a small delay before exit to allow error message to be visible
|
|
1502
|
+
if (process.platform === 'win32') {
|
|
1503
|
+
setTimeout(() => process.exit(1), 100);
|
|
1504
|
+
}
|
|
1505
|
+
else {
|
|
1506
|
+
process.exit(1);
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
throw error instanceof Error ? error : new Error(message);
|
|
1510
|
+
}
|
|
1511
|
+
// Create McpServer (for all transports)
|
|
1512
|
+
this.mcpServer = new mcp_js_1.McpServer({
|
|
1513
|
+
name: "mcp-abap-adt",
|
|
1514
|
+
version: "0.1.0"
|
|
1515
|
+
});
|
|
1516
|
+
// AuthBroker will be initialized lazily when needed (per destination)
|
|
1517
|
+
// Only for HTTP/streamable-http transport (not for stdio or SSE)
|
|
1518
|
+
const isHttpTransport = this.transportConfig.type === "streamable-http";
|
|
1519
|
+
if (isHttpTransport && !useAuthBroker) {
|
|
1520
|
+
// Only initialize if --auth-broker flag is NOT set (for stdio/SSE, ignore the flag)
|
|
1521
|
+
// For stdio/SSE, --auth-broker flag is ignored
|
|
1522
|
+
}
|
|
1523
|
+
else if (isHttpTransport && useAuthBroker) {
|
|
1524
|
+
// Support DEBUG_AUTH_BROKER as alias for DEBUG_AUTH_LOG
|
|
1525
|
+
// If DEBUG_AUTH_BROKER is set, ensure DEBUG_AUTH_LOG is also set for auth-broker package
|
|
1526
|
+
if (process.env.DEBUG_AUTH_BROKER === "true" && !process.env.DEBUG_AUTH_LOG) {
|
|
1527
|
+
process.env.DEBUG_AUTH_LOG = "true";
|
|
1528
|
+
}
|
|
1529
|
+
// Get paths for service keys and sessions
|
|
1530
|
+
// Use authBrokerPath if provided, otherwise use default platform paths
|
|
1531
|
+
// If authBrokerPath is provided, it's the base path - service-keys and sessions will be subdirectories
|
|
1532
|
+
const customPath = authBrokerPath ? path_1.default.resolve(authBrokerPath.replace(/^~/, os.homedir())) : undefined;
|
|
1533
|
+
const serviceKeysPaths = (0, platformPaths_1.getPlatformPaths)(customPath, 'service-keys');
|
|
1534
|
+
const sessionsPaths = (0, platformPaths_1.getPlatformPaths)(customPath, 'sessions');
|
|
1535
|
+
// Create directories if they don't exist
|
|
1536
|
+
const fs = require('fs');
|
|
1537
|
+
const serviceKeysDir = serviceKeysPaths[0]; // First path is where we save
|
|
1538
|
+
const sessionsDir = sessionsPaths[0]; // First path is where we save
|
|
1539
|
+
if (!fs.existsSync(serviceKeysDir)) {
|
|
1540
|
+
fs.mkdirSync(serviceKeysDir, { recursive: true });
|
|
1541
|
+
logger_1.logger?.info("Created service keys directory", {
|
|
1542
|
+
type: "SERVICE_KEYS_DIR_CREATED",
|
|
1543
|
+
path: serviceKeysDir,
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
if (!fs.existsSync(sessionsDir)) {
|
|
1547
|
+
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
1548
|
+
logger_1.logger?.info("Created sessions directory", {
|
|
1549
|
+
type: "SESSIONS_DIR_CREATED",
|
|
1550
|
+
path: sessionsDir,
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
logger_1.logger?.info("AuthBroker will be initialized lazily when destination is needed", {
|
|
1554
|
+
type: "AUTH_BROKER_LAZY_INIT",
|
|
1555
|
+
transport: this.transportConfig.type,
|
|
1556
|
+
useAuthBrokerFlag: useAuthBroker,
|
|
1557
|
+
hasEnvFile: !!envFilePath,
|
|
1558
|
+
authBrokerPath: customPath || 'default',
|
|
1559
|
+
});
|
|
1560
|
+
// Print paths information with stars and empty lines
|
|
1561
|
+
console.log('');
|
|
1562
|
+
console.log('********************************************************************************');
|
|
1563
|
+
console.log('* AuthBroker Storage Paths:');
|
|
1564
|
+
console.log('*');
|
|
1565
|
+
console.log('* Service Keys (searched in order):');
|
|
1566
|
+
serviceKeysPaths.forEach((p, i) => {
|
|
1567
|
+
console.log(`* ${i + 1}. ${p}`);
|
|
1568
|
+
});
|
|
1569
|
+
console.log('*');
|
|
1570
|
+
console.log('* Sessions (saved to):');
|
|
1571
|
+
sessionsPaths.forEach((p, i) => {
|
|
1572
|
+
console.log(`* ${i + 1}. ${p}`);
|
|
1573
|
+
});
|
|
1574
|
+
console.log('********************************************************************************');
|
|
1575
|
+
console.log('');
|
|
1576
|
+
}
|
|
1577
|
+
else {
|
|
1578
|
+
logger_1.logger?.info("AuthBroker not available - not needed for this transport type", {
|
|
1579
|
+
type: "AUTH_BROKER_SKIPPED",
|
|
1580
|
+
transport: this.transportConfig.type,
|
|
1581
|
+
reason: "AuthBroker is only used for HTTP/streamable-http transport. For stdio/SSE, use .env file instead.",
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
if (this.transportConfig.type === "streamable-http") {
|
|
1585
|
+
logger_1.logger?.info("Transport configured", {
|
|
1586
|
+
type: "TRANSPORT_CONFIG",
|
|
1587
|
+
transport: this.transportConfig.type,
|
|
1588
|
+
host: this.transportConfig.host,
|
|
1589
|
+
port: this.transportConfig.port,
|
|
1590
|
+
enableJsonResponse: this.transportConfig.enableJsonResponse,
|
|
1591
|
+
allowedOrigins: this.transportConfig.allowedOrigins ?? [],
|
|
1592
|
+
allowedHosts: this.transportConfig.allowedHosts ?? [],
|
|
1593
|
+
enableDnsRebindingProtection: this.transportConfig.enableDnsRebindingProtection,
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
else if (this.transportConfig.type === "sse") {
|
|
1597
|
+
logger_1.logger?.info("Transport configured", {
|
|
1598
|
+
type: "TRANSPORT_CONFIG",
|
|
1599
|
+
transport: this.transportConfig.type,
|
|
1600
|
+
host: this.transportConfig.host,
|
|
1601
|
+
port: this.transportConfig.port,
|
|
1602
|
+
allowedOrigins: this.transportConfig.allowedOrigins ?? [],
|
|
1603
|
+
allowedHosts: this.transportConfig.allowedHosts ?? [],
|
|
1604
|
+
enableDnsRebindingProtection: this.transportConfig.enableDnsRebindingProtection,
|
|
1605
|
+
});
|
|
1606
|
+
}
|
|
1607
|
+
else {
|
|
1608
|
+
logger_1.logger?.info("Transport configured", {
|
|
1609
|
+
type: "TRANSPORT_CONFIG",
|
|
1610
|
+
transport: this.transportConfig.type,
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
// Register handlers immediately if context is provided (for embedding scenarios)
|
|
1614
|
+
if (options?.context) {
|
|
1615
|
+
this.registerHandlers(options.context);
|
|
1616
|
+
logger_1.logger?.info("Handlers registered via context injection", {
|
|
1617
|
+
type: "HANDLERS_REGISTERED_VIA_CONTEXT",
|
|
1618
|
+
exposition: this.exposition,
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Creates AbapConnection from available sources (headers, broker, or .env)
|
|
1624
|
+
* @param headers - Optional HTTP headers containing connection info
|
|
1625
|
+
* @param sessionId - Optional session ID
|
|
1626
|
+
* @param destination - Optional destination name for broker-based auth
|
|
1627
|
+
* @returns AbapConnection instance
|
|
1628
|
+
* @private
|
|
1629
|
+
*/
|
|
1630
|
+
async getOrCreateConnectionForServer(headers, sessionId, destination) {
|
|
1631
|
+
// Extract destination from headers if not provided
|
|
1632
|
+
let actualDestination = destination;
|
|
1633
|
+
if (!actualDestination && headers) {
|
|
1634
|
+
actualDestination = headers[interfaces_1.HEADER_MCP_DESTINATION] ||
|
|
1635
|
+
headers['X-MCP-Destination'] ||
|
|
1636
|
+
headers['x-mcp-destination'];
|
|
1637
|
+
}
|
|
1638
|
+
// Use default destination if still not set
|
|
1639
|
+
if (!actualDestination && this.defaultDestination) {
|
|
1640
|
+
actualDestination = this.defaultDestination;
|
|
1641
|
+
logger_1.logger?.debug('Using default destination for connection', {
|
|
1642
|
+
destination: actualDestination,
|
|
1643
|
+
type: 'DEFAULT_DESTINATION_USED',
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
1646
|
+
logger_1.logger?.info("Creating connection for server", {
|
|
1647
|
+
type: "CONNECTION_CREATION_START",
|
|
1648
|
+
hasHeaders: !!headers,
|
|
1649
|
+
sessionId: sessionId || 'not-provided',
|
|
1650
|
+
destination: actualDestination || 'not-provided',
|
|
1651
|
+
});
|
|
1652
|
+
// Try to get connection from session context first
|
|
1653
|
+
const context = utils_1.sessionContext.getStore();
|
|
1654
|
+
if (context?.sapConfig) {
|
|
1655
|
+
logger_1.logger?.info("Using connection from session context", {
|
|
1656
|
+
type: "CONNECTION_FROM_SESSION_CONTEXT",
|
|
1657
|
+
sessionId: sessionId || 'not-provided',
|
|
1658
|
+
url: context.sapConfig.url,
|
|
1659
|
+
authType: context.sapConfig.authType,
|
|
1660
|
+
});
|
|
1661
|
+
const sessionConnection = (0, connection_1.createAbapConnection)(context.sapConfig, loggerAdapter_1.loggerAdapter, sessionId || `mcp-server-${(0, crypto_1.randomUUID)()}`);
|
|
1662
|
+
return sessionConnection;
|
|
1663
|
+
}
|
|
1664
|
+
// If headers provided, create connection from headers
|
|
1665
|
+
if (headers && this.hasSapHeaders(headers)) {
|
|
1666
|
+
// Get config from headers (already processed by applyAuthHeaders)
|
|
1667
|
+
const config = this.sapConfig;
|
|
1668
|
+
if (config && config.url !== "http://placeholder" && config.url !== "http://injected-connection") {
|
|
1669
|
+
// If destination is known, try to refresh token via auth broker before creating connection
|
|
1670
|
+
if (actualDestination) {
|
|
1671
|
+
try {
|
|
1672
|
+
const brokerForHeaders = await this.getOrCreateAuthBroker(actualDestination, sessionId || 'global');
|
|
1673
|
+
if (brokerForHeaders) {
|
|
1674
|
+
const freshToken = await brokerForHeaders.getToken(actualDestination);
|
|
1675
|
+
if (freshToken && config) {
|
|
1676
|
+
config.authorizationToken = freshToken;
|
|
1677
|
+
}
|
|
1678
|
+
const { registerAuthBroker } = require('../../lib/utils.js');
|
|
1679
|
+
registerAuthBroker(actualDestination, brokerForHeaders);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
catch (e) {
|
|
1683
|
+
logger_1.logger?.warn?.("Auth broker refresh failed for header connection", {
|
|
1684
|
+
type: "CONNECTION_FROM_HEADERS_BROKER_WARN",
|
|
1685
|
+
error: e instanceof Error ? e.message : String(e),
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
logger_1.logger?.info("Using connection from headers", {
|
|
1690
|
+
type: "CONNECTION_FROM_HEADERS",
|
|
1691
|
+
sessionId: sessionId || 'not-provided',
|
|
1692
|
+
url: config.url,
|
|
1693
|
+
authType: config.authType,
|
|
1694
|
+
});
|
|
1695
|
+
let connection = (0, connection_1.createAbapConnection)(config, loggerAdapter_1.loggerAdapter, sessionId || `mcp-server-${(0, crypto_1.randomUUID)()}`);
|
|
1696
|
+
return connection;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
// If destination provided, create connection via broker
|
|
1700
|
+
if (actualDestination) {
|
|
1701
|
+
try {
|
|
1702
|
+
logger_1.logger?.info("Attempting to create connection via auth broker", {
|
|
1703
|
+
type: "CONNECTION_VIA_BROKER_ATTEMPT",
|
|
1704
|
+
destination: actualDestination,
|
|
1705
|
+
sessionId: sessionId || 'not-provided',
|
|
1706
|
+
});
|
|
1707
|
+
const authBroker = await this.getOrCreateAuthBroker(actualDestination, sessionId || 'global');
|
|
1708
|
+
console.error("[DEBUG v1] authBroker result:", { hasBroker: !!authBroker, destination: actualDestination });
|
|
1709
|
+
if (authBroker) {
|
|
1710
|
+
const connConfig = await authBroker.getConnectionConfig(actualDestination);
|
|
1711
|
+
console.error("[DEBUG v1] connConfig result:", {
|
|
1712
|
+
hasConfig: !!connConfig,
|
|
1713
|
+
serviceUrl: connConfig?.serviceUrl?.substring(0, 50),
|
|
1714
|
+
authType: connConfig?.authType,
|
|
1715
|
+
hasToken: !!connConfig?.authorizationToken,
|
|
1716
|
+
hasUsername: !!connConfig?.username,
|
|
1717
|
+
});
|
|
1718
|
+
if (connConfig?.serviceUrl) {
|
|
1719
|
+
// Register AuthBroker in global registry for connection to use during token refresh
|
|
1720
|
+
const { registerAuthBroker } = require('../../lib/utils.js');
|
|
1721
|
+
registerAuthBroker(actualDestination, authBroker);
|
|
1722
|
+
// Determine auth type from connection config (like v2)
|
|
1723
|
+
const authType = connConfig.authType ||
|
|
1724
|
+
(connConfig.username && connConfig.password ? 'basic' : 'jwt');
|
|
1725
|
+
let config;
|
|
1726
|
+
if (authType === 'basic') {
|
|
1727
|
+
// Basic auth - use username/password from connection config
|
|
1728
|
+
config = {
|
|
1729
|
+
url: connConfig.serviceUrl,
|
|
1730
|
+
authType: "basic",
|
|
1731
|
+
username: connConfig.username,
|
|
1732
|
+
password: connConfig.password,
|
|
1733
|
+
client: connConfig.sapClient,
|
|
1734
|
+
};
|
|
1735
|
+
logger_1.logger?.info("Using basic auth connection from auth broker", {
|
|
1736
|
+
type: "CONNECTION_FROM_BROKER_BASIC",
|
|
1737
|
+
destination: actualDestination,
|
|
1738
|
+
sessionId: sessionId || 'not-provided',
|
|
1739
|
+
url: config.url,
|
|
1740
|
+
authType: config.authType,
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
else {
|
|
1744
|
+
// JWT auth - try to get fresh token from broker
|
|
1745
|
+
let jwtToken;
|
|
1746
|
+
try {
|
|
1747
|
+
jwtToken = await authBroker.getToken(actualDestination);
|
|
1748
|
+
}
|
|
1749
|
+
catch (error) {
|
|
1750
|
+
// Broker can't provide/refresh token (e.g., no UAA credentials for .env-only setup)
|
|
1751
|
+
// Use existing token from connectionConfig
|
|
1752
|
+
logger_1.logger?.debug?.("Broker can't refresh token, using existing token from session", {
|
|
1753
|
+
type: "TOKEN_REFRESH_FALLBACK",
|
|
1754
|
+
destination: actualDestination,
|
|
1755
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1756
|
+
});
|
|
1757
|
+
jwtToken = connConfig.authorizationToken;
|
|
1758
|
+
}
|
|
1759
|
+
const tokenToUse = jwtToken || connConfig.authorizationToken;
|
|
1760
|
+
if (!tokenToUse) {
|
|
1761
|
+
logger_1.logger?.warn("No JWT token available for connection", {
|
|
1762
|
+
type: "NO_JWT_TOKEN",
|
|
1763
|
+
destination: actualDestination,
|
|
1764
|
+
});
|
|
1765
|
+
throw new Error(`No JWT token available for destination: ${actualDestination}`);
|
|
1766
|
+
}
|
|
1767
|
+
config = {
|
|
1768
|
+
url: connConfig.serviceUrl,
|
|
1769
|
+
authType: "jwt",
|
|
1770
|
+
jwtToken: tokenToUse,
|
|
1771
|
+
client: connConfig.sapClient,
|
|
1772
|
+
};
|
|
1773
|
+
logger_1.logger?.info("Using JWT auth connection from auth broker", {
|
|
1774
|
+
type: "CONNECTION_FROM_BROKER_JWT",
|
|
1775
|
+
destination: actualDestination,
|
|
1776
|
+
sessionId: sessionId || 'not-provided',
|
|
1777
|
+
url: config.url,
|
|
1778
|
+
authType: config.authType,
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
// Create connection
|
|
1782
|
+
let connection = (0, connection_1.createAbapConnection)(config, loggerAdapter_1.loggerAdapter, sessionId || `mcp-server-${(0, crypto_1.randomUUID)()}`);
|
|
1783
|
+
return connection;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
catch (error) {
|
|
1788
|
+
logger_1.logger?.warn("Failed to create connection from destination", {
|
|
1789
|
+
type: "CONNECTION_FROM_DESTINATION_FAILED",
|
|
1790
|
+
destination: actualDestination,
|
|
1791
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
// Fallback to default config (from .env or constructor)
|
|
1796
|
+
// BUT: Don't use .env fallback if we have defaultDestination (means we're using auth-broker)
|
|
1797
|
+
// This prevents .env from being used when --mcp is specified
|
|
1798
|
+
if (this.sapConfig &&
|
|
1799
|
+
this.sapConfig.url !== "http://placeholder" &&
|
|
1800
|
+
this.sapConfig.url !== "http://injected-connection" &&
|
|
1801
|
+
!this.defaultDestination) { // Don't fallback to .env if using auth-broker
|
|
1802
|
+
logger_1.logger?.info("Using connection from default config (.env or constructor)", {
|
|
1803
|
+
type: "CONNECTION_FROM_DEFAULT_CONFIG",
|
|
1804
|
+
sessionId: sessionId || 'not-provided',
|
|
1805
|
+
url: this.sapConfig.url,
|
|
1806
|
+
authType: this.sapConfig.authType,
|
|
1807
|
+
});
|
|
1808
|
+
return (0, connection_1.createAbapConnection)(this.sapConfig, loggerAdapter_1.loggerAdapter, sessionId || `mcp-server-${(0, crypto_1.randomUUID)()}`);
|
|
1809
|
+
}
|
|
1810
|
+
logger_1.logger?.error("Unable to create connection: no valid configuration available", {
|
|
1811
|
+
type: "CONNECTION_CREATION_FAILED",
|
|
1812
|
+
hasHeaders: !!headers,
|
|
1813
|
+
sessionId: sessionId || 'not-provided',
|
|
1814
|
+
destination: actualDestination || 'not-provided',
|
|
1815
|
+
defaultDestination: this.defaultDestination || 'not-set',
|
|
1816
|
+
hasSapConfig: !!this.sapConfig,
|
|
1817
|
+
sapConfigUrl: this.sapConfig?.url || 'not-set',
|
|
1818
|
+
});
|
|
1819
|
+
throw new Error("Unable to create connection: no valid configuration available");
|
|
1820
|
+
}
|
|
1821
|
+
/**
|
|
1822
|
+
* Creates a new McpServer instance with all handlers registered
|
|
1823
|
+
* Used for SSE sessions where each session needs its own server instance
|
|
1824
|
+
* @param context - HandlerContext instance to use for handlers
|
|
1825
|
+
* @private
|
|
1826
|
+
*/
|
|
1827
|
+
createMcpServerForSession(context) {
|
|
1828
|
+
const server = new mcp_js_1.McpServer({
|
|
1829
|
+
name: "mcp-abap-adt",
|
|
1830
|
+
version: "0.1.0"
|
|
1831
|
+
});
|
|
1832
|
+
// Register all tools using McpHandlers
|
|
1833
|
+
const handlers = new mcp_handlers_1.McpHandlers();
|
|
1834
|
+
handlers.RegisterAllToolsOnServer(server, context, this.exposition);
|
|
1835
|
+
return server;
|
|
1836
|
+
}
|
|
1837
|
+
/**
|
|
1838
|
+
* Register handlers on the internal McpServer with a provided context.
|
|
1839
|
+
* Use this when embedding mcp_abap_adt_server in an external server
|
|
1840
|
+
* that manages its own connection lifecycle.
|
|
1841
|
+
*
|
|
1842
|
+
* @param context - HandlerContext with connection and logger
|
|
1843
|
+
* @public
|
|
1844
|
+
*/
|
|
1845
|
+
registerHandlers(context) {
|
|
1846
|
+
this.mcpHandlers.RegisterAllToolsOnServer(this.mcpServer, context, this.exposition);
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Sets up handlers for new McpServer using registerTool (recommended API)
|
|
1850
|
+
* @param context - HandlerContext with connection and logger
|
|
1851
|
+
* @private
|
|
1852
|
+
*/
|
|
1853
|
+
setupMcpServerHandlers(context) {
|
|
1854
|
+
// Connection is already wrapped with token refresh in run() method
|
|
1855
|
+
// Just register handlers with the provided context
|
|
1856
|
+
this.mcpHandlers.RegisterAllToolsOnServer(this.mcpServer, context, this.exposition);
|
|
1857
|
+
}
|
|
1858
|
+
/**
|
|
1859
|
+
* Creates a wrapper connection that refreshes token before each request
|
|
1860
|
+
* @private
|
|
1861
|
+
*/
|
|
1862
|
+
createConnectionWithTokenRefresh(connection, destination) {
|
|
1863
|
+
const connectionWithRefresh = connection;
|
|
1864
|
+
// Wrap makeAdtRequest to refresh token before each request
|
|
1865
|
+
if (connectionWithRefresh.makeAdtRequest) {
|
|
1866
|
+
const originalMakeAdtRequest = connectionWithRefresh.makeAdtRequest.bind(connection);
|
|
1867
|
+
connectionWithRefresh.makeAdtRequest = async function (options) {
|
|
1868
|
+
// Always refresh token via AuthBroker before request (AuthBroker will refresh if needed)
|
|
1869
|
+
const { getAuthBroker } = require('../../lib/utils.js');
|
|
1870
|
+
const authBroker = getAuthBroker(destination);
|
|
1871
|
+
logger_1.logger?.debug('makeAdtRequest called, checking AuthBroker', {
|
|
1872
|
+
destination,
|
|
1873
|
+
hasAuthBroker: !!authBroker,
|
|
1874
|
+
method: options?.method,
|
|
1875
|
+
url: options?.url,
|
|
1876
|
+
});
|
|
1877
|
+
if (!authBroker) {
|
|
1878
|
+
logger_1.logger?.warn('AuthBroker not found for destination, cannot refresh token', {
|
|
1879
|
+
destination,
|
|
1880
|
+
type: 'AUTH_BROKER_NOT_FOUND',
|
|
1881
|
+
});
|
|
1882
|
+
// Continue without refresh - will use existing token
|
|
1883
|
+
return await originalMakeAdtRequest(options);
|
|
1884
|
+
}
|
|
1885
|
+
try {
|
|
1886
|
+
// Get fresh token from AuthBroker (will refresh automatically if expired)
|
|
1887
|
+
logger_1.logger?.debug('Requesting fresh token from AuthBroker', {
|
|
1888
|
+
destination,
|
|
1889
|
+
type: 'TOKEN_REFRESH_REQUEST',
|
|
1890
|
+
});
|
|
1891
|
+
const freshToken = await authBroker.getToken(destination);
|
|
1892
|
+
const config = connectionWithRefresh.getConfig();
|
|
1893
|
+
if (config) {
|
|
1894
|
+
// Always update token (AuthBroker.getToken() returns fresh token)
|
|
1895
|
+
const tokenChanged = config.jwtToken !== freshToken;
|
|
1896
|
+
const oldToken = config.jwtToken;
|
|
1897
|
+
logger_1.logger?.debug('Updating connection config with fresh token', {
|
|
1898
|
+
destination,
|
|
1899
|
+
tokenChanged,
|
|
1900
|
+
oldTokenPreview: oldToken ? oldToken.substring(0, 20) + '...' : 'none',
|
|
1901
|
+
newTokenPreview: freshToken.substring(0, 20) + '...',
|
|
1902
|
+
type: 'TOKEN_UPDATE',
|
|
1903
|
+
});
|
|
1904
|
+
config.jwtToken = freshToken;
|
|
1905
|
+
// Verify token was updated
|
|
1906
|
+
const verifyConfig = connectionWithRefresh.getConfig();
|
|
1907
|
+
if (verifyConfig.jwtToken !== freshToken) {
|
|
1908
|
+
logger_1.logger?.error('Token update failed - config.jwtToken does not match fresh token', {
|
|
1909
|
+
destination,
|
|
1910
|
+
expectedPreview: freshToken.substring(0, 20) + '...',
|
|
1911
|
+
actualPreview: verifyConfig.jwtToken ? verifyConfig.jwtToken.substring(0, 20) + '...' : 'none',
|
|
1912
|
+
type: 'TOKEN_UPDATE_FAILED',
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
// Get refresh token from session store if available
|
|
1916
|
+
try {
|
|
1917
|
+
const { authorizationConfig } = await authBroker.getConnectionConfig(destination);
|
|
1918
|
+
if (authorizationConfig?.refreshToken) {
|
|
1919
|
+
config.refreshToken = authorizationConfig.refreshToken;
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
catch (configError) {
|
|
1923
|
+
// Ignore errors getting refresh token (not critical)
|
|
1924
|
+
logger_1.logger?.debug('Could not get refresh token from AuthBroker', {
|
|
1925
|
+
destination,
|
|
1926
|
+
error: configError instanceof Error ? configError.message : String(configError),
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1929
|
+
// Reset connection only if token changed to ensure fresh token is used
|
|
1930
|
+
// This ensures that buildAuthorizationHeader() will use the new token
|
|
1931
|
+
// Don't reset if token didn't change to preserve CSRF token and cookies
|
|
1932
|
+
if (tokenChanged) {
|
|
1933
|
+
connectionWithRefresh.reset();
|
|
1934
|
+
logger_1.logger?.info('Token refreshed via AuthBroker before request', {
|
|
1935
|
+
destination,
|
|
1936
|
+
tokenPreview: freshToken.substring(0, 20) + '...',
|
|
1937
|
+
oldTokenPreview: oldToken ? oldToken.substring(0, 20) + '...' : 'none',
|
|
1938
|
+
type: 'TOKEN_REFRESHED_BEFORE_REQUEST',
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
else {
|
|
1942
|
+
// Token didn't change - no need to reset, connection can reuse CSRF token and cookies
|
|
1943
|
+
logger_1.logger?.debug('Token verified via AuthBroker before request (no change, keeping session)', {
|
|
1944
|
+
destination,
|
|
1945
|
+
tokenPreview: freshToken.substring(0, 20) + '...',
|
|
1946
|
+
type: 'TOKEN_VERIFIED_BEFORE_REQUEST',
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
else {
|
|
1951
|
+
logger_1.logger?.warn('Connection config not available, cannot update token', {
|
|
1952
|
+
destination,
|
|
1953
|
+
type: 'CONNECTION_CONFIG_NOT_AVAILABLE',
|
|
1954
|
+
});
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
catch (error) {
|
|
1958
|
+
logger_1.logger?.error('Failed to refresh token before request', {
|
|
1959
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1960
|
+
destination,
|
|
1961
|
+
errorStack: error instanceof Error ? error.stack : undefined,
|
|
1962
|
+
type: 'TOKEN_REFRESH_FAILED',
|
|
1963
|
+
});
|
|
1964
|
+
// Continue with existing token - will try to use it, but may fail if expired
|
|
1965
|
+
}
|
|
1966
|
+
// Call original makeAdtRequest (with potentially refreshed token)
|
|
1967
|
+
try {
|
|
1968
|
+
return await originalMakeAdtRequest(options);
|
|
1969
|
+
}
|
|
1970
|
+
catch (error) {
|
|
1971
|
+
// If we still get 401/403 or "JWT token has expired" error, try to refresh token again
|
|
1972
|
+
const isAuthError = error?.response?.status === 401 || error?.response?.status === 403;
|
|
1973
|
+
const isExpiredTokenError = error?.message?.includes('JWT token has expired') ||
|
|
1974
|
+
error?.message?.includes('Please re-authenticate');
|
|
1975
|
+
if ((isAuthError || isExpiredTokenError) && authBroker) {
|
|
1976
|
+
// Check if this is a permissions error, not an auth error
|
|
1977
|
+
const responseData = error?.response?.data;
|
|
1978
|
+
const responseText = typeof responseData === "string" ? responseData : JSON.stringify(responseData || "");
|
|
1979
|
+
if (responseText.includes("ExceptionResourceNoAccess") ||
|
|
1980
|
+
responseText.includes("No authorization") ||
|
|
1981
|
+
responseText.includes("Missing authorization")) {
|
|
1982
|
+
// Not an auth token issue, re-throw
|
|
1983
|
+
throw error;
|
|
1984
|
+
}
|
|
1985
|
+
// Try to refresh token again and retry
|
|
1986
|
+
try {
|
|
1987
|
+
logger_1.logger?.info('Got 401/403 after token refresh, attempting to refresh token again', {
|
|
1988
|
+
destination,
|
|
1989
|
+
error: error?.message,
|
|
1990
|
+
type: 'TOKEN_REFRESH_RETRY',
|
|
1991
|
+
});
|
|
1992
|
+
const freshToken = await authBroker.getToken(destination);
|
|
1993
|
+
const config = connectionWithRefresh.getConfig();
|
|
1994
|
+
if (config) {
|
|
1995
|
+
config.jwtToken = freshToken;
|
|
1996
|
+
// Get refresh token from session store if available
|
|
1997
|
+
try {
|
|
1998
|
+
const { authorizationConfig } = await authBroker.getConnectionConfig(destination);
|
|
1999
|
+
if (authorizationConfig?.refreshToken) {
|
|
2000
|
+
config.refreshToken = authorizationConfig.refreshToken;
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
catch (configError) {
|
|
2004
|
+
// Ignore errors getting refresh token
|
|
2005
|
+
}
|
|
2006
|
+
// Reset connection to use new token
|
|
2007
|
+
connectionWithRefresh.reset();
|
|
2008
|
+
logger_1.logger?.info('Token refreshed again, retrying request', {
|
|
2009
|
+
destination,
|
|
2010
|
+
tokenPreview: freshToken.substring(0, 20) + '...',
|
|
2011
|
+
type: 'TOKEN_REFRESHED_RETRY',
|
|
2012
|
+
});
|
|
2013
|
+
// Retry the request with new token
|
|
2014
|
+
return await originalMakeAdtRequest(options);
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
catch (refreshError) {
|
|
2018
|
+
logger_1.logger?.error('Failed to refresh token on retry', {
|
|
2019
|
+
destination,
|
|
2020
|
+
error: refreshError instanceof Error ? refreshError.message : String(refreshError),
|
|
2021
|
+
type: 'TOKEN_REFRESH_RETRY_FAILED',
|
|
2022
|
+
});
|
|
2023
|
+
// Re-throw original error if refresh fails
|
|
2024
|
+
throw error;
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
// Re-throw error if not handled
|
|
2028
|
+
throw error;
|
|
2029
|
+
}
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
2032
|
+
return connectionWithRefresh;
|
|
2033
|
+
}
|
|
2034
|
+
setupSignalHandlers() {
|
|
2035
|
+
const signals = ["SIGINT", "SIGTERM"];
|
|
2036
|
+
for (const signal of signals) {
|
|
2037
|
+
process.on(signal, () => {
|
|
2038
|
+
if (this.shuttingDown) {
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
2041
|
+
this.shuttingDown = true;
|
|
2042
|
+
logger_1.logger?.info("Received shutdown signal", {
|
|
2043
|
+
type: "SERVER_SHUTDOWN_SIGNAL",
|
|
2044
|
+
signal,
|
|
2045
|
+
transport: this.transportConfig.type,
|
|
2046
|
+
});
|
|
2047
|
+
void this.shutdown().finally(() => {
|
|
2048
|
+
if (this.allowProcessExit) {
|
|
2049
|
+
process.exit(0);
|
|
2050
|
+
}
|
|
2051
|
+
});
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
async shutdown() {
|
|
2056
|
+
try {
|
|
2057
|
+
await this.mcpServer.close();
|
|
2058
|
+
}
|
|
2059
|
+
catch (error) {
|
|
2060
|
+
logger_1.logger?.error("Failed to close MCP server", {
|
|
2061
|
+
type: "SERVER_SHUTDOWN_ERROR",
|
|
2062
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
if (this.httpServer) {
|
|
2066
|
+
await new Promise((resolve) => {
|
|
2067
|
+
this.httpServer?.close((closeError) => {
|
|
2068
|
+
if (closeError) {
|
|
2069
|
+
logger_1.logger?.error("Failed to close HTTP server", {
|
|
2070
|
+
type: "HTTP_SERVER_SHUTDOWN_ERROR",
|
|
2071
|
+
error: closeError instanceof Error ? closeError.message : String(closeError),
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2074
|
+
resolve();
|
|
2075
|
+
});
|
|
2076
|
+
});
|
|
2077
|
+
this.httpServer = undefined;
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
exports.mcp_abap_adt_server = mcp_abap_adt_server;
|
|
2082
|
+
// if (process.env.MCP_SKIP_AUTO_START !== "true") {
|
|
2083
|
+
// const server = new mcp_abap_adt_server();
|
|
2084
|
+
// server.run().catch((error) => {
|
|
2085
|
+
// logger?.error("Fatal error while running MCP server", {
|
|
2086
|
+
// type: "SERVER_FATAL_ERROR",
|
|
2087
|
+
// error: error instanceof Error ? error.message : String(error),
|
|
2088
|
+
// });
|
|
2089
|
+
// // Always write to stderr (safe even in stdio mode)
|
|
2090
|
+
// process.stderr.write(`[MCP] ✗ Fatal error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
2091
|
+
// // On Windows, add a small delay before exit to allow error message to be visible
|
|
2092
|
+
// if (process.platform === 'win32') {
|
|
2093
|
+
// setTimeout(() => process.exit(1), 100);
|
|
2094
|
+
// } else {
|
|
2095
|
+
// process.exit(1);
|
|
2096
|
+
// }
|
|
2097
|
+
// });
|
|
2098
|
+
// }
|
|
2099
|
+
//# sourceMappingURL=legacy-server.js.map
|