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+)
Поддержка платформ
Заголовок раздела «Поддержка платформ»| Платформа | Минимальная версия |
|---|---|
| macOS | 13.0+ |
| iOS / Mac Catalyst | 16.0+ |
| watchOS | 9.0+ |
| tvOS | 16.0+ |
| visionOS | 1.0+ |
| Linux | glibc или musl (Ubuntu, Debian, Fedora, Alpine) |
Установка
Заголовок раздела «Установка»Swift Package Manager
Заголовок раздела «Swift Package Manager»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
Заголовок раздела «Работа с prompts»// Список promptslet (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 (сервер → клиент)
Заголовок раздела «Sampling (сервер → клиент)»// Регистрация обработчика samplingawait 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) )}Batching запросов
Заголовок раздела «Batching запросов»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)") }}Initialize Hook
Заголовок раздела «Initialize Hook»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)")}Graceful Shutdown с Service Lifecycle
Заголовок раздела «Graceful Shutdown с Service Lifecycle»import MCPimport ServiceLifecycleimport 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()Транспорты
Заголовок раздела «Транспорты»| Транспорт | Описание | Платформы |
|---|---|---|
StdioTransport | stdio (stdin/stdout) | Apple, Linux |
HTTPClientTransport | Streamable HTTP | Все с Foundation |
InMemoryTransport | В памяти для тестов | Все |
NetworkTransport | Apple 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 Loggingimport 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)