Skip to content

Commit 18c9870

Browse files
committed
Pre-release 0.47.170
1 parent c19cbda commit 18c9870

File tree

5 files changed

+105
-79
lines changed

5 files changed

+105
-79
lines changed

Core/Sources/HostApp/ToolsSettings/MCPRegistry/MCPRegistryInstallation.swift

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,55 @@ public class MCPRegistryService: ObservableObject {
9090
public static let shared = MCPRegistryService()
9191
public static let apiVersion = "v0.1"
9292
@AppStorage(\.mcpRegistryBaseURL) var mcpRegistryBaseURL
93-
93+
@Published public private(set) var mcpRegistryEntries: [MCPRegistryEntry]?
94+
9495
private init() {}
9596

97+
/// Fetches the MCP registry allowlist from the language server and updates
98+
/// ``mcpRegistryEntries``. Safe to call from any view's `onAppear` –
99+
/// duplicate in-flight calls are coalesced via the `isRefreshing` flag.
100+
private var isRefreshing = false
101+
102+
public func refreshAllowlist() async {
103+
guard !isRefreshing else { return }
104+
isRefreshing = true
105+
defer { isRefreshing = false }
106+
107+
do {
108+
let service = try getService()
109+
110+
let authStatus = try await service.getXPCServiceAuthStatus()
111+
guard authStatus?.status == .loggedIn else {
112+
Logger.client.info("User not logged in, skipping MCP registry allowlist fetch")
113+
mcpRegistryEntries = nil
114+
return
115+
}
116+
117+
let result = try await service.getMCPRegistryAllowlist()
118+
119+
guard let result = result, !result.mcpRegistries.isEmpty else {
120+
if result == nil {
121+
Logger.client.error("Failed to get allowlist result")
122+
} else {
123+
mcpRegistryEntries = []
124+
}
125+
return
126+
}
127+
128+
if let firstRegistry = result.mcpRegistries.first {
129+
let entry = MCPRegistryEntry(
130+
url: firstRegistry.url,
131+
registryAccess: firstRegistry.registryAccess,
132+
owner: firstRegistry.owner
133+
)
134+
mcpRegistryEntries = [entry]
135+
Logger.client.info("Current MCP Registry Entry: \(entry)")
136+
}
137+
} catch {
138+
Logger.client.error("Failed to get MCP allowlist from registry: \(error)")
139+
}
140+
}
141+
96142
public static func getServerName(from serverDetail: MCPRegistryServerDetail) -> String {
97143
return serverDetail.name
98144
}

Core/Sources/HostApp/ToolsSettings/MCPRegistry/MCPRegistryURLView.swift

Lines changed: 14 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ struct MCPRegistryURLView: View {
1414
@State private var isLoading: Bool = false
1515
@State private var tempURLText: String = ""
1616
@State private var errorMessage: String = ""
17-
@State private var mcpRegistry: [MCPRegistryEntry]? = nil
17+
@ObservedObject private var registryService = MCPRegistryService.shared
1818

1919
private let maxURLLength = 2048
2020
private let mcpRegistryUrlVersion = "/v0.1/servers"
@@ -48,7 +48,7 @@ struct MCPRegistryURLView: View {
4848
}
4949
.buttonStyle(.bordered)
5050
.help("Configure your MCP Registry Base URL")
51-
.disabled(mcpRegistry?.first?.registryAccess == .registryOnly)
51+
.disabled(registryService.mcpRegistryEntries?.first?.registryAccess == .registryOnly)
5252

5353
Button { Task{ await loadMCPServers() } } label: {
5454
HStack(spacing: 0) {
@@ -74,7 +74,7 @@ struct MCPRegistryURLView: View {
7474
urlText: $tempURLText,
7575
maxURLLength: maxURLLength,
7676
isSheet: false,
77-
mcpRegistryEntry: mcpRegistry?.first,
77+
mcpRegistryEntry: registryService.mcpRegistryEntries?.first,
7878
onValidationChange: { _ in
7979
// Only validate, don't update mcpRegistryURL here
8080
},
@@ -115,7 +115,7 @@ struct MCPRegistryURLView: View {
115115
tempURLText = newValue
116116
Task { await updateGalleryWindowIfOpen() }
117117
}
118-
.onChange(of: mcpRegistry) { _ in
118+
.onChange(of: registryService.mcpRegistryEntries) { _ in
119119
Task { await updateGalleryWindowIfOpen() }
120120
}
121121
}
@@ -145,7 +145,7 @@ struct MCPRegistryURLView: View {
145145
mcpRegistryBaseURLHistory.addToHistory(mcpRegistryBaseURL)
146146
errorMessage = ""
147147

148-
MCPServerGalleryWindow.open(serverList: serverList, mcpRegistryEntry: mcpRegistry?.first)
148+
MCPServerGalleryWindow.open(serverList: serverList, mcpRegistryEntry: registryService.mcpRegistryEntries?.first)
149149
} catch {
150150
Logger.client.error("Failed to load MCP servers from registry: \(error.localizedDescription)")
151151
if let serviceError = error as? XPCExtensionServiceError {
@@ -160,44 +160,14 @@ struct MCPRegistryURLView: View {
160160
private func getMCPRegistryAllowlist() async {
161161
isLoading = true
162162
defer { isLoading = false }
163-
do {
164-
let service = try getService()
165-
166-
// Only fetch allowlist if user is logged in
167-
let authStatus = try await service.getXPCServiceAuthStatus()
168-
guard authStatus?.status == .loggedIn else {
169-
Logger.client.info("User not logged in, skipping MCP registry allowlist fetch")
170-
return
171-
}
172-
173-
let result = try await service.getMCPRegistryAllowlist()
174-
175-
guard let result = result, !result.mcpRegistries.isEmpty else {
176-
if result == nil {
177-
Logger.client.error("Failed to get allowlist result")
178-
} else {
179-
mcpRegistry = []
180-
}
181-
return
182-
}
183-
184-
if let firstRegistry = result.mcpRegistries.first {
185-
let entry = MCPRegistryEntry(
186-
url: firstRegistry.url,
187-
registryAccess: firstRegistry.registryAccess,
188-
owner: firstRegistry.owner
189-
)
190-
mcpRegistry = [entry]
191-
Logger.client.info("Current MCP Registry Entry: \(entry)")
192-
193-
// If registryOnly, force the URL to be the registry URL
194-
if entry.registryAccess == .registryOnly {
195-
mcpRegistryBaseURL = entry.url
196-
tempURLText = entry.url
197-
}
198-
}
199-
} catch {
200-
Logger.client.error("Failed to get MCP allowlist from registry: \(error)")
163+
164+
await registryService.refreshAllowlist()
165+
166+
// If registryOnly, force the URL to be the registry URL
167+
if let entry = registryService.mcpRegistryEntries?.first,
168+
entry.registryAccess == .registryOnly {
169+
mcpRegistryBaseURL = entry.url
170+
tempURLText = entry.url
201171
}
202172
}
203173

@@ -211,7 +181,7 @@ struct MCPRegistryURLView: View {
211181
defer { isLoading = false }
212182

213183
// Let the view model handle the entire update flow including clearing and fetching
214-
if let error = await MCPServerGalleryWindow.refreshFromURL(mcpRegistryEntry: mcpRegistry?.first) {
184+
if let error = await MCPServerGalleryWindow.refreshFromURL(mcpRegistryEntry: registryService.mcpRegistryEntries?.first) {
215185
// Display error in the URL view
216186
if let serviceError = error as? XPCExtensionServiceError {
217187
errorMessage = serviceError.underlyingError?.localizedDescription ?? serviceError.localizedDescription

Core/Sources/HostApp/ToolsSettings/MCPXcodeServerInstallView.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ struct MCPXcodeServerInstallView: View {
1313
/// Cached to avoid repeated file I/O during SwiftUI rendering.
1414
@State private var configuredXcodeServerNames: Set<String> = []
1515
@ObservedObject private var mcpToolManager = CopilotMCPToolManagerObservable.shared
16+
@ObservedObject private var registryService = MCPRegistryService.shared
1617

1718
private let requiredXcodeVersion = "26.4"
1819
private let serverName = "xcode"
@@ -39,6 +40,10 @@ struct MCPXcodeServerInstallView: View {
3940
isConfigured || isConnected
4041
}
4142

43+
private var isRegistryOnly: Bool {
44+
registryService.mcpRegistryEntries?.first?.registryAccess == .registryOnly
45+
}
46+
4247
var body: some View {
4348
HStack(alignment: .center, spacing: 16) {
4449
VStack(alignment: .leading, spacing: 0) {
@@ -61,6 +66,7 @@ struct MCPXcodeServerInstallView: View {
6166
.settingsContainerStyle(isExpanded: false)
6267
.onAppear {
6368
checkInstallationStatus()
69+
Task { await registryService.refreshAllowlist() }
6470
}
6571
.onChange(of: mcpToolManager.availableMCPServerTools) { _ in
6672
checkInstallationStatus()
@@ -76,11 +82,13 @@ struct MCPXcodeServerInstallView: View {
7682
Text("Requires Xcode \(requiredXcodeVersion) or later. Current version: \(versionText).")
7783
} else if isConnected {
7884
Text("Xcode's built-in MCP server is connected, enabling richer editor integration.")
85+
} else if isRegistryOnly {
86+
Text("Manual installation of Xcode's built-in MCP server is blocked by your organization's registry policy. Please check the MCP Registry for an approved installation option, or contact your enterprise IT administrator.")
7987
} else if isConfiguredButNotConnected {
8088
Text("Please confirm in Xcode to allow the built-in MCP server.")
8189
} else {
8290
VStack(alignment: .leading, spacing: 4) {
83-
Text("Connect Copilot to Xcodes builtin MCP server to enable richer editor integration.")
91+
Text("Connect Copilot to Xcode's built-in MCP server to enable richer editor integration.")
8492
if let installError {
8593
Text(installError)
8694
.font(.caption)
@@ -96,6 +104,8 @@ struct MCPXcodeServerInstallView: View {
96104
EmptyView()
97105
} else if isConnected {
98106
Text("Connected").foregroundColor(.secondary)
107+
} else if isRegistryOnly {
108+
EmptyView()
99109
} else if isConfiguredButNotConnected {
100110
HStack(spacing: 6) {
101111
ProgressView()

Server/package-lock.json

Lines changed: 30 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Server/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
"build": "webpack"
88
},
99
"dependencies": {
10-
"@github/copilot-language-server": "1.451.0",
11-
"@github/copilot-language-server-darwin-arm64": "1.451.0",
12-
"@github/copilot-language-server-darwin-x64": "1.451.0",
10+
"@github/copilot-language-server": "1.457.0",
11+
"@github/copilot-language-server-darwin-arm64": "1.457.0",
12+
"@github/copilot-language-server-darwin-x64": "1.457.0",
1313
"@xterm/addon-fit": "^0.10.0",
1414
"@xterm/xterm": "^5.5.0",
1515
"monaco-editor": "0.52.2"

0 commit comments

Comments
 (0)