Skip to content

Commit d29385c

Browse files
committed
feat: redesign MCP client configuration with per-tool setup instructions
1 parent a741c4c commit d29385c

1 file changed

Lines changed: 156 additions & 30 deletions

File tree

TablePro/Views/Settings/MCPSettingsView.swift

Lines changed: 156 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import SwiftUI
88
struct MCPSettingsView: View {
99
@Bindable var settingsManager: AppSettingsManager
1010
@State private var manager = MCPServerManager.shared
11+
@State private var selectedTool: MCPClientTool = .claudeDesktop
1112

1213
var body: some View {
1314
Form {
@@ -83,30 +84,16 @@ struct MCPSettingsView: View {
8384
// MARK: - Client Configuration
8485

8586
private var clientConfigurationSection: some View {
86-
Section("Client Configuration") {
87-
VStack(alignment: .leading, spacing: 8) {
88-
Text("Add this to your AI tool's MCP configuration:")
89-
.foregroundStyle(.secondary)
90-
.font(.callout)
91-
92-
HStack {
93-
Text(configSnippet)
94-
.font(.system(.caption, design: .monospaced))
95-
.textSelection(.enabled)
96-
.padding(8)
97-
.frame(maxWidth: .infinity, alignment: .leading)
98-
.background(.quaternary)
99-
.clipShape(RoundedRectangle(cornerRadius: 6))
100-
101-
Button {
102-
NSPasteboard.general.clearContents()
103-
NSPasteboard.general.setString(configSnippet, forType: .string)
104-
} label: {
105-
Image(systemName: "doc.on.doc")
106-
}
107-
.help(String(localized: "Copy to clipboard"))
87+
Section("Setup") {
88+
Picker("", selection: $selectedTool) {
89+
ForEach(MCPClientTool.allCases) { tool in
90+
Text(tool.displayName).tag(tool)
10891
}
10992
}
93+
.pickerStyle(.segmented)
94+
.labelsHidden()
95+
96+
MCPSetupInstructions(tool: selectedTool, port: settingsManager.mcp.port)
11097
}
11198
}
11299

@@ -145,16 +132,155 @@ struct MCPSettingsView: View {
145132
}
146133
}
147134

148-
private var configSnippet: String {
149-
"""
150-
{
151-
"mcpServers": {
152-
"tablepro": {
153-
"url": "http://127.0.0.1:\(settingsManager.mcp.port)/mcp"
135+
}
136+
137+
// MARK: - Client Tool Enum
138+
139+
private enum MCPClientTool: String, CaseIterable, Identifiable {
140+
case claudeDesktop
141+
case claudeCode
142+
case cursor
143+
144+
var id: String { rawValue }
145+
146+
var displayName: String {
147+
switch self {
148+
case .claudeDesktop: "Claude Desktop"
149+
case .claudeCode: "Claude Code"
150+
case .cursor: "Cursor"
151+
}
152+
}
153+
}
154+
155+
// MARK: - Setup Instructions
156+
157+
private struct MCPSetupInstructions: View {
158+
let tool: MCPClientTool
159+
let port: Int
160+
@State private var copied = false
161+
162+
var body: some View {
163+
VStack(alignment: .leading, spacing: 12) {
164+
ForEach(Array(steps.enumerated()), id: \.offset) { index, step in
165+
HStack(alignment: .firstTextBaseline, spacing: 8) {
166+
Text("\(index + 1).")
167+
.foregroundStyle(.secondary)
168+
.monospacedDigit()
169+
.frame(width: 20, alignment: .trailing)
170+
Text(step)
171+
.textSelection(.enabled)
172+
}
154173
}
155-
}
174+
175+
if let snippet = configSnippet {
176+
HStack(alignment: .top) {
177+
Text(snippet)
178+
.font(.system(.caption, design: .monospaced))
179+
.textSelection(.enabled)
180+
.padding(8)
181+
.frame(maxWidth: .infinity, alignment: .leading)
182+
.background(.quaternary)
183+
.clipShape(RoundedRectangle(cornerRadius: 6))
184+
185+
Button {
186+
NSPasteboard.general.clearContents()
187+
NSPasteboard.general.setString(snippet, forType: .string)
188+
copied = true
189+
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { copied = false }
190+
} label: {
191+
Image(systemName: copied ? "checkmark" : "doc.on.doc")
192+
.contentTransition(.symbolEffect(.replace))
193+
}
194+
.help(String(localized: "Copy to clipboard"))
195+
}
196+
}
197+
198+
if let command {
199+
HStack(alignment: .top) {
200+
Text(command)
201+
.font(.system(.caption, design: .monospaced))
202+
.textSelection(.enabled)
203+
.padding(8)
204+
.frame(maxWidth: .infinity, alignment: .leading)
205+
.background(.quaternary)
206+
.clipShape(RoundedRectangle(cornerRadius: 6))
207+
208+
Button {
209+
NSPasteboard.general.clearContents()
210+
NSPasteboard.general.setString(command, forType: .string)
211+
copied = true
212+
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { copied = false }
213+
} label: {
214+
Image(systemName: copied ? "checkmark" : "doc.on.doc")
215+
.contentTransition(.symbolEffect(.replace))
216+
}
217+
.help(String(localized: "Copy to clipboard"))
218+
}
219+
}
220+
}
221+
.font(.callout)
222+
}
223+
224+
private var url: String {
225+
"http://127.0.0.1:\(port)/mcp"
226+
}
227+
228+
private var steps: [String] {
229+
switch tool {
230+
case .claudeDesktop:
231+
[
232+
"Open Claude Desktop, go to Settings > Developer",
233+
"Click \"Edit Config\" to open claude_desktop_config.json",
234+
"Add the JSON below inside the file and save",
235+
"Restart Claude Desktop"
236+
]
237+
case .claudeCode:
238+
[
239+
"Run the command below in your terminal",
240+
]
241+
case .cursor:
242+
[
243+
"Open Cursor, go to Settings > MCP",
244+
"Click \"+ Add new global MCP server\"",
245+
"Paste the JSON below and save"
246+
]
247+
}
248+
}
249+
250+
private var configSnippet: String? {
251+
switch tool {
252+
case .claudeDesktop:
253+
"""
254+
{
255+
"mcpServers": {
256+
"tablepro": {
257+
"url": "\(url)"
258+
}
259+
}
260+
}
261+
"""
262+
case .claudeCode:
263+
nil
264+
case .cursor:
265+
"""
266+
{
267+
"mcpServers": {
268+
"tablepro": {
269+
"url": "\(url)"
270+
}
271+
}
272+
}
273+
"""
274+
}
275+
}
276+
277+
private var command: String? {
278+
switch tool {
279+
case .claudeCode:
280+
"claude mcp add tablepro --transport sse \(url)"
281+
default:
282+
nil
156283
}
157-
"""
158284
}
159285
}
160286

0 commit comments

Comments
 (0)