Перейти к содержимому

Swift SDK

Официальный Swift SDK для Model Context Protocol — создание MCP-серверов и клиентов для Apple платформ и Linux

Официальный Swift SDK для Model Context Protocol реализует клиентские и серверные компоненты согласно спецификации MCP 2025-03-26 для Apple платформ и Linux.

  • Swift 6.0+ (Xcode 16+)
ПлатформаМинимальная версия
macOS13.0+
iOS / Mac Catalyst16.0+
watchOS9.0+
tvOS16.0+
visionOS1.0+
Linuxglibc или musl (Ubuntu, Debian, Fedora, Alpine)
Package.swift
dependencies: [
.package(url: "https://github.com/modelcontextprotocol/swift-sdk.git", from: "0.10.0")
]
// В target
.target(
name: "YourTarget",
dependencies: [
.product(name: "MCP", package: "swift-sdk")
]
)
import MCP
// Инициализация клиента
let client = Client(name: "MyApp", version: "1.0.0")
// Подключение через транспорт
let transport = StdioTransport()
let result = try await client.connect(transport: transport)
// Проверка capabilities сервера
if result.capabilities.tools != nil {
// Сервер поддерживает инструменты
}

Stdio (для локальных процессов):

let transport = StdioTransport()
try await client.connect(transport: transport)

HTTP (для удалённых серверов):

let transport = HTTPClientTransport(
endpoint: URL(string: "http://localhost:8080")!,
streaming: true // SSE для real-time обновлений
)
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": "photorealistic",
"width": 1024,
"height": 768
]
)
// Обработка результата
for item in content {
switch item {
case .text(let text):
print("Текст: \(text)")
case .image(let data, let mimeType, let metadata):
print("Изображение: \(mimeType)")
case .audio(let data, let mimeType):
print("Аудио: \(mimeType)")
case .resource(let uri, let mimeType, let text):
print("Ресурс: \(uri)")
}
}
// Список ресурсов
let (resources, nextCursor) = try await client.listResources()
// Чтение ресурса
let contents = try await client.readResource(uri: "resource://example")
// Подписка на обновления
if result.capabilities.resources.subscribe {
try await client.subscribeToResource(uri: "resource://example")
await client.onNotification(ResourceUpdatedNotification.self) { message in
print("Ресурс \(message.params.uri) обновлён")
let updated = try await client.readResource(uri: message.params.uri)
}
}
// Список prompts
let (prompts, nextCursor) = try await client.listPrompts()
// Получение prompt с аргументами
let (description, messages) = try await client.getPrompt(
name: "customer-service",
arguments: [
"customerName": "Алиса",
"orderNumber": "ORD-12345",
"issue": "задержка доставки"
]
)
for message in messages {
if case .text(text: let text) = message.content {
print("\(message.role): \(text)")
}
}
// Регистрация обработчика sampling
await client.withSamplingHandler { parameters in
// Просмотр запроса (human-in-the-loop)
print("Сервер запрашивает: \(parameters.messages)")
// Вызов вашего LLM
let completion = try await callYourLLMService(
messages: parameters.messages,
maxTokens: parameters.maxTokens,
temperature: parameters.temperature
)
// Возврат результата
return CreateSamplingMessage.Result(
model: "your-model-name",
stopReason: .endTurn,
role: .assistant,
content: .text(completion)
)
}
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)]))
)
)
}
}
// Обработка результатов
for (index, task) in toolTasks.enumerated() {
do {
let result = try await task.value
print("\(index): \(result.content)")
} catch {
print("\(index) failed: \(error)")
}
}
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("metric или imperial")
])
])
),
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 ?? "Unknown"
let weather = getWeatherData(location: location)
return .init(
content: [.text("Погода в \(location): \(weather.temperature)°")],
isError: false
)
case "calculator":
if let expression = params.arguments?["expression"]?.stringValue {
let result = evaluateExpression(expression)
return .init(content: [.text("\(result)")], isError: false)
}
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",
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":
return .init(contents: [
Resource.Content.text("# База знаний\n\nСодержимое...", uri: params.uri)
])
case "resource://system/status":
let status = getCurrentSystemStatus()
return .init(contents: [
Resource.Content.text(status.toJSON(), uri: params.uri, mimeType: "application/json")
])
default:
throw MCPError.invalidParams("Неизвестный URI: \(params.uri)")
}
}
try await server.start(transport: transport) { clientInfo, clientCapabilities in
// Валидация клиента
guard clientInfo.name != "BlockedClient" else {
throw MCPError.invalidRequest("Клиент заблокирован")
}
// Проверка capabilities
if clientCapabilities.sampling == nil {
print("Клиент не поддерживает sampling")
}
print("Подключён: \(clientInfo.name) v\(clientInfo.version)")
}
import MCP
import ServiceLifecycle
import Logging
struct MCPService: Service {
let server: Server
let transport: Transport
func run() async throws {
try await server.start(transport: transport)
try await Task.sleep(for: .days(365 * 100))
}
func shutdown() async throws {
await server.stop()
}
}
// Использование
let server = Server(name: "MyServer", version: "1.0.0", capabilities: ...)
let transport = StdioTransport()
let mcpService = MCPService(server: server, transport: transport)
let serviceGroup = ServiceGroup(
services: [mcpService],
configuration: .init(gracefulShutdownSignals: [.sigterm, .sigint]),
logger: Logger(label: "mcp-server")
)
try await serviceGroup.run()
ТранспортОписаниеПлатформы
StdioTransportstdio (stdin/stdout)Apple, Linux
HTTPClientTransportStreamable HTTPВсе с Foundation
InMemoryTransportВ памяти для тестовВсе
NetworkTransportApple Network.frameworkТолько Apple
public actor MyCustomTransport: Transport {
public nonisolated let logger: Logger
private var isConnected = false
public func connect() async throws {
isConnected = true
}
public func disconnect() async {
isConnected = false
}
public func send(_ data: Data) async throws {
// Отправка данных
}
public func receive() -> AsyncThrowingStream<Data, any Swift.Error> {
// Получение данных
}
}
import Logging
import MCP
LoggingSystem.bootstrap { label in
var handler = StreamLogHandler.standardOutput(label: label)
handler.logLevel = .debug
return handler
}
let logger = Logger(label: "com.example.mcp")
let transport = StdioTransport(logger: logger)