-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathMCPRegistryURLInputField.swift
More file actions
112 lines (100 loc) · 3.56 KB
/
MCPRegistryURLInputField.swift
File metadata and controls
112 lines (100 loc) · 3.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import GitHubCopilotService
import SwiftUI
import SharedUIComponents
struct MCPRegistryURLInputField: View {
@Binding var urlText: String
@AppStorage(\.mcpRegistryURLHistory) private var urlHistory
@State private var showHistory: Bool = false
@FocusState private var isFocused: Bool
let defaultMCPRegistryURL = "https://api.mcp.github.com/2025-09-15/v0/servers"
let maxURLLength: Int
let isSheet: Bool
let onValidationChange: ((Bool) -> Void)?
init(
urlText: Binding<String>,
maxURLLength: Int = 2048,
isSheet: Bool = false,
onValidationChange: ((Bool) -> Void)? = nil
) {
self._urlText = urlText
self.maxURLLength = maxURLLength
self.isSheet = isSheet
self.onValidationChange = onValidationChange
}
var body: some View {
HStack(spacing: 8) {
if isSheet {
TextFieldsContainer {
TextField("MCP Registry URL", text: $urlText)
.focused($isFocused)
.onChange(of: urlText) { newValue in
handleURLChange(newValue)
}
}
} else {
TextField("MCP Registry URL:", text: $urlText)
.textFieldStyle(.roundedBorder)
.focused($isFocused)
.onChange(of: urlText) { newValue in
handleURLChange(newValue)
}
}
Menu {
ForEach(urlHistory, id: \.self) { url in
Button(url) {
urlText = url
isFocused = false
}
}
Divider()
Button("Reset to Default") {
urlText = defaultMCPRegistryURL
}
if !urlHistory.isEmpty {
Button("Clear History") {
urlHistory = []
}
}
} label: {
Image(systemName: "chevron.down")
.resizable()
.scaledToFit()
.frame(width: 11, height: 11)
.padding(isSheet ? 9 : 3)
}
.labelStyle(.iconOnly)
.menuIndicator(.hidden)
.buttonStyle(
HoverButtonStyle(
hoverColor: SecondarySystemFillColor,
backgroundColor: SecondarySystemFillColor,
cornerRadius: isSheet ? 12 : 6
)
)
}
}
private func handleURLChange(_ newValue: String) {
let limitedText = String(newValue.prefix(maxURLLength))
if limitedText != newValue {
urlText = limitedText
}
let isValid = limitedText.isEmpty || isValidURL(limitedText)
onValidationChange?(isValid)
}
private func isValidURL(_ string: String) -> Bool {
guard !string.isEmpty else { return true }
return URL(string: string) != nil && (string.hasPrefix("http://") || string.hasPrefix("https://"))
}
}
extension Array where Element == String {
mutating func addToHistory(_ url: String, maxItems: Int = 10) {
// Remove if already exists
removeAll { $0 == url }
// Add to beginning
insert(url, at: 0)
// Keep only maxItems
if count > maxItems {
removeLast(count - maxItems)
}
}
}