Skip to content

Commit b90e5e1

Browse files
hubab1hubab1
authored andcommitted
fix(mcp): share app dependencies with stdio
1 parent ea843a0 commit b90e5e1

5 files changed

Lines changed: 328 additions & 14 deletions

File tree

OpenASO/App/AppServices.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ final class AppServices {
4040
let refreshCoordinator: RankingRefreshCoordinator
4141
let appDetailRefreshService: AppDetailRefreshService?
4242
let refreshProgressStore: AppRefreshProgressStore
43+
let mcpServerProvider: OpenASOMCPServerProvider
4344
let mcpServerController: OpenASOMCPServerController
4445
let keywordResearchProjectStore: KeywordResearchProjectStore?
4546
let keywordResearchProjectCopyService: KeywordResearchProjectCopyService?
@@ -383,9 +384,7 @@ final class AppServices {
383384
self.keywordResearchProjectCopyService = keywordResearchProjectCopyService
384385
self.keywordResearchRankingWorkflow = keywordResearchRankingWorkflow
385386
self.keywordResearchMetricsWorkflow = keywordResearchMetricsWorkflow
386-
self.mcpServerController = OpenASOMCPServerController(portProvider: {
387-
settingsStore.mcpServerPort
388-
}) {
387+
let mcpServerProvider = OpenASOMCPServerProvider {
389388
guard let backgroundModelStore, let keywordResearchProjectStore else {
390389
throw OpenASOError.providerUnavailable("OpenASO MCP needs an initialized workspace store.")
391390
}
@@ -417,6 +416,12 @@ final class AppServices {
417416
configuration: OpenASOMCPServerConfiguration(version: "1.5.0")
418417
).makeServer()
419418
}
419+
self.mcpServerProvider = mcpServerProvider
420+
self.mcpServerController = OpenASOMCPServerController(portProvider: {
421+
settingsStore.mcpServerPort
422+
}) {
423+
try await mcpServerProvider.makeServer()
424+
}
420425
self.backgroundModelStore = backgroundModelStore
421426
self.backgroundModelStoreRevision = backgroundModelStore == nil ? 0 : 1
422427
if let headlessRefreshService {

OpenASO/App/OpenASOApp.swift

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@ struct OpenASOApp: App {
1010
private let startupState: OpenASOStartupState
1111

1212
init() {
13+
let startupState = Self.makeStartupState()
1314
if Self.shouldRunMCPStdio {
14-
Self.runMCPStdioAndExit()
15+
switch startupState {
16+
case .ready(_, let services):
17+
Self.runMCPStdioAndExit(serverProvider: services.mcpServerProvider)
18+
case .storeUnavailable(let error):
19+
Self.exitMCPStdio(with: error.diagnosticReport)
20+
}
1521
}
1622

17-
startupState = Self.makeStartupState()
23+
self.startupState = startupState
1824
_launchAlert = State(initialValue: nil)
1925
}
2026

@@ -36,13 +42,13 @@ struct OpenASOApp: App {
3642
ProcessInfo.processInfo.arguments.contains("--mcp-stdio")
3743
}
3844

39-
private static func runMCPStdioAndExit() -> Never {
45+
private static func runMCPStdioAndExit(
46+
serverProvider: OpenASOMCPServerProvider
47+
) -> Never {
4048
Task.detached {
4149
let exitCode: Int32
4250
do {
43-
try await OpenASOMCPRuntime.runStdio(
44-
configuration: OpenASOMCPServerConfiguration(version: "1.5.0")
45-
)
51+
try await OpenASOMCPRuntime.runStdio(serverProvider: serverProvider)
4652
exitCode = 0
4753
} catch {
4854
let description = (error as? PersistentStoreError)?.diagnosticReport
@@ -56,6 +62,11 @@ struct OpenASOApp: App {
5662
dispatchMain()
5763
}
5864

65+
private static func exitMCPStdio(with diagnostic: String) -> Never {
66+
FileHandle.standardError.write(Data("OpenASO MCP server failed: \(diagnostic)\n".utf8))
67+
Foundation.exit(1)
68+
}
69+
5970
var body: some Scene {
6071
WindowGroup("OpenASO") {
6172
switch startupState {

OpenASO/Features/Settings/SettingsView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ struct SettingsView: View {
194194
} header: {
195195
Text("MCP Server")
196196
} footer: {
197-
Text("Starts a local loopback MCP HTTP server for agents that can connect to an already-running app. Stop the server before changing the port. Stdio MCP clients should still launch the OpenASOMCP command-line target directly.")
197+
Text("Starts a local loopback MCP HTTP server for agents that can connect to an already-running app. Stop the server before changing the port. Stdio MCP clients should launch the OpenASO app executable with --mcp-stdio; both transports use the same workspace and configured providers.")
198198
}
199199
}
200200

OpenASO/Services/MCP/OpenASOMCPRuntime.swift

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,55 @@ import Foundation
22
import MCP
33
import SwiftData
44

5+
struct OpenASOMCPServerProvider: Sendable {
6+
private let makeServerImplementation: @MainActor @Sendable () async throws -> Server
7+
8+
init(
9+
makeServer: @escaping @MainActor @Sendable () async throws -> Server
10+
) {
11+
self.makeServerImplementation = makeServer
12+
}
13+
14+
@MainActor
15+
func makeServer() async throws -> Server {
16+
try await makeServerImplementation()
17+
}
18+
}
19+
520
enum OpenASOMCPRuntime {
21+
static func run(
22+
serverProvider: OpenASOMCPServerProvider,
23+
transport: any Transport
24+
) async throws {
25+
let server = try await serverProvider.makeServer()
26+
do {
27+
try await server.start(transport: transport)
28+
await withTaskCancellationHandler {
29+
await server.waitUntilCompleted()
30+
} onCancel: {
31+
Task {
32+
await server.stop()
33+
}
34+
}
35+
await server.stop()
36+
try Task.checkCancellation()
37+
} catch {
38+
await server.stop()
39+
throw error
40+
}
41+
}
42+
43+
static func runStdio(
44+
serverProvider: OpenASOMCPServerProvider
45+
) async throws {
46+
try await run(
47+
serverProvider: serverProvider,
48+
transport: StdioTransport()
49+
)
50+
}
51+
52+
// Retained for the dormant standalone target. The shipped app's --mcp-stdio
53+
// path supplies AppServices.mcpServerProvider so HTTP and stdio share dependencies.
654
static func makeServer(
755
configuration: OpenASOMCPServerConfiguration = OpenASOMCPServerConfiguration()
856
) async throws -> Server {
@@ -62,9 +110,9 @@ enum OpenASOMCPRuntime {
62110
static func runStdio(
63111
configuration: OpenASOMCPServerConfiguration = OpenASOMCPServerConfiguration()
64112
) async throws {
65-
let server = try await makeServer(configuration: configuration)
66-
let transport = StdioTransport()
67-
try await server.start(transport: transport)
68-
await server.waitUntilCompleted()
113+
let serverProvider = OpenASOMCPServerProvider {
114+
try await makeServer(configuration: configuration)
115+
}
116+
try await runStdio(serverProvider: serverProvider)
69117
}
70118
}

0 commit comments

Comments
 (0)