Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 253 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Official Swift SDK for the [Model Context Protocol][mcp] (MCP).
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-06-18][mcp-spec-2025-06-18] (latest) version
according to the [2025-11-25][mcp-spec-2025-11-25] (latest) version
of the MCP specification.

## Requirements
Expand Down Expand Up @@ -60,6 +60,14 @@ let result = try await client.connect(transport: transport)
if result.capabilities.tools != nil {
// Server supports tools (implicitly including tool calling if the 'tools' capability object is present)
}

if result.capabilities.logging != nil {
// Server supports sending log messages
}

if result.capabilities.completions != nil {
// Server supports argument autocompletion
}
```

> [!NOTE]
Expand Down Expand Up @@ -298,6 +306,61 @@ for message in messages {
}
```

### Completions

Completions allow servers to provide autocompletion suggestions for prompt and resource template arguments as users type:

```swift
// Request completions for a prompt argument
let completion = try await client.complete(
promptName: "code_review",
argumentName: "language",
argumentValue: "py"
)

// Display suggestions to the user
for value in completion.values {
print("Suggestion: \(value)")
}

if completion.hasMore == true {
print("More suggestions available (total: \(completion.total ?? 0))")
}
```

You can also provide context with already-resolved arguments:

```swift
// First, user selects a language
let languageCompletion = try await client.complete(
promptName: "code_review",
argumentName: "language",
argumentValue: "py"
)
// User selects "python"

// Then get framework suggestions based on the selected language
let frameworkCompletion = try await client.complete(
promptName: "code_review",
argumentName: "framework",
argumentValue: "fla",
context: ["language": .string("python")]
)
// Returns: ["flask"]
```

Completions work for resource templates as well:

```swift
// Get path completions for a resource URI template
let pathCompletion = try await client.complete(
resourceURI: "file:///{path}",
argumentName: "path",
argumentValue: "/usr/"
)
// Returns: ["/usr/bin", "/usr/lib", "/usr/local"]
```

### Sampling

Sampling allows servers to request LLM completions through the client,
Expand Down Expand Up @@ -477,6 +540,42 @@ Common use cases for elicitation:
- **Configuration**: Collect preferences or settings during operation
- **Missing information**: Request additional details not provided initially

### Logging

Clients can control server logging levels and receive structured log messages:

```swift
// Set the minimum logging level
try await client.setLoggingLevel(.warning)

// Register a handler for log messages from the server
await client.onNotification(LogMessageNotification.self) { message in
let level = message.params.level // LogLevel (debug, info, warning, etc.)
let logger = message.params.logger // Optional logger name
let data = message.params.data // Arbitrary JSON data

// Display log message based on level
switch level {
case .error, .critical, .alert, .emergency:
print("❌ [\(logger ?? "server")] \(data)")
case .warning:
print("⚠️ [\(logger ?? "server")] \(data)")
default:
print("ℹ️ [\(logger ?? "server")] \(data)")
}
}
```

Log levels follow the standard syslog severity levels (RFC 5424):
- **debug**: Detailed debugging information
- **info**: General informational messages
- **notice**: Normal but significant events
- **warning**: Warning conditions
- **error**: Error conditions
- **critical**: Critical conditions
- **alert**: Action must be taken immediately
- **emergency**: System is unusable

### Error Handling

Handle common client errors:
Expand Down Expand Up @@ -612,6 +711,8 @@ let server = Server(
name: "MyModelServer",
version: "1.0.0",
capabilities: .init(
completions: .init(),
logging: .init(),
prompts: .init(listChanged: true),
resources: .init(subscribe: true, listChanged: true),
tools: .init(listChanged: true)
Expand Down Expand Up @@ -796,6 +897,156 @@ await server.withMethodHandler(GetPrompt.self) { params in
}
```

### Completions

Servers can provide autocompletion suggestions for prompt and resource template arguments:

```swift
// Enable completions capability
let server = Server(
name: "MyServer",
version: "1.0.0",
capabilities: .init(
completions: .init(),
prompts: .init(listChanged: true)
)
)

// Register a completion handler
await server.withMethodHandler(Complete.self) { params in
// Get the argument being completed
let argumentName = params.argument.name
let currentValue = params.argument.value

// Check which prompt or resource is being completed
switch params.ref {
case .prompt(let promptRef):
// Provide completions for a prompt argument
if promptRef.name == "code_review" && argumentName == "language" {
// Simple prefix matching
let allLanguages = ["python", "perl", "php", "javascript", "java", "swift"]
let matches = allLanguages.filter { $0.hasPrefix(currentValue.lowercased()) }

return .init(
completion: .init(
values: Array(matches.prefix(100)), // Max 100 items
total: matches.count,
hasMore: matches.count > 100
)
)
}

case .resource(let resourceRef):
// Provide completions for a resource template argument
if resourceRef.uri == "file:///{path}" && argumentName == "path" {
// Return directory suggestions
let suggestions = try getDirectoryCompletions(for: currentValue)
return .init(
completion: .init(
values: suggestions,
total: suggestions.count,
hasMore: false
)
)
}
}

// No completions available
return .init(completion: .init(values: [], total: 0, hasMore: false))
}
```

You can also use context from already-resolved arguments:

```swift
await server.withMethodHandler(Complete.self) { params in
// Access context from previous argument completions
if let context = params.context,
let language = context.arguments["language"]?.stringValue {

// Provide framework suggestions based on selected language
if language == "python" {
let frameworks = ["flask", "django", "fastapi", "tornado"]
let matches = frameworks.filter {
$0.hasPrefix(params.argument.value.lowercased())
}
return .init(
completion: .init(values: matches, total: matches.count, hasMore: false)
)
}
}

return .init(completion: .init(values: [], total: 0, hasMore: false))
}
```

### Logging

Servers can send structured log messages to clients:

```swift
// Enable logging capability
let server = Server(
name: "MyServer",
version: "1.0.0",
capabilities: .init(
logging: .init(),
tools: .init(listChanged: true)
)
)

// Send log messages at different severity levels
try await server.log(
level: .info,
logger: "database",
data: Value.object([
"message": .string("Database connected successfully"),
"host": .string("localhost"),
"port": .int(5432)
])
)

try await server.log(
level: .error,
logger: "api",
data: Value.object([
"message": .string("Request failed"),
"statusCode": .int(500),
"error": .string("Internal server error")
])
)

// You can also use codable types directly
struct ErrorLog: Codable {
let message: String
let code: Int
let timestamp: String
}

let errorLog = ErrorLog(
message: "Operation failed",
code: 500,
timestamp: ISO8601DateFormatter().string(from: Date())
)

try await server.log(level: .error, logger: "operations", data: errorLog)
```

Clients can control which log levels they receive:

```swift
// Register a handler for client's logging level preferences
await server.withMethodHandler(SetLoggingLevel.self) { params in
let minimumLevel = params.level

// Store the client's preference and filter log messages accordingly
// (Implementation depends on your server architecture)
storeLogLevel(minimumLevel)

return Empty()
}
```

### Sampling

Servers can request LLM completions from clients through sampling. This enables agentic behaviors where servers can ask for AI assistance while maintaining human oversight.
Expand Down Expand Up @@ -1089,4 +1340,4 @@ see the [GitHub Releases page](https://github.com/modelcontextprotocol/swift-sdk
This project is licensed under the MIT License.

[mcp]: https://modelcontextprotocol.io
[mcp-spec-2025-06-18]: https://modelcontextprotocol.io/specification/2025-06-18
[mcp-spec-2025-11-25]: https://modelcontextprotocol.io/specification/2025-11-25
81 changes: 81 additions & 0 deletions Sources/MCP/Client/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,87 @@ public actor Client {
return self
}

// MARK: - Logging

/// Set the minimum logging level for server log messages.
///
/// Servers that declare the `logging` capability will send log messages via
/// `notifications/message` notifications. Use this method to control which
/// severity levels the server should send.
///
/// - Parameter level: The minimum log level to receive
/// - Throws: MCPError if the client is not connected or if the server doesn't support logging
/// - SeeAlso: https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/logging/
public func setLoggingLevel(_ level: LogLevel) async throws {
try validateServerCapability(\.logging, "Logging")
let request = SetLoggingLevel.request(.init(level: level))
_ = try await sendAndAwait(request)
}

// MARK: - Completions

/// Request completion suggestions for a prompt argument.
///
/// Servers that declare the `completions` capability can provide autocompletion
/// suggestions for prompt arguments as users type.
///
/// - Parameters:
/// - promptName: The name of the prompt
/// - argumentName: The name of the argument being completed
/// - argumentValue: The current (partial) value of the argument
/// - context: Optional context with already-resolved arguments
/// - Returns: A completion result containing suggested values
/// - Throws: MCPError if the client is not connected or if the server doesn't support completions
/// - SeeAlso: https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/completion/
public func complete(
promptName: String,
argumentName: String,
argumentValue: String,
context: [String: Value]? = nil
) async throws -> Complete.Result.Completion {
try validateServerCapability(\.completions, "Completions")
let request = Complete.request(
.init(
ref: .prompt(.init(name: promptName)),
argument: .init(name: argumentName, value: argumentValue),
context: context.map { .init(arguments: $0) }
)
)
let result = try await sendAndAwait(request)
return result.completion
}

/// Request completion suggestions for a resource template argument.
///
/// Servers that declare the `completions` capability can provide autocompletion
/// suggestions for resource template arguments as users type.
///
/// - Parameters:
/// - resourceURI: The URI of the resource template
/// - argumentName: The name of the argument being completed
/// - argumentValue: The current (partial) value of the argument
/// - context: Optional context with already-resolved arguments
/// - Returns: A completion result containing suggested values
/// - Throws: MCPError if the client is not connected or if the server doesn't support completions
/// - SeeAlso: https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/completion/
public func complete(
resourceURI: String,
argumentName: String,
argumentValue: String,
context: [String: Value]? = nil
) async throws -> Complete.Result.Completion {
try validateServerCapability(\.completions, "Completions")
let request = Complete.request(
.init(
ref: .resource(.init(uri: resourceURI)),
argument: .init(name: argumentName, value: argumentValue),
context: context.map { .init(arguments: $0) }
)
)
let result = try await sendAndAwait(request)
return result.completion
}

// MARK: -

private func handleResponse(_ response: Response<AnyMethod>) async {
Expand Down
Loading