返回市场
迅捷软件开发工具包

迅捷软件开发工具包

作者:modelcontextprotocol994 星标更新:2025-09-07

项目介绍

MCP Swift SDK

MCP(模型上下文协议)的官方Swift SDK。

概述

模型上下文协议(MCP)定义了一种标准化的方式,使应用程序能够与AI和ML模型进行通信。此Swift SDK实现了客户端和服务器组件,根据最新的MCP规范版本(2025-03-26)。

要求

  • Swift 6.0+ (Xcode 16+)

请参阅下面的平台可用性部分以获取特定于平台的要求。

安装

Swift 包管理器

在您的 Package.swift 文件中添加以下内容:

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

然后将依赖项添加到您的目标中:

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

客户端使用

客户端组件允许您的应用程序连接到MCP服务器。

基本客户端设置

import MCP

// 初始化客户端
let client = Client(name: "MyApp", version: "1.0.0")

// 创建传输并连接
let transport = StdioTransport()
let result = try await client.connect(transport: transport)

// 检查服务器功能
if result.capabilities.tools != nil {
    // 服务器支持工具(如果存在“工具”功能对象,则隐含支持工具调用)
}

[!NOTE] Client.connect(transport:) 方法返回初始化结果。 此返回值可丢弃, 因此如果您不需要检查服务器功能,可以忽略它。

客户端传输选项

标准输入输出传输

用于本地子进程通信:

// 创建标准输入输出传输(最简单的选项)
let transport = StdioTransport()
try await client.connect(transport: transport)

HTTP传输

用于远程服务器通信:

// 创建流式HTTP传输
let transport = HTTPClientTransport(
    endpoint: URL(string: "http://localhost:8080")!,
    streaming: true  // 启用服务器发送事件以实现实时更新
)
try await client.connect(transport: transport)

工具

工具代表可以由客户端调用的功能:

// 列出可用工具
let (tools, cursor) = try await client.listTools()
print("可用工具:\(tools.map { $0.name }.joined(separator: ", "))")

// 使用参数调用工具
let (content, isError) = try await client.callTool(
    name: "image-generator",
    arguments: [
        "prompt": "日落时宁静的山景",
        "style": "照片级真实",
        "width": 1024,
        "height":  768
    ]
)

// 处理工具内容
for item in content {
    switch item {
    case .text(let text):
        print("生成文本:\(text)")
    case .image(let data, let mimeType, let metadata):
        if let width = metadata?["width"] as? Int,
           let height = metadata?["height"] as? Int {
            print("生成了宽度为\(width)高度为\(height)的类型为\(mimeType)的图像")
            // 保存或显示图像数据
        }
    case .audio(let data, let mimeType):
        print("接收到类型为\(mimeType)的音频数据")
    case .resource(let uri, let mimeType, let text):
        print("从\(uri)接收到类型为\(mimeType)的资源")
        if let text = text {
            print("资源文本:\(text)")
        }
    }
}

资源

资源代表可以访问并可能订阅的数据:

// 列出可用资源
let (resources, nextCursor) = try await client.listResources()
print("可用资源:\(resources.map { $0.uri }.joined(separator: ", "))")

// 读取资源
let contents = try await client.readResource(uri: "resource://example")
print("资源内容:\(contents)")

// 如果支持,订阅资源更新
if result.capabilities.resources.subscribe {
    try await client.subscribeToResource(uri: "resource://example")

    // 注册通知处理程序
    await client.onNotification(ResourceUpdatedNotification.self) { message in
        let uri = message.params.uri
        print("资源\(uri)已更新,带有新内容")

        // 获取更新的资源内容
        let updatedContents = try await client.readResource(uri: uri)
        print("收到更新的资源内容")
    }
}

提示

提示代表模板化的对话开始:

// 列出可用提示
let (prompts, nextCursor) = try await client.listPrompts()
print("可用提示:\(prompts.map { $0.name }.joined(separator: ", "))")

// 获取具有参数的提示
let (description, messages) = try await client.getPrompt(
    name: "customer-service",
    arguments: [
        "customerName": "Alice",
        "orderNumber": "ORD-12345",
        "issue": "交货延迟"
    ]
)

// 在您的应用程序中使用提示消息
print("提示描述:\(description)")
for message in messages {
    if case .text(text: let text) = message.content {
        print("\(message.role): \(text)")
    }
}

抽样

抽样允许服务器通过客户端请求LLM完成,从而实现代理行为,同时保持人工控制。客户端注册一个处理器来处理来自服务器的抽样请求。

[!TIP] 抽样请求从服务器流向客户端, 而不是从客户端流向服务器。 这使得服务器可以在需要时请求AI协助, 而客户端则保留对模型访问和用户批准的控制。

// 在客户端注册抽样处理器
await client.withSamplingHandler { parameters in
    // 审查抽样请求(人工控制步骤1)
    print("服务器请求完成:\(parameters.messages)")
    
    // 根据用户输入可选地修改请求
    var messages = parameters.messages
    if let systemPrompt = parameters.systemPrompt {
        print("系统提示:\(systemPrompt)")
    }
    
    // 从您的LLM抽样(这里您会调用您的AI服务)
    let completion = try await callYourLLMService(
        messages: messages,
        maxTokens: parameters.maxTokens,
        temperature: parameters.temperature
    )
    
    // 审查完成(人工控制步骤2)
    print("LLM生成:\(completion)")
    // 用户可以批准、修改或拒绝完成
    
    // 将结果返回给服务器
    return CreateSamplingMessage.Result(
        model: "your-model-name",
        stopReason: .endTurn,
        role: .assistant,
        content: .text(completion)
    )
}

抽样的流程如下:

sequenceDiagram
    participant S as MCP Server
    participant C as MCP Client
    participant U as User/Human
    participant L as LLM Service

    Note over S,L: 服务器发起的抽样请求
    S->>C: sampling/createMessage 请求
    Note right of S: 服务器需要AI协助<br/>决策或内容

    Note over C,U: 人工控制审查 #1
    C->>U: 显示抽样请求
    U->>U: 审查并可选地修改<br/>消息、系统提示
    U->>C: 批准请求

    Note over C,L: 客户端处理LLM交互
    C->>L: 发送消息到LLM
    L->>C: 返回完成

    Note over C,U: 人工控制审查 #2
    C->>U: 显示LLM完成
    U->>U: 审查并可选地修改<br/>或拒绝完成
    U->>C: 批准完成

    Note over C,S: 将结果返回给服务器
    C->>S: sampling/createMessage 响应
    Note left of C: 包含使用的模型、<br/>停止原因、最终内容

    Note over S: 服务器继续使用<br/>AI辅助的结果

这种人工控制设计确保用户即使在服务器发起请求时也能控制LLM看到和生成的内容。

错误处理

处理常见的客户端错误:

do {
    try await client.connect(transport: transport)
    // 成功
} catch let error as MCPError {
    print("MCP 错误:\(error.localizedDescription)")
} catch {
    print("意外错误:\(error)")
}

高级客户端功能

严格与非严格配置

配置客户端的行为以检查功能:

// 严格配置 - 如果缺少功能,则快速失败
let strictClient = Client(
    name: "StrictClient",
    version: "1.0.0",
    configuration: .strict
)

// 严格配置下,调用不支持功能的方法
// 将立即抛出错误而不发送请求
do {
    // 如果资源列表功能不可用,这将抛出错误
    let resources = try await strictClient.listResources()
} catch let error as MCPError {
    print("功能不可用:\(error.localizedDescription)")
}

// 默认(非严格)配置 - 尝试请求
let client = Client(
    name: "FlexibleClient",
    version: "1.0.0",
    configuration: .default
)

// 默认配置下,客户端将尝试请求
// 即使服务器没有宣传该功能
do {
    let resources = try await client.listResources()
} catch let error as MCPError {
    // 仍然处理服务器拒绝请求的错误
    print("服务器拒绝请求:\(error.localizedDescription)")
}

请求批处理

通过一次发送多个请求来提高性能:

// 数组用于存储工具调用任务
var toolTasks: [Task<CallTool.Result, Swift.Error>] = []

// 发送一批请求
try await client.withBatch { batch in
    // 将多个工具调用添加到批次中
    for i in 0..<10 {
        toolTasks.append(
            try await batch.addRequest(
                CallTool.request(.init(name: "square", arguments: ["n": Value(i)]))
            )
        )
    }
}

// 在批次发送后处理结果
print("处理 \(toolTasks.count) 个工具结果...")
for (index, task) in toolTasks.enumerated() {
    do {
        let result = try await task.value
        print("\(index): \(result.content)")
    } catch {
        print("\(index) 失败:\(error)")
    }
}

您还可以批处理不同类型的请求:

// 声明任务变量
var pingTask: Task<Ping.Result, Error>?
var promptTask: Task<GetPrompt.Result, Error>?

// 发送包含不同类型请求的批次
try await client.withBatch { batch in
    pingTask = try await batch.addRequest(Ping.request())
    promptTask = try await batch.addRequest(
        GetPrompt.request(.init(name: "greeting"))
    )
}

// 处理单独的结果
do {
    if let pingTask = pingTask {
        try await pingTask.value
        print("Ping 成功")
    }

    if let promptTask = promptTask {
        let promptResult = try await promptTask.value
        print("提示:\(promptResult.description ?? "无")")
    }
} catch {
    print("处理批次结果时出错:\(error)")
}

[!NOTE] Server 自动处理来自 MCP 客户端的批处理请求。

服务器使用

服务器组件允许您的应用程序托管模型功能并响应客户端请求。

基本服务器设置

import MCP

// 使用给定功能创建服务器
let server = Server(
    name: "MyModelServer",
    version: "1.0.0",
    capabilities: .init(
        prompts: .init(listChanged: true),
        resources: .init(subscribe: true, listChanged: true),
        tools: .init(listChanged: true)
    )
)

// 创建传输并启动服务器
let transport = StdioTransport()
try await server.start(transport: transport)

// 现在为启用的功能注册处理器

工具

注册工具处理器以响应客户端工具调用:

// 注册工具列表处理器
await server.withMethodHandler(ListTools.self) { _ in
    let tools = [
        Tool(
            name: "weather",
            description: "获取位置的当前天气",
            inputSchema: .object([
                "properties": .object([
                    "location": .string("城市名称或坐标"),
                    "units": .string("单位,例如公制、英制")
                ])
            ])
        ),
        Tool(
            name: "calculator",
            description: "执行计算",
            inputSchema: .object([
                "properties": .object([
                    "expression": .string("要评估的数学表达式")
                ])
            ])
        )
    ]
    return .init(tools: tools)
}

// 注册工具调用处理器
await server.withMethodHandler(CallTool.self) { params in
    switch params.name {
    case "weather":
        let location = params.arguments?["location"]?.stringValue ?? "未知"
        let units = params.arguments?["units"]?.stringValue ?? "公制"
        let weatherData = getWeatherData(location: location, units: units) // 您的实现
        return .init(
            content: [.text("天气:\(location): \(weatherData.temperature)°, \(weatherData.conditions)")],
            isError: false
        )

    case "calculator":
        if let expression = params.arguments?["expression"]?.stringValue {
            let result = evaluateExpression(expression) // 您的实现
            return .init(content: [.text("\(result)")], isError: false)
        } else {
            return .init(content: [.text("缺少表达式参数")], isError: true)
        }

    default:
        return .init(content: [.text("未知工具")], isError: true)
    }
}

资源

实现资源处理器以访问数据:

// 注册资源列表处理器
await server.withMethodHandler(ListResources.self) { params in
    let resources = [
        Resource(
            name: "知识库文章",
            uri: "resource://knowledge-base/articles",
            description: "支持文章和文档集合"
        ),
        Resource(
            name: "系统状态",
            uri: "resource://system/status",
            description: "当前系统运行状态"
        )
    ]
    return .init(resources: resources, nextCursor: nil)
}

// 注册资源读取处理器
await server.withMethodHandler(ReadResource.self) { params in
    switch params.uri {
    case "resource://knowledge-base/articles":
        return .init(contents: [Resource.Content.text("# 知识库\n\n这是知识库的内容...", uri: params.uri)])

    case "resource://system/status":
        let status = getCurrentSystemStatus() // 您的实现
        let statusJson = """
            {
                "status": "\(status.overall)",
                "components": {
                    "database": "\(status.database)",
                    "api": "\(status.api)",
                    "model": "\(status.model)"
                },
                "lastUpdated": "\(status.timestamp)"
            }
            """
        return .init(contents: [Resource.Content.text(statusJson, uri: params.uri, mimeType: "application/json")])

    default:
        throw MCPError.invalidParams("未知资源URI:\(params.uri)")
    }
}

// 注册资源订阅处理器
await server.withMethodHandler(ResourceSubscribe.self) { params in
    // 存储订阅以供后续通知。
    // 对于多客户端场景,服务器应用程序需要管理客户端身份,
    // 可能使用初始化握手中的信息,如果服务器在初始化后只处理一个客户端。
    // addSubscription(clientID: /* some_client_identifier */, uri: params.uri)
    print("客户端订阅了 \(params.uri)。服务器需要实现逻辑来跟踪此订阅。")
    return .init()
}

提示

实现提示处理器:

// 注册提示列表处理器
await server.withMethodHandler(ListPrompts.self) { params in
    let prompts = [
        Prompt(
            name: "interview",
            description: "工作面试对话开始",
            arguments: [
                .init(name: "position", description: "职位", required: true),
                .init(name: "company", description: "公司名称", required: true),
                .init(name: "interviewee", description: "候选人姓名")
            ]
        ),
        Prompt(
            name: "customer-support",
            description: "客户服务对话开始",
            arguments: [
                .init(name: "issue", description: "客户问题", required: true),
                .init(name: "product", description: "产品名称", required: true)
            ]
        )
    ]
    return .init(prompts: prompts, nextCursor: nil)
}

// 注册提示获取处理器
await server.withMethodHandler(GetPrompt.self) { params in
    switch params.name {
    case "interview":
        let position = params.arguments?["position"]?.stringValue ?? "软件工程师"
        let company = params.arguments?["company"]?.stringValue ?? "Acme Corp"
        let interviewee = params.arguments?["interviewee"]?.stringValue ?? "候选人"

        let description = "职位面试:\(position) 职位在 \(company)"
        let messages: [Prompt.Message] = [
            .user("您是 \(company) 的 \(position) 职位的面试官。"),
            .user("您好,我是 \(interviewee),我在这里是为了参加 \(position) 的面试。"),
            .assistant("你好 \(interviewee),欢迎来到 \(company)! 我想先问一下你的背景和经验。")
        ]

        return .init(description: description, messages: messages)

    case "customer-support":
        // 类似的实现用于客户服务提示

    default:
        throw MCPError.invalidParams("未知提示名称:\(params.name)")
    }
}

抽样

服务器可以通过抽样从客户端请求LLM完成。这使得代理行为成为可能,其中服务器可以请求AI协助,同时保持人工监督。

[!NOTE] 当前实现提供了正确的API设计用于抽样,但需要传输层支持双向通信。当添加双向传输支持时,此功能将完全可用。

// 在服务器中启用抽样功能
let server = Server(
    name: "MyModelServer",
    version: "1.0.0",
    capabilities: .init(
        sampling: .init(),  //