MCP(模型上下文协议)的官方Swift SDK。
模型上下文协议(MCP)定义了一种标准化的方式,使应用程序能够与AI和ML模型进行通信。此Swift SDK实现了客户端和服务器组件,根据最新的MCP规范版本(2025-03-26)。
请参阅下面的平台可用性部分以获取特定于平台的要求。
在您的 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传输
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(), //