豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,55 @@ public class MCPRegistryService: ObservableObject {
public static let shared = MCPRegistryService()
public static let apiVersion = "v0.1"
@AppStorage(\.mcpRegistryBaseURL) var mcpRegistryBaseURL

@Published public private(set) var mcpRegistryEntries: [MCPRegistryEntry]?

private init() {}

/// Fetches the MCP registry allowlist from the language server and updates
/// ``mcpRegistryEntries``. Safe to call from any view's `onAppear` –
/// duplicate in-flight calls are coalesced via the `isRefreshing` flag.
private var isRefreshing = false

public func refreshAllowlist() async {
guard !isRefreshing else { return }
isRefreshing = true
defer { isRefreshing = false }

do {
let service = try getService()

let authStatus = try await service.getXPCServiceAuthStatus()
guard authStatus?.status == .loggedIn else {
Logger.client.info("User not logged in, skipping MCP registry allowlist fetch")
mcpRegistryEntries = nil
return
}

let result = try await service.getMCPRegistryAllowlist()

guard let result = result, !result.mcpRegistries.isEmpty else {
if result == nil {
Logger.client.error("Failed to get allowlist result")
} else {
mcpRegistryEntries = []
}
return
}

if let firstRegistry = result.mcpRegistries.first {
let entry = MCPRegistryEntry(
url: firstRegistry.url,
registryAccess: firstRegistry.registryAccess,
owner: firstRegistry.owner
)
mcpRegistryEntries = [entry]
Logger.client.info("Current MCP Registry Entry: \(entry)")
}
} catch {
Logger.client.error("Failed to get MCP allowlist from registry: \(error)")
}
}

public static func getServerName(from serverDetail: MCPRegistryServerDetail) -> String {
return serverDetail.name
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct MCPRegistryURLView: View {
@State private var isLoading: Bool = false
@State private var tempURLText: String = ""
@State private var errorMessage: String = ""
@State private var mcpRegistry: [MCPRegistryEntry]? = nil
@ObservedObject private var registryService = MCPRegistryService.shared

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

Button { Task{ await loadMCPServers() } } label: {
HStack(spacing: 0) {
Expand All @@ -74,7 +74,7 @@ struct MCPRegistryURLView: View {
urlText: $tempURLText,
maxURLLength: maxURLLength,
isSheet: false,
mcpRegistryEntry: mcpRegistry?.first,
mcpRegistryEntry: registryService.mcpRegistryEntries?.first,
onValidationChange: { _ in
// Only validate, don't update mcpRegistryURL here
},
Expand Down Expand Up @@ -115,7 +115,7 @@ struct MCPRegistryURLView: View {
tempURLText = newValue
Task { await updateGalleryWindowIfOpen() }
}
.onChange(of: mcpRegistry) { _ in
.onChange(of: registryService.mcpRegistryEntries) { _ in
Task { await updateGalleryWindowIfOpen() }
}
}
Expand Down Expand Up @@ -145,7 +145,7 @@ struct MCPRegistryURLView: View {
mcpRegistryBaseURLHistory.addToHistory(mcpRegistryBaseURL)
errorMessage = ""

MCPServerGalleryWindow.open(serverList: serverList, mcpRegistryEntry: mcpRegistry?.first)
MCPServerGalleryWindow.open(serverList: serverList, mcpRegistryEntry: registryService.mcpRegistryEntries?.first)
} catch {
Logger.client.error("Failed to load MCP servers from registry: \(error.localizedDescription)")
if let serviceError = error as? XPCExtensionServiceError {
Expand All @@ -160,44 +160,14 @@ struct MCPRegistryURLView: View {
private func getMCPRegistryAllowlist() async {
isLoading = true
defer { isLoading = false }
do {
let service = try getService()

// Only fetch allowlist if user is logged in
let authStatus = try await service.getXPCServiceAuthStatus()
guard authStatus?.status == .loggedIn else {
Logger.client.info("User not logged in, skipping MCP registry allowlist fetch")
return
}

let result = try await service.getMCPRegistryAllowlist()

guard let result = result, !result.mcpRegistries.isEmpty else {
if result == nil {
Logger.client.error("Failed to get allowlist result")
} else {
mcpRegistry = []
}
return
}

if let firstRegistry = result.mcpRegistries.first {
let entry = MCPRegistryEntry(
url: firstRegistry.url,
registryAccess: firstRegistry.registryAccess,
owner: firstRegistry.owner
)
mcpRegistry = [entry]
Logger.client.info("Current MCP Registry Entry: \(entry)")

// If registryOnly, force the URL to be the registry URL
if entry.registryAccess == .registryOnly {
mcpRegistryBaseURL = entry.url
tempURLText = entry.url
}
}
} catch {
Logger.client.error("Failed to get MCP allowlist from registry: \(error)")

await registryService.refreshAllowlist()

// If registryOnly, force the URL to be the registry URL
if let entry = registryService.mcpRegistryEntries?.first,
entry.registryAccess == .registryOnly {
mcpRegistryBaseURL = entry.url
tempURLText = entry.url
}
}

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

// Let the view model handle the entire update flow including clearing and fetching
if let error = await MCPServerGalleryWindow.refreshFromURL(mcpRegistryEntry: mcpRegistry?.first) {
if let error = await MCPServerGalleryWindow.refreshFromURL(mcpRegistryEntry: registryService.mcpRegistryEntries?.first) {
// Display error in the URL view
if let serviceError = error as? XPCExtensionServiceError {
errorMessage = serviceError.underlyingError?.localizedDescription ?? serviceError.localizedDescription
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ struct MCPXcodeServerInstallView: View {
/// Cached to avoid repeated file I/O during SwiftUI rendering.
@State private var configuredXcodeServerNames: Set<String> = []
@ObservedObject private var mcpToolManager = CopilotMCPToolManagerObservable.shared
@ObservedObject private var registryService = MCPRegistryService.shared

private let requiredXcodeVersion = "26.4"
private let serverName = "xcode"
Expand All @@ -39,6 +40,10 @@ struct MCPXcodeServerInstallView: View {
isConfigured || isConnected
}

private var isRegistryOnly: Bool {
registryService.mcpRegistryEntries?.first?.registryAccess == .registryOnly
}

var body: some View {
HStack(alignment: .center, spacing: 16) {
VStack(alignment: .leading, spacing: 0) {
Expand All @@ -61,6 +66,7 @@ struct MCPXcodeServerInstallView: View {
.settingsContainerStyle(isExpanded: false)
.onAppear {
checkInstallationStatus()
Task { await registryService.refreshAllowlist() }
}
.onChange(of: mcpToolManager.availableMCPServerTools) { _ in
checkInstallationStatus()
Expand All @@ -76,11 +82,13 @@ struct MCPXcodeServerInstallView: View {
Text("Requires Xcode \(requiredXcodeVersion) or later. Current version: \(versionText).")
} else if isConnected {
Text("Xcode's built-in MCP server is connected, enabling richer editor integration.")
} else if isRegistryOnly {
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.")
} else if isConfiguredButNotConnected {
Text("Please confirm in Xcode to allow the built-in MCP server.")
} else {
VStack(alignment: .leading, spacing: 4) {
Text("Connect Copilot to Xcodes builtin MCP server to enable richer editor integration.")
Text("Connect Copilot to Xcode's built-in MCP server to enable richer editor integration.")
if let installError {
Text(installError)
.font(.caption)
Expand All @@ -96,6 +104,8 @@ struct MCPXcodeServerInstallView: View {
EmptyView()
} else if isConnected {
Text("Connected").foregroundColor(.secondary)
} else if isRegistryOnly {
EmptyView()
} else if isConfiguredButNotConnected {
HStack(spacing: 6) {
ProgressView()
Expand Down
60 changes: 30 additions & 30 deletions Server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
"build": "webpack"
},
"dependencies": {
"@github/copilot-language-server": "1.451.0",
"@github/copilot-language-server-darwin-arm64": "1.451.0",
"@github/copilot-language-server-darwin-x64": "1.451.0",
"@github/copilot-language-server": "1.457.0",
"@github/copilot-language-server-darwin-arm64": "1.457.0",
"@github/copilot-language-server-darwin-x64": "1.457.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
"monaco-editor": "0.52.2"
Expand Down
Loading