Skip to content

Swift SDK

Model Context Protocol (MCP) 的 Swift SDK 提供了构建 MCP 服务器和客户端的现代 Swift 解决方案,支持 async/await、类型安全和 Swift 并发特性。

安装

Swift Package Manager

Package.swift 中添加依赖:

swift
dependencies: [
    .package(url: "https://github.com/modelcontextprotocol/swift-sdk.git", from: "1.0.0")
]

在目标中添加产品:

swift
.target(
    name: "MyMcpServer",
    dependencies: [
        .product(name: "ModelContextProtocol", package: "swift-sdk")
    ]
)

Xcode 项目

  1. 在 Xcode 中打开项目
  2. 选择 File → Add Package Dependencies
  3. 输入 URL: https://github.com/modelcontextprotocol/swift-sdk.git
  4. 选择版本并添加到项目

快速开始

创建 MCP 服务器

swift
import ModelContextProtocol
import Foundation

@main
struct MyMcpServer {
    static func main() async throws {
        // 创建服务器
        let server = McpServer(
            name: "my-swift-server",
            version: "1.0.0",
            description: "Swift MCP 服务器示例"
        )
        
        // 注册工具
        server.registerTool(EchoTool())
        
        // 启动服务器
        try await server.start()
    }
}

struct EchoRequest: Codable {
    let message: String
}

struct EchoResponse: Codable {
    let echo: String
}

struct EchoTool: Tool {
    let name = "echo"
    let description = "回显输入的消息"
    
    func inputSchema() -> JSONSchema {
        return JSONSchema.object([
            "message": .string(description: "要回显的消息")
        ], required: ["message"])
    }
    
    func call(with arguments: [String: Any]) async throws -> ToolResult {
        guard let message = arguments["message"] as? String else {
            return .error("缺少 message 参数")
        }
        
        let response = EchoResponse(echo: message)
        return .success(response)
    }
}

创建 MCP 客户端

swift
import ModelContextProtocol
import Foundation

@main
struct MyMcpClient {
    static func main() async throws {
        // 创建传输层
        let transport = StdioTransport()
        
        // 创建客户端
        let client = McpClient(
            transport: transport,
            connectTimeout: .seconds(10),
            requestTimeout: .seconds(30)
        )
        
        do {
            // 连接到服务器
            try await client.connect()
            
            // 列出可用工具
            let tools = try await client.listTools()
            print("可用工具: \(tools.map(\.name).joined(separator: ", "))")
            
            // 调用工具
            let arguments = ["message": "Hello from Swift!"]
            let result = try await client.callTool(name: "echo", arguments: arguments)
            print("工具调用结果: \(result)")
        } catch {
            print("错误: \(error)")
        }
        
        await client.close()
    }
}

核心功能

服务器功能

工具注册

swift
// 计算器工具
struct CalculatorRequest: Codable {
    let operation: Operation
    let a: Double
    let b: Double
    
    enum Operation: String, Codable {
        case add, subtract, multiply, divide
    }
}

struct CalculatorResponse: Codable {
    let result: Double
}

struct CalculatorTool: Tool {
    let name = "calculator"
    let description = "执行基本数学运算"
    
    func inputSchema() -> JSONSchema {
        return JSONSchema.object([
            "operation": .string(enum: ["add", "subtract", "multiply", "divide"]),
            "a": .number(description: "第一个数字"),
            "b": .number(description: "第二个数字")
        ], required: ["operation", "a", "b"])
    }
    
    func call(with arguments: [String: Any]) async throws -> ToolResult {
        guard let operationString = arguments["operation"] as? String,
              let operation = CalculatorRequest.Operation(rawValue: operationString),
              let a = arguments["a"] as? Double,
              let b = arguments["b"] as? Double else {
            return .error("无效的参数")
        }
        
        let result: Double
        switch operation {
        case .add:
            result = a + b
        case .subtract:
            result = a - b
        case .multiply:
            result = a * b
        case .divide:
            guard b != 0 else {
                return .error("除数不能为零")
            }
            result = a / b
        }
        
        let response = CalculatorResponse(result: result)
        return .success(response)
    }
}

// 注册工具
server.registerTool(CalculatorTool())

资源管理

swift
import Foundation

class FileResourceProvider: ResourceProvider {
    private let basePath: URL
    
    init(basePath: URL) {
        self.basePath = basePath
    }
    
    func getResource(uri: ResourceURI) async throws -> Resource {
        let filePath = basePath.appendingPathComponent(uri.path.dropFirst())
        
        guard FileManager.default.fileExists(atPath: filePath.path) else {
            throw ResourceError.notFound("资源不存在: \(uri)")
        }
        
        let content = try String(contentsOf: filePath)
        
        return Resource(
            uri: uri,
            mimeType: "text/plain",
            content: content
        )
    }
    
    func listResources() async throws -> [ResourceURI] {
        let fileManager = FileManager.default
        let enumerator = fileManager.enumerator(at: basePath, includingPropertiesForKeys: nil)
        
        var resources: [ResourceURI] = []
        
        while let fileURL = enumerator?.nextObject() as? URL {
            var isDirectory: ObjCBool = false
            if fileManager.fileExists(atPath: fileURL.path, isDirectory: &isDirectory),
               !isDirectory.boolValue {
                let relativePath = fileURL.path.replacingOccurrences(
                    of: basePath.path,
                    with: ""
                )
                resources.append(ResourceURI("file://\(relativePath)"))
            }
        }
        
        return resources
    }
}

// 注册资源提供者
server.registerResourceProvider(FileResourceProvider(basePath: URL(fileURLWithPath: "/path/to/files")))

提示模板

swift
struct CodeReviewRequest: Codable {
    let code: String
    let language: String
}

class CodeReviewPromptProvider: PromptProvider {
    
    func getPrompt(name: String, arguments: [String: Any]) async throws -> Prompt {
        switch name {
        case "code-review":
            return try await createCodeReviewPrompt(arguments: arguments)
        default:
            throw PromptError.notFound("未知提示: \(name)")
        }
    }
    
    func listPrompts() async throws -> [String] {
        return ["code-review"]
    }
    
    private func createCodeReviewPrompt(arguments: [String: Any]) async throws -> Prompt {
        guard let code = arguments["code"] as? String else {
            throw PromptError.invalidArguments("缺少 code 参数")
        }
        
        let language = arguments["language"] as? String ?? "unknown"
        
        let content = """
            请审查以下 \(language) 代码并提供改进建议:
            
            ```\(language)
            \(code)
            ```
            """
        
        return Prompt(
            name: "code-review",
            description: "代码审查提示",
            content: content
        )
    }
}

// 注册提示提供者
server.registerPromptProvider(CodeReviewPromptProvider())

客户端功能

连接管理

swift
// 自动重连客户端
let client = McpClient(
    transport: transport,
    autoReconnect: true,
    maxReconnectAttempts: 5,
    reconnectDelay: .seconds(2)
)

// 连接状态监听
client.onConnected = {
    print("已连接到服务器")
}

client.onDisconnected = {
    print("与服务器断开连接")
}

client.onError = { error in
    print("连接错误: \(error)")
}

并发操作

swift
// 并发调用多个工具
async let result1 = client.callTool(name: "tool1", arguments: args1)
async let result2 = client.callTool(name: "tool2", arguments: args2)
async let result3 = client.callTool(name: "tool3", arguments: args3)

let results = try await [result1, result2, result3]

for (index, result) in results.enumerated() {
    print("结果 \(index + 1): \(result)")
}

流式操作

swift
// 流式工具调用
for try await chunk in client.callToolStream(name: "streaming-tool", arguments: args) {
    print(chunk, terminator: "")
}

传输协议

Stdio 传输

swift
let transport = StdioTransport(
    command: ["python", "server.py"],
    workingDirectory: URL(fileURLWithPath: "/path/to/server"),
    environment: ["ENV_VAR": "value"]
)

WebSocket 传输

swift
let transport = WebSocketTransport(
    url: URL(string: "ws://localhost:8080/mcp")!,
    headers: ["Authorization": "Bearer token"],
    connectTimeout: .seconds(10)
)

HTTP SSE 传输

swift
let transport = SSETransport(
    url: URL(string: "http://localhost:8080/mcp")!,
    headers: ["Authorization": "Bearer token"],
    readTimeout: .seconds(30)
)

高级特性

依赖注入支持

swift
import Swinject

// 创建容器
let container = Container()

// 注册服务
container.register(DatabaseService.self) { _ in
    DatabaseService()
}.inObjectScope(.container)

container.register(EmailService.self) { _ in
    EmailService()
}.inObjectScope(.container)

// 注册工具
container.register(DatabaseTool.self) { resolver in
    DatabaseTool(databaseService: resolver.resolve(DatabaseService.self)!)
}

container.register(EmailTool.self) { resolver in
    EmailTool(emailService: resolver.resolve(EmailService.self)!)
}

// 创建服务器
let server = McpServer(name: "my-server", version: "1.0.0")

// 注册工具
server.registerTool(container.resolve(DatabaseTool.self)!)
server.registerTool(container.resolve(EmailTool.self)!)

// 带依赖注入的工具
class DatabaseTool: Tool {
    let name = "database-query"
    let description = "执行数据库查询"
    
    private let databaseService: DatabaseService
    
    init(databaseService: DatabaseService) {
        self.databaseService = databaseService
    }
    
    func inputSchema() -> JSONSchema {
        return JSONSchema.object([
            "query": .string(description: "SQL 查询语句")
        ], required: ["query"])
    }
    
    func call(with arguments: [String: Any]) async throws -> ToolResult {
        guard let query = arguments["query"] as? String else {
            return .error("缺少 query 参数")
        }
        
        do {
            let result = try await databaseService.executeQuery(query)
            return .success(result)
        } catch {
            return .error("查询执行失败: \(error.localizedDescription)")
        }
    }
}

中间件支持

swift
protocol Middleware {
    func handle(request: Request, next: @escaping (Request) async throws -> Response) async throws -> Response
}

class LoggingMiddleware: Middleware {
    func handle(request: Request, next: @escaping (Request) async throws -> Response) async throws -> Response {
        print("处理请求: \(request.method)")
        let startTime = Date()
        
        do {
            let response = try await next(request)
            let duration = Date().timeIntervalSince(startTime)
            print("请求完成,耗时: \(Int(duration * 1000))ms")
            return response
        } catch {
            let duration = Date().timeIntervalSince(startTime)
            print("请求处理失败,耗时: \(Int(duration * 1000))ms")
            throw error
        }
    }
}

// 注册中间件
server.use(LoggingMiddleware())
server.use(AuthenticationMiddleware())
server.use(RateLimitingMiddleware())

配置管理

swift
struct McpConfig: Codable {
    let server: ServerConfig
    let logging: LoggingConfig
    let transport: TransportConfig
    
    struct ServerConfig: Codable {
        let name: String
        let version: String
        let description: String
        let maxConnections: Int
        let requestTimeoutSeconds: Int
    }
    
    struct LoggingConfig: Codable {
        let level: String
        let file: String?
    }
    
    struct TransportConfig: Codable {
        let type: String
        let bufferSize: Int
    }
}

// 加载配置
func loadConfig() throws -> McpConfig {
    let configURL = URL(fileURLWithPath: "config.json")
    let data = try Data(contentsOf: configURL)
    return try JSONDecoder().decode(McpConfig.self, from: data)
}

// 使用配置创建服务器
let config = try loadConfig()
let server = McpServer(
    name: config.server.name,
    version: config.server.version,
    description: config.server.description,
    maxConnections: config.server.maxConnections,
    requestTimeout: .seconds(config.server.requestTimeoutSeconds)
)

Actor 并发安全

swift
actor SafeCounter {
    private var value = 0
    
    func increment() -> Int {
        value += 1
        return value
    }
    
    func getValue() -> Int {
        return value
    }
}

class CounterTool: Tool {
    let name = "counter"
    let description = "线程安全的计数器"
    
    private let counter = SafeCounter()
    
    func inputSchema() -> JSONSchema {
        return JSONSchema.object([
            "action": .string(enum: ["increment", "get"])
        ], required: ["action"])
    }
    
    func call(with arguments: [String: Any]) async throws -> ToolResult {
        guard let action = arguments["action"] as? String else {
            return .error("缺少 action 参数")
        }
        
        switch action {
        case "increment":
            let newValue = await counter.increment()
            return .success(["value": newValue])
        case "get":
            let currentValue = await counter.getValue()
            return .success(["value": currentValue])
        default:
            return .error("不支持的操作: \(action)")
        }
    }
}

测试

单元测试

swift
import XCTest
@testable import ModelContextProtocol

final class EchoToolTests: XCTestCase {
    
    func testCallWithValidMessage() async throws {
        // Arrange
        let tool = EchoTool()
        let arguments = ["message": "test"]
        
        // Act
        let result = try await tool.call(with: arguments)
        
        // Assert
        switch result {
        case .success(let data):
            let response = try JSONDecoder().decode(EchoResponse.self, from: JSONSerialization.data(withJSONObject: data))
            XCTAssertEqual(response.echo, "test")
        case .error(let message):
            XCTFail("期望成功,但得到错误: \(message)")
        }
    }
    
    func testCallWithMissingMessage() async throws {
        // Arrange
        let tool = EchoTool()
        let arguments: [String: Any] = [:]
        
        // Act
        let result = try await tool.call(with: arguments)
        
        // Assert
        switch result {
        case .success:
            XCTFail("期望错误,但得到成功")
        case .error(let message):
            XCTAssertTrue(message.contains("缺少 message 参数"))
        }
    }
}

集成测试

swift
import XCTest
@testable import ModelContextProtocol

final class McpIntegrationTests: XCTestCase {
    var server: McpServer!
    var client: McpClient!
    
    override func setUp() async throws {
        // 启动测试服务器
        server = McpServer(name: "test-server", version: "1.0.0")
        server.registerTool(EchoTool())
        
        Task {
            try await server.start()
        }
        
        // 等待服务器启动
        try await Task.sleep(nanoseconds: 100_000_000) // 100ms
        
        // 创建客户端
        let transport = StdioTransport()
        client = McpClient(transport: transport)
        try await client.connect()
    }
    
    override func tearDown() async throws {
        await client?.close()
        await server?.stop()
    }
    
    func testToolCall() async throws {
        // Arrange
        let arguments = ["message": "test"]
        
        // Act
        let result = try await client.callTool(name: "echo", arguments: arguments)
        
        // Assert
        XCTAssertNotNil(result)
        // 验证结果内容
    }
}

部署

iOS 应用集成

swift
import SwiftUI
import ModelContextProtocol

@main
struct McpApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct ContentView: View {
    @StateObject private var mcpManager = McpManager()
    @State private var message = ""
    @State private var response = ""
    
    var body: some View {
        VStack {
            TextField("输入消息", text: $message)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()
            
            Button("发送") {
                Task {
                    response = await mcpManager.sendMessage(message)
                }
            }
            .padding()
            
            Text("响应: \(response)")
                .padding()
            
            Spacer()
        }
        .onAppear {
            Task {
                await mcpManager.connect()
            }
        }
    }
}

@MainActor
class McpManager: ObservableObject {
    private var client: McpClient?
    
    func connect() async {
        let transport = StdioTransport()
        client = McpClient(transport: transport)
        
        do {
            try await client?.connect()
        } catch {
            print("连接失败: \(error)")
        }
    }
    
    func sendMessage(_ message: String) async -> String {
        guard let client = client else { return "未连接" }
        
        do {
            let result = try await client.callTool(
                name: "echo",
                arguments: ["message": message]
            )
            return "\(result)"
        } catch {
            return "错误: \(error)"
        }
    }
}

macOS 命令行工具

swift
import Foundation
import ArgumentParser
import ModelContextProtocol

@main
struct McpCLI: AsyncParsableCommand {
    @Option(help: "服务器命令")
    var serverCommand: String = "python server.py"
    
    @Option(help: "工具名称")
    var tool: String
    
    @Option(help: "工具参数 (JSON 格式)")
    var arguments: String = "{}"
    
    func run() async throws {
        let transport = StdioTransport(command: serverCommand.components(separatedBy: " "))
        let client = McpClient(transport: transport)
        
        do {
            try await client.connect()
            
            guard let data = arguments.data(using: .utf8),
                  let args = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
                throw ValidationError("无效的 JSON 参数")
            }
            
            let result = try await client.callTool(name: tool, arguments: args)
            
            let resultData = try JSONSerialization.data(withJSONObject: result, options: .prettyPrinted)
            if let resultString = String(data: resultData, encoding: .utf8) {
                print(resultString)
            }
        } catch {
            print("错误: \(error)")
            throw ExitCode.failure
        }
        
        await client.close()
    }
}

Docker 部署

dockerfile
FROM swift:5.9-slim

WORKDIR /app

COPY Package.swift .
COPY Sources ./Sources

RUN swift build -c release

EXPOSE 8080

CMD [".build/release/McpServer"]

相关资源

社区支持

🚀 探索模型上下文协议的无限可能 | 连接 AI 与世界的桥梁 | 让智能更智能