An open API service indexing awesome lists of open source software.

https://github.com/modelcontextprotocol/swift-sdk

The official Swift SDK for Model Context Protocol servers and clients. Maintained in collaboration with @loopwork-ai.
https://github.com/modelcontextprotocol/swift-sdk

mcp swift

Last synced: about 1 month ago
JSON representation

The official Swift SDK for Model Context Protocol servers and clients. Maintained in collaboration with @loopwork-ai.

Awesome Lists containing this project

README

        

# MCP Swift SDK

Official Swift SDK for the [Model Context Protocol][mcp] (MCP).

## Overview

The Model Context Protocol (MCP) defines a standardized way
for applications to communicate with AI and ML models.
This Swift SDK implements both client and server components
according to the [2025-03-26][mcp-spec-2025-03-26] (latest) version
of the MCP specification.

## Requirements

- Swift 6.0+ (Xcode 16+)

See the [Platform Availability](#platform-availability) section below
for platform-specific requirements.

## Installation

### Swift Package Manager

Add the following to your `Package.swift` file:

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

Then add the dependency to your target:

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

## Client Usage

The client component allows your application to connect to MCP servers.

### Basic Client Setup

```swift
import MCP

// Initialize the client
let client = Client(name: "MyApp", version: "1.0.0")

// Create a transport and connect
let transport = StdioTransport()
try await client.connect(transport: transport)

// Initialize the connection
let result = try await client.initialize()

// Check server capabilities
if result.capabilities.tools != nil {
// Server supports tools (implicitly including tool calling if the 'tools' capability object is present)
}
```

### Transport Options for Clients

#### Stdio Transport

For local subprocess communication:

```swift
// Create a stdio transport (simplest option)
let transport = StdioTransport()
try await client.connect(transport: transport)
```

#### HTTP Transport

For remote server communication:

```swift
// Create a streaming HTTP transport
let transport = HTTPClientTransport(
endpoint: URL(string: "http://localhost:8080")!,
streaming: true // Enable Server-Sent Events for real-time updates
)
try await client.connect(transport: transport)
```

### Tools

Tools represent functions that can be called by the client:

```swift
// List available tools
let tools = try await client.listTools()
print("Available tools: \(tools.map { $0.name }.joined(separator: ", "))")

// Call a tool with arguments
let (content, isError) = try await client.callTool(
name: "image-generator",
arguments: [
"prompt": "A serene mountain landscape at sunset",
"style": "photorealistic",
"width": 1024,
"height": 768
]
)

// Handle tool content
for item in content {
switch item {
case .text(let text):
print("Generated text: \(text)")
case .image(let data, let mimeType, let metadata):
if let width = metadata?["width"] as? Int,
let height = metadata?["height"] as? Int {
print("Generated \(width)x\(height) image of type \(mimeType)")
// Save or display the image data
}
case .audio(let data, let mimeType):
print("Received audio data of type \(mimeType)")
case .resource(let uri, let mimeType, let text):
print("Received resource from \(uri) of type \(mimeType)")
if let text = text {
print("Resource text: \(text)")
}
}
}
```

### Resources

Resources represent data that can be accessed and potentially subscribed to:

```swift
// List available resources
let (resources, nextCursor) = try await client.listResources()
print("Available resources: \(resources.map { $0.uri }.joined(separator: ", "))")

// Read a resource
let contents = try await client.readResource(uri: "resource://example")
print("Resource content: \(contents)")

// Subscribe to resource updates if supported
if result.capabilities.resources.subscribe {
try await client.subscribeToResource(uri: "resource://example")

// Register notification handler
await client.onNotification(ResourceUpdatedNotification.self) { message in
let uri = message.params.uri
print("Resource \(uri) updated with new content")

// Fetch the updated resource content
let updatedContents = try await client.readResource(uri: uri)
print("Updated resource content received")
}
}
```

### Prompts

Prompts represent templated conversation starters:

```swift
// List available prompts
let (prompts, nextCursor) = try await client.listPrompts()
print("Available prompts: \(prompts.map { $0.name }.joined(separator: ", "))")

// Get a prompt with arguments
let (description, messages) = try await client.getPrompt(
name: "customer-service",
arguments: [
"customerName": "Alice",
"orderNumber": "ORD-12345",
"issue": "delivery delay"
]
)

// Use the prompt messages in your application
print("Prompt description: \(description)")
for message in messages {
if case .text(text: let text) = message.content {
print("\(message.role): \(text)")
}
}
```

### Error Handling

Handle common client errors:

```swift
do {
let result = try await client.initialize()
// Success
} catch let error as MCPError {
print("MCP Error: \(error.localizedDescription)")
} catch {
print("Unexpected error: \(error)")
}
```

### Advanced Client Features

#### Strict vs Non-Strict Configuration

Configure client behavior for capability checking:

```swift
// Strict configuration - fail fast if a capability is missing
let strictClient = Client(
name: "StrictClient",
version: "1.0.0",
configuration: .strict
)

// With strict configuration, calling a method for an unsupported capability
// will throw an error immediately without sending a request
do {
// This will throw an error if resources.list capability is not available
let resources = try await strictClient.listResources()
} catch let error as MCPError {
print("Capability not available: \(error.localizedDescription)")
}

// Default (non-strict) configuration - attempt the request anyway
let client = Client(
name: "FlexibleClient",
version: "1.0.0",
configuration: .default
)

// With default configuration, the client will attempt the request
// even if the capability wasn't advertised by the server
do {
let resources = try await client.listResources()
} catch let error as MCPError {
// Still handle the error if the server rejects the request
print("Server rejected request: \(error.localizedDescription)")
}
```

#### Request Batching

Improve performance by sending multiple requests in a single batch:

```swift
// Array to hold tool call tasks
var toolTasks: [Task] = []

// Send a batch of requests
try await client.withBatch { batch in
// Add multiple tool calls to the batch
for i in 0..<10 {
toolTasks.append(
try await batch.addRequest(
CallTool.request(.init(name: "square", arguments: ["n": i]))
)
)
}
}

// Process results after the batch is sent
print("Processing \(toolTasks.count) tool results...")
for (index, task) in toolTasks.enumerated() {
do {
let result = try await task.value
print("\(index): \(result.content)")
} catch {
print("\(index) failed: \(error)")
}
}
```

You can also batch different types of requests:

```swift
// Declare task variables
var pingTask: Task?
var promptTask: Task?

// Send a batch with different request types
try await client.withBatch { batch in
pingTask = try await batch.addRequest(Ping.request())
promptTask = try await batch.addRequest(
GetPrompt.request(.init(name: "greeting"))
)
}

// Process individual results
do {
if let pingTask = pingTask {
try await pingTask.value
print("Ping successful")
}

if let promptTask = promptTask {
let promptResult = try await promptTask.value
print("Prompt: \(promptResult.description ?? "None")")
}
} catch {
print("Error processing batch results: \(error)")
}
```

> [!NOTE]
> `Server` automatically handles batch requests from MCP clients.

## Server Usage

The server component allows your application to host model capabilities and respond to client requests.

### Basic Server Setup

```swift
import MCP

// Initialize the server with capabilities
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)
)
)

// Create transport and start server
let transport = StdioTransport()
try await server.start(transport: transport)

// Now register handlers for the capabilities you've enabled
```

### Tools

Register tool handlers to respond to client tool calls:

```swift
// Register a tool list handler
server.withMethodHandler(ListTools.self) { _ in
let tools = [
Tool(
name: "weather",
description: "Get current weather for a location",
inputSchema: .object([
"location": .string("City name or coordinates"),
"units": .string("Units of measurement, e.g., metric, imperial")
])
),
Tool(
name: "calculator",
description: "Perform calculations",
inputSchema: .object([
"expression": .string("Mathematical expression to evaluate")
])
)
]
return .init(tools: tools)
}

// Register a tool call handler
server.withMethodHandler(CallTool.self) { params in
switch params.name {
case "weather":
let location = params.arguments?["location"]?.stringValue ?? "Unknown"
let units = params.arguments?["units"]?.stringValue ?? "metric"
let weatherData = getWeatherData(location: location, units: units) // Your implementation
return .init(
content: [.text("Weather for \(location): \(weatherData.temperature)°, \(weatherData.conditions)")],
isError: false
)

case "calculator":
if let expression = params.arguments?["expression"]?.stringValue {
let result = evaluateExpression(expression) // Your implementation
return .init(content: [.text("\(result)")], isError: false)
} else {
return .init(content: [.text("Missing expression parameter")], isError: true)
}

default:
return .init(content: [.text("Unknown tool")], isError: true)
}
}
```

### Resources

Implement resource handlers for data access:

```swift
// Register a resource list handler
server.withMethodHandler(ListResources.self) { params in
let resources = [
Resource(
uri: "resource://knowledge-base/articles",
name: "Knowledge Base Articles",
description: "Collection of support articles and documentation"
),
Resource(
uri: "resource://system/status",
name: "System Status",
description: "Current system operational status"
)
]
return .init(resources: resources, nextCursor: nil)
}

// Register a resource read handler
server.withMethodHandler(ReadResource.self) { params in
switch params.uri {
case "resource://knowledge-base/articles":
return .init(contents: [Resource.Content.text("# Knowledge Base\n\nThis is the content of the knowledge base...", uri: params.uri)])

case "resource://system/status":
let status = getCurrentSystemStatus() // Your implementation
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("Unknown resource URI: \(params.uri)")
}
}

// Register a resource subscribe handler
server.withMethodHandler(SubscribeToResource.self) { params in
// Store subscription for later notifications.
// Client identity for multi-client scenarios needs to be managed by the server application,
// potentially using information from the initialize handshake if the server handles one client post-init.
// addSubscription(clientID: /* some_client_identifier */, uri: params.uri)
print("Client subscribed to \(params.uri). Server needs to implement logic to track this subscription.")
return .init()
}
```

### Prompts

Implement prompt handlers:

```swift
// Register a prompt list handler
server.withMethodHandler(ListPrompts.self) { params in
let prompts = [
Prompt(
name: "interview",
description: "Job interview conversation starter",
arguments: [
.init(name: "position", description: "Job position", required: true),
.init(name: "company", description: "Company name", required: true),
.init(name: "interviewee", description: "Candidate name")
]
),
Prompt(
name: "customer-support",
description: "Customer support conversation starter",
arguments: [
.init(name: "issue", description: "Customer issue", required: true),
.init(name: "product", description: "Product name", required: true)
]
)
]
return .init(prompts: prompts, nextCursor: nil)
}

// Register a prompt get handler
server.withMethodHandler(GetPrompt.self) { params in
switch params.name {
case "interview":
let position = params.arguments?["position"]?.stringValue ?? "Software Engineer"
let company = params.arguments?["company"]?.stringValue ?? "Acme Corp"
let interviewee = params.arguments?["interviewee"]?.stringValue ?? "Candidate"

let description = "Job interview for \(position) position at \(company)"
let messages: [Prompt.Message] = [
.init(role: .user, content: .text(text: "You are an interviewer for the \(position) position at \(company).")),
.init(role: .user, content: .text(text: "Hello, I'm \(interviewee) and I'm here for the \(position) interview.")),
.init(role: .assistant, content: .text(text: "Hi \(interviewee), welcome to \(company)! I'd like to start by asking about your background and experience."))
]

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

case "customer-support":
// Similar implementation for customer support prompt

default:
throw MCPError.invalidParams("Unknown prompt name: \(params.name)")
}
}
```

#### Initialize Hook

Control client connections with an initialize hook:

```swift
// Start the server with an initialize hook
try await server.start(transport: transport) { clientInfo, clientCapabilities in
// Validate client info
guard clientInfo.name != "BlockedClient" else {
throw MCPError.invalidRequest("This client is not allowed")
}

// You can also inspect client capabilities
if clientCapabilities.tools == nil {
print("Client does not support tools")
}

// Perform any server-side setup based on client info
print("Client \(clientInfo.name) v\(clientInfo.version) connected")

// If the hook completes without throwing, initialization succeeds
}
```

### Graceful Shutdown

We recommend using
[Swift Service Lifecycle](https://github.com/swift-server/swift-service-lifecycle)
for managing startup and shutdown of services.

First, add the dependency to your `Package.swift`:

```swift
.package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.3.0"),
```

Then implement the MCP server as a `Service`:

```swift
import MCP
import ServiceLifecycle
import Logging

struct MCPService: Service {
let server: Server
let transport: Transport

init(server: Server, transport: Transport) {
self.server = server
self.transport = transport
}

func run() async throws {
// Start the server
try await server.start(transport: transport)

// Keep running until external cancellation
try await Task.sleep(for: .days(365 * 100)) // Effectively forever
}

func shutdown() async throws {
// Gracefully shutdown the server
await server.stop()
}
}
```

Then use it in your application:

```swift
import MCP
import ServiceLifecycle
import Logging

let logger = Logger(label: "com.example.mcp-server")

// Create the MCP server
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)
),
logger: logger
)

// Add handlers directly to the server
server.withMethodHandler(ListTools.self) { _ in
// Your implementation
return .init(tools: [
Tool(name: "example", description: "An example tool")
])
}

server.withMethodHandler(CallTool.self) { params in
// Your implementation
return .init(content: [.text("Tool result")], isError: false)
}

// Create MCP service and other services
let transport = StdioTransport(logger: logger)
let mcpService = MCPService(server: server, transport: transport)
let databaseService = DatabaseService() // Your other services

// Create service group with signal handling
let serviceGroup = ServiceGroup(
services: [mcpService, databaseService],
configuration: .init(
gracefulShutdownSignals: [.sigterm, .sigint]
),
logger: logger
)

// Run the service group - this blocks until shutdown
try await serviceGroup.run()
```

This approach has several benefits:

- **Signal handling**:
Automatically traps SIGINT, SIGTERM and triggers graceful shutdown
- **Graceful shutdown**:
Properly shuts down your MCP server and other services
- **Timeout-based shutdown**:
Configurable shutdown timeouts to prevent hanging processes
- **Advanced service management**:
[`ServiceLifecycle`](https://swiftpackageindex.com/swift-server/swift-service-lifecycle/documentation/servicelifecycle)
also supports service dependencies, conditional services,
and other useful features.

## Transports

MCP's transport layer handles communication between clients and servers.
The Swift SDK provides multiple built-in transports:

| Transport | Description | Platforms | Best for |
|-----------|-------------|-----------|----------|
| [`StdioTransport`](/Sources/MCP/Base/Transports/StdioTransport.swift) | Implements [stdio transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#stdio) using standard input/output streams | Apple platforms, Linux with glibc | Local subprocesses, CLI tools |
| [`HTTPClientTransport`](/Sources/MCP/Base/Transports/HTTPClientTransport.swift) | Implements [Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) using Foundation's URL Loading System | All platforms with Foundation | Remote servers, web applications |
| [`NetworkTransport`](/Sources/MCP/Base/Transports/NetworkTransport.swift) | Custom transport using Apple's Network framework for TCP/UDP connections | Apple platforms only | Low-level networking, custom protocols |

### Custom Transport Implementation

You can implement a custom transport by conforming to the `Transport` protocol:

```swift
import MCP
import Foundation

public actor MyCustomTransport: Transport {
public nonisolated let logger: Logger
private var isConnected = false
private let messageStream: AsyncThrowingStream
private let messageContinuation: AsyncThrowingStream.Continuation

public init(logger: Logger? = nil) {
self.logger = logger ?? Logger(label: "my.custom.transport")

var continuation: AsyncThrowingStream.Continuation!
self.messageStream = AsyncThrowingStream { continuation = $0 }
self.messageContinuation = continuation
}

public func connect() async throws {
// Implement your connection logic
isConnected = true
}

public func disconnect() async {
// Implement your disconnection logic
isConnected = false
messageContinuation.finish()
}

public func send(_ data: Data) async throws {
// Implement your message sending logic
}

public func receive() -> AsyncThrowingStream {
return messageStream
}
}
```

## Platform Availability

The Swift SDK has the following platform requirements:

| Platform | Minimum Version |
|----------|----------------|
| macOS | 13.0+ |
| iOS / Mac Catalyst | 16.0+ |
| watchOS | 9.0+ |
| tvOS | 16.0+ |
| visionOS | 1.0+ |
| Linux | Distributions with `glibc` |

While the core library works on any platform supporting Swift 6
(including Linux and Windows),
running a client or server requires a compatible transport.

We're actively working to expand platform support:
- [Alpine Linux support](https://github.com/modelcontextprotocol/swift-sdk/pull/64)
- [Windows support](https://github.com/modelcontextprotocol/swift-sdk/pull/64)

## Debugging and Logging

Enable logging to help troubleshoot issues:

```swift
import Logging
import MCP

// Configure Logger
LoggingSystem.bootstrap { label in
var handler = StreamLogHandler.standardOutput(label: label)
handler.logLevel = .debug
return handler
}

// Create logger
let logger = Logger(label: "com.example.mcp")

// Pass to client/server
let client = Client(name: "MyApp", version: "1.0.0", logger: logger)

// Pass to transport
let transport = StdioTransport(logger: logger)
```

## Additional Resources

- [MCP Specification](https://modelcontextprotocol.io/specification/2025-03-26/)
- [Protocol Documentation](https://modelcontextprotocol.io)
- [GitHub Repository](https://github.com/modelcontextprotocol/swift-sdk)

## Changelog

This project follows [Semantic Versioning](https://semver.org/).
For pre-1.0 releases,
minor version increments (0.X.0) may contain breaking changes.

For details about changes in each release,
see the [GitHub Releases page](https://github.com/modelcontextprotocol/swift-sdk/releases).

## License

This project is licensed under the MIT License.

[mcp]: https://modelcontextprotocol.io
[mcp-spec-2025-03-26]: https://modelcontextprotocol.io/specification/2025-03-26