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
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,9 @@
},
"[github-actions-workflow]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[swift]": {
"editor.insertSpaces": true,
"editor.tabSize": 4
}
}
4 changes: 2 additions & 2 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

106 changes: 101 additions & 5 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-03-26][mcp-spec-2025-03-26] (latest) version
according to the [2025-06-18][mcp-spec-2025-06-18] (latest) version
of the MCP specification.

## Requirements
Expand Down Expand Up @@ -274,6 +274,102 @@ This human-in-the-loop design ensures that users
maintain control over what the LLM sees and generates,
even when servers initiate the requests.

### Elicitation

Elicitation allows servers to request structured information directly from users through the client.
This is useful when servers need user input that wasn't provided in the original request,
such as credentials, configuration choices, or approval for sensitive operations.

> [!TIP]
> Elicitation requests flow from **server to client**,
> similar to sampling.
> Clients must register a handler to respond to elicitation requests from servers.

#### Client-Side: Handling Elicitation Requests

Register an elicitation handler to respond to server requests:

```swift
// Register an elicitation handler in the client
await client.setElicitationHandler { parameters in
// Display the request to the user
print("Server requests: \(parameters.message)")

// If a schema was provided, validate against it
if let schema = parameters.requestedSchema {
print("Required fields: \(schema.required ?? [])")
print("Schema: \(schema.properties)")
}

// Present UI to collect user input
let userResponse = presentElicitationUI(parameters)

// Return the user's response
if userResponse.accepted {
return CreateElicitation.Result(
action: .accept,
content: userResponse.data
)
} else if userResponse.canceled {
return CreateElicitation.Result(action: .cancel)
} else {
return CreateElicitation.Result(action: .decline)
}
}
```

#### Server-Side: Requesting User Input

Servers can request information from users through elicitation:

```swift
// Request credentials from the user
let schema = Elicitation.RequestSchema(
title: "API Credentials Required",
description: "Please provide your API credentials to continue",
properties: [
"apiKey": .object([
"type": .string("string"),
"description": .string("Your API key")
]),
"apiSecret": .object([
"type": .string("string"),
"description": .string("Your API secret")
])
],
required: ["apiKey", "apiSecret"]
)

let result = try await client.request(
CreateElicitation.self,
params: CreateElicitation.Parameters(
message: "This operation requires API credentials",
requestedSchema: schema
)
)

switch result.action {
case .accept:
if let credentials = result.content {
let apiKey = credentials["apiKey"]?.stringValue
let apiSecret = credentials["apiSecret"]?.stringValue
// Use the credentials...
}
case .decline:
// User declined to provide credentials
throw MCPError.invalidRequest("User declined credential request")
case .cancel:
// User canceled the operation
throw MCPError.invalidRequest("Operation canceled by user")
}
```

Common use cases for elicitation:
- **Authentication**: Request credentials when needed rather than upfront
- **Confirmation**: Ask for user approval before sensitive operations
- **Configuration**: Collect preferences or settings during operation
- **Missing information**: Request additional details not provided initially

### Error Handling

Handle common client errors:
Expand Down Expand Up @@ -774,8 +870,8 @@ 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 |
| [`StdioTransport`](/Sources/MCP/Base/Transports/StdioTransport.swift) | Implements [stdio transport](https://modelcontextprotocol.io/specification/2025-06-18/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-06-18/basic/transports#streamable-http) using Foundation's URL Loading System | All platforms with Foundation | Remote servers, web applications |
| [`InMemoryTransport`](/Sources/MCP/Base/Transports/InMemoryTransport.swift) | Custom in-memory transport for direct communication within the same process | All platforms | Testing, debugging, same-process client-server communication |
| [`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 |

Expand Down Expand Up @@ -868,7 +964,7 @@ let transport = StdioTransport(logger: logger)

## Additional Resources

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

Expand All @@ -886,4 +982,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-03-26]: https://modelcontextprotocol.io/specification/2025-03-26
[mcp-spec-2025-06-18]: https://modelcontextprotocol.io/specification/2025-06-18
49 changes: 49 additions & 0 deletions Sources/MCP/Base/Lifecycle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,55 @@ public enum Initialize: Method {
public let capabilities: Server.Capabilities
public let serverInfo: Server.Info
public let instructions: String?
public var _meta: [String: Value]?
public var extraFields: [String: Value]?

public init(
protocolVersion: String,
capabilities: Server.Capabilities,
serverInfo: Server.Info,
instructions: String? = nil,
_meta: [String: Value]? = nil,
extraFields: [String: Value]? = nil
) {
self.protocolVersion = protocolVersion
self.capabilities = capabilities
self.serverInfo = serverInfo
self.instructions = instructions
self._meta = _meta
self.extraFields = extraFields
}

private enum CodingKeys: String, CodingKey, CaseIterable {
case protocolVersion, capabilities, serverInfo, instructions
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(protocolVersion, forKey: .protocolVersion)
try container.encode(capabilities, forKey: .capabilities)
try container.encode(serverInfo, forKey: .serverInfo)
try container.encodeIfPresent(instructions, forKey: .instructions)

var dynamicContainer = encoder.container(keyedBy: DynamicCodingKey.self)
try encodeMeta(_meta, to: &dynamicContainer)
try encodeExtraFields(
extraFields, to: &dynamicContainer,
excluding: Set(CodingKeys.allCases.map(\.rawValue)))
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
protocolVersion = try container.decode(String.self, forKey: .protocolVersion)
capabilities = try container.decode(Server.Capabilities.self, forKey: .capabilities)
serverInfo = try container.decode(Server.Info.self, forKey: .serverInfo)
instructions = try container.decodeIfPresent(String.self, forKey: .instructions)

let dynamicContainer = try decoder.container(keyedBy: DynamicCodingKey.self)
_meta = try decodeMeta(from: dynamicContainer)
extraFields = try decodeExtraFields(
from: dynamicContainer, excluding: Set(CodingKeys.allCases.map(\.rawValue)))
}
}
}

Expand Down
Loading