diff --git a/MCPClient/Sources/stdioTransport/DataChannel+StdioProcess.swift b/MCPClient/Sources/stdioTransport/DataChannel+StdioProcess.swift index e3d83b2..8ed97fd 100644 --- a/MCPClient/Sources/stdioTransport/DataChannel+StdioProcess.swift +++ b/MCPClient/Sources/stdioTransport/DataChannel+StdioProcess.swift @@ -1,4 +1,3 @@ - import Foundation import JSONRPC import MCPInterface @@ -76,13 +75,15 @@ extension Transport { let process = Process() // In MacOS, zsh is the default since macOS Catalina 10.15.7. We can safely assume it is available. process.launchPath = "/bin/zsh" + if let executable = path(for: executable, env: env) { + // If executable is found directly, use user-provided env or current process env + process.environment = env ?? ProcessInfo.processInfo.environment let command = "\(executable) \(args.joined(separator: " "))" process.arguments = ["-c"] + [command] - process.environment = env ?? ProcessInfo.processInfo.environment } else { - // If we cannot locate the executable, try loading the default environment for zsh, as the current process might not have the correct PATH. - process.environment = try loadZshEnvironment() + // If we cannot locate the executable, try loading the default environment for zsh + process.environment = try loadZshEnvironment(userEnv: env) let command = "\(executable) \(args.joined(separator: " "))" process.arguments = ["-c"] + [command] } @@ -208,19 +209,41 @@ extension Transport { return executablePath } - private static func loadZshEnvironment() throws -> [String: String] { - let process = Process() - process.launchPath = "/bin/zsh" - // Those are loaded for interactive login shell by zsh: - // https://www.freecodecamp.org/news/how-do-zsh-configuration-files-work/ - process.arguments = ["-c", "source ~/.zshenv; source ~/.zprofile; source ~/.zshrc; source ~/.zshrc; printenv"] - let env = try getProcessStdout(process: process) + private static func loadZshEnvironment(userEnv: [String: String]? = nil) throws -> [String: String] { + // Load shell environment as base + let shellProcess = Process() + shellProcess.executableURL = URL(fileURLWithPath: "/bin/zsh") - if let path = env?.split(separator: "\n").filter({ $0.starts(with: "PATH=") }).last { - return ["PATH": String(path.dropFirst("PATH=".count))] + // Set process environment - either use userEnv if it exists and isn't empty, or use system environment + if let env = userEnv, !env.isEmpty { + shellProcess.environment = env } else { + shellProcess.environment = ProcessInfo.processInfo.environment + } + + shellProcess.arguments = ["-ilc", "printenv"] + + let outputPipe = Pipe() + shellProcess.standardOutput = outputPipe + shellProcess.standardError = Pipe() + + try shellProcess.run() + shellProcess.waitUntilExit() + + let data = outputPipe.fileHandleForReading.readDataToEndOfFile() + guard let outputString = String(data: data, encoding: .utf8) else { + logger.error("Failed to read environment from shell.") return ProcessInfo.processInfo.environment } + + // Parse shell environment + return outputString + .split(separator: "\n") + .reduce(into: [String: String]()) { result, line in + let components = line.split(separator: "=", maxSplits: 1) + guard components.count == 2 else { return } + result[String(components[0])] = String(components[1]) + } } private static func getProcessStdout(process: Process) throws -> String? { diff --git a/MCPClientChatDemo/MCPClientChat/MCPClientChat.xcodeproj/project.pbxproj b/MCPClientChatDemo/MCPClientChat/MCPClientChat.xcodeproj/project.pbxproj index 99d7087..9af0426 100644 --- a/MCPClientChatDemo/MCPClientChat/MCPClientChat.xcodeproj/project.pbxproj +++ b/MCPClientChatDemo/MCPClientChat/MCPClientChat.xcodeproj/project.pbxproj @@ -7,8 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 7B73E9F52D814ABE00BF0CD1 /* MCPClient in Frameworks */ = {isa = PBXBuildFile; productRef = 7B73E9F42D814ABE00BF0CD1 /* MCPClient */; }; 7BDD62DB2D764B3D00E18088 /* SwiftAnthropic in Frameworks */ = {isa = PBXBuildFile; productRef = 7BDD62DA2D764B3D00E18088 /* SwiftAnthropic */; }; - 7BDD62ED2D764B7C00E18088 /* MCPClient in Frameworks */ = {isa = PBXBuildFile; productRef = 7BDD62EC2D764B7C00E18088 /* MCPClient */; }; 7BDD644F2D7811BA00E18088 /* SwiftOpenAI in Frameworks */ = {isa = PBXBuildFile; productRef = 7BDD644E2D7811BA00E18088 /* SwiftOpenAI */; }; /* End PBXBuildFile section */ @@ -58,9 +58,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7BDD62ED2D764B7C00E18088 /* MCPClient in Frameworks */, 7BDD644F2D7811BA00E18088 /* SwiftOpenAI in Frameworks */, 7BDD62DB2D764B3D00E18088 /* SwiftAnthropic in Frameworks */, + 7B73E9F52D814ABE00BF0CD1 /* MCPClient in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -130,8 +130,8 @@ name = MCPClientChat; packageProductDependencies = ( 7BDD62DA2D764B3D00E18088 /* SwiftAnthropic */, - 7BDD62EC2D764B7C00E18088 /* MCPClient */, 7BDD644E2D7811BA00E18088 /* SwiftOpenAI */, + 7B73E9F42D814ABE00BF0CD1 /* MCPClient */, ); productName = MCPClientChat; productReference = 7BDD62A92D764A4A00E18088 /* MCPClientChat.app */; @@ -217,8 +217,8 @@ minimizedProjectReferenceProxies = 1; packageReferences = ( 7BDD62D72D764ADA00E18088 /* XCRemoteSwiftPackageReference "SwiftAnthropic" */, - 7BDD62D82D764B1800E18088 /* XCLocalSwiftPackageReference "../../../mcp-swift-sdk" */, 7BDD644D2D7811B300E18088 /* XCRemoteSwiftPackageReference "SwiftOpenAI" */, + 7B73E9F32D814ABE00BF0CD1 /* XCLocalSwiftPackageReference "../../../mcp-swift-sdk" */, ); preferredProjectObjectVersion = 77; productRefGroup = 7BDD62AA2D764A4A00E18088 /* Products */; @@ -576,7 +576,7 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 7BDD62D82D764B1800E18088 /* XCLocalSwiftPackageReference "../../../mcp-swift-sdk" */ = { + 7B73E9F32D814ABE00BF0CD1 /* XCLocalSwiftPackageReference "../../../mcp-swift-sdk" */ = { isa = XCLocalSwiftPackageReference; relativePath = "../../../mcp-swift-sdk"; }; @@ -602,16 +602,15 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 7B73E9F42D814ABE00BF0CD1 /* MCPClient */ = { + isa = XCSwiftPackageProductDependency; + productName = MCPClient; + }; 7BDD62DA2D764B3D00E18088 /* SwiftAnthropic */ = { isa = XCSwiftPackageProductDependency; package = 7BDD62D72D764ADA00E18088 /* XCRemoteSwiftPackageReference "SwiftAnthropic" */; productName = SwiftAnthropic; }; - 7BDD62EC2D764B7C00E18088 /* MCPClient */ = { - isa = XCSwiftPackageProductDependency; - package = 7BDD62D82D764B1800E18088 /* XCLocalSwiftPackageReference "../../../mcp-swift-sdk" */; - productName = MCPClient; - }; 7BDD644E2D7811BA00E18088 /* SwiftOpenAI */ = { isa = XCSwiftPackageProductDependency; package = 7BDD644D2D7811B300E18088 /* XCRemoteSwiftPackageReference "SwiftOpenAI" */; diff --git a/MCPClientChatDemo/MCPClientChat/MCPClientChat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/MCPClientChatDemo/MCPClientChat/MCPClientChat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ffdaaf7..bd90525 100644 --- a/MCPClientChatDemo/MCPClientChat/MCPClientChat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/MCPClientChatDemo/MCPClientChat/MCPClientChat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,12 +1,13 @@ { - "originHash" : "ea89bc09a647584f246cc2ccc1334b5e579f0d3a53f72aa83011d8c436a3060f", + "originHash" : "cce6852fb88fdb9dea165eaa0f67690c423dc6b8f55b0b5fc6ee4cb95709df9b", "pins" : [ { "identity" : "jsonrpc", "kind" : "remoteSourceControl", - "location" : "https://github.com/ChimeHQ/JSONRPC", + "location" : "https://github.com/gsabran/JSONRPC", "state" : { - "revision" : "ef61a695bafa0e07080dadac65a0c59b37880548" + "revision" : "56c936de4e4385287dc18f260557dbdf443a55bd", + "version" : "0.9.1" } }, {