Skip to content

Commit 4b5726f

Browse files
authored
Support 2025-06-18 Spec (#167)
* Support OpenAI _meta for Tool * Update init * Add title and outputSchema * Concurreny issue * General fields * Add Generate Fields to Server elements * 4 tab spacing * Update Version * Elicitation * Format * More tests and inits * Elicitation in README * Fix orphaned test * Add title to more structs * Public init * Fix meta validation and make tests more generic * Format and make Result properties public again * Revert more visibility changes
1 parent c0407a0 commit 4b5726f

24 files changed

Lines changed: 1956 additions & 109 deletions

.vscode/settings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,9 @@
77
},
88
"[github-actions-workflow]": {
99
"editor.defaultFormatter": "esbenp.prettier-vscode"
10+
},
11+
"[swift]": {
12+
"editor.insertSpaces": true,
13+
"editor.tabSize": 4
1014
}
1115
}

Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 101 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Official Swift SDK for the [Model Context Protocol][mcp] (MCP).
77
The Model Context Protocol (MCP) defines a standardized way
88
for applications to communicate with AI and ML models.
99
This Swift SDK implements both client and server components
10-
according to the [2025-03-26][mcp-spec-2025-03-26] (latest) version
10+
according to the [2025-06-18][mcp-spec-2025-06-18] (latest) version
1111
of the MCP specification.
1212

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

277+
### Elicitation
278+
279+
Elicitation allows servers to request structured information directly from users through the client.
280+
This is useful when servers need user input that wasn't provided in the original request,
281+
such as credentials, configuration choices, or approval for sensitive operations.
282+
283+
> [!TIP]
284+
> Elicitation requests flow from **server to client**,
285+
> similar to sampling.
286+
> Clients must register a handler to respond to elicitation requests from servers.
287+
288+
#### Client-Side: Handling Elicitation Requests
289+
290+
Register an elicitation handler to respond to server requests:
291+
292+
```swift
293+
// Register an elicitation handler in the client
294+
await client.setElicitationHandler { parameters in
295+
// Display the request to the user
296+
print("Server requests: \(parameters.message)")
297+
298+
// If a schema was provided, validate against it
299+
if let schema = parameters.requestedSchema {
300+
print("Required fields: \(schema.required ?? [])")
301+
print("Schema: \(schema.properties)")
302+
}
303+
304+
// Present UI to collect user input
305+
let userResponse = presentElicitationUI(parameters)
306+
307+
// Return the user's response
308+
if userResponse.accepted {
309+
return CreateElicitation.Result(
310+
action: .accept,
311+
content: userResponse.data
312+
)
313+
} else if userResponse.canceled {
314+
return CreateElicitation.Result(action: .cancel)
315+
} else {
316+
return CreateElicitation.Result(action: .decline)
317+
}
318+
}
319+
```
320+
321+
#### Server-Side: Requesting User Input
322+
323+
Servers can request information from users through elicitation:
324+
325+
```swift
326+
// Request credentials from the user
327+
let schema = Elicitation.RequestSchema(
328+
title: "API Credentials Required",
329+
description: "Please provide your API credentials to continue",
330+
properties: [
331+
"apiKey": .object([
332+
"type": .string("string"),
333+
"description": .string("Your API key")
334+
]),
335+
"apiSecret": .object([
336+
"type": .string("string"),
337+
"description": .string("Your API secret")
338+
])
339+
],
340+
required: ["apiKey", "apiSecret"]
341+
)
342+
343+
let result = try await client.request(
344+
CreateElicitation.self,
345+
params: CreateElicitation.Parameters(
346+
message: "This operation requires API credentials",
347+
requestedSchema: schema
348+
)
349+
)
350+
351+
switch result.action {
352+
case .accept:
353+
if let credentials = result.content {
354+
let apiKey = credentials["apiKey"]?.stringValue
355+
let apiSecret = credentials["apiSecret"]?.stringValue
356+
// Use the credentials...
357+
}
358+
case .decline:
359+
// User declined to provide credentials
360+
throw MCPError.invalidRequest("User declined credential request")
361+
case .cancel:
362+
// User canceled the operation
363+
throw MCPError.invalidRequest("Operation canceled by user")
364+
}
365+
```
366+
367+
Common use cases for elicitation:
368+
- **Authentication**: Request credentials when needed rather than upfront
369+
- **Confirmation**: Ask for user approval before sensitive operations
370+
- **Configuration**: Collect preferences or settings during operation
371+
- **Missing information**: Request additional details not provided initially
372+
277373
### Error Handling
278374

279375
Handle common client errors:
@@ -774,8 +870,8 @@ The Swift SDK provides multiple built-in transports:
774870

775871
| Transport | Description | Platforms | Best for |
776872
|-----------|-------------|-----------|----------|
777-
| [`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 |
778-
| [`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 |
873+
| [`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 |
874+
| [`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 |
779875
| [`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 |
780876
| [`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 |
781877

@@ -868,7 +964,7 @@ let transport = StdioTransport(logger: logger)
868964

869965
## Additional Resources
870966

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

@@ -886,4 +982,4 @@ see the [GitHub Releases page](https://github.com/modelcontextprotocol/swift-sdk
886982
This project is licensed under the MIT License.
887983

888984
[mcp]: https://modelcontextprotocol.io
889-
[mcp-spec-2025-03-26]: https://modelcontextprotocol.io/specification/2025-03-26
985+
[mcp-spec-2025-06-18]: https://modelcontextprotocol.io/specification/2025-06-18

Sources/MCP/Base/Lifecycle.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,55 @@ public enum Initialize: Method {
4646
public let capabilities: Server.Capabilities
4747
public let serverInfo: Server.Info
4848
public let instructions: String?
49+
public var _meta: [String: Value]?
50+
public var extraFields: [String: Value]?
51+
52+
public init(
53+
protocolVersion: String,
54+
capabilities: Server.Capabilities,
55+
serverInfo: Server.Info,
56+
instructions: String? = nil,
57+
_meta: [String: Value]? = nil,
58+
extraFields: [String: Value]? = nil
59+
) {
60+
self.protocolVersion = protocolVersion
61+
self.capabilities = capabilities
62+
self.serverInfo = serverInfo
63+
self.instructions = instructions
64+
self._meta = _meta
65+
self.extraFields = extraFields
66+
}
67+
68+
private enum CodingKeys: String, CodingKey, CaseIterable {
69+
case protocolVersion, capabilities, serverInfo, instructions
70+
}
71+
72+
public func encode(to encoder: Encoder) throws {
73+
var container = encoder.container(keyedBy: CodingKeys.self)
74+
try container.encode(protocolVersion, forKey: .protocolVersion)
75+
try container.encode(capabilities, forKey: .capabilities)
76+
try container.encode(serverInfo, forKey: .serverInfo)
77+
try container.encodeIfPresent(instructions, forKey: .instructions)
78+
79+
var dynamicContainer = encoder.container(keyedBy: DynamicCodingKey.self)
80+
try encodeMeta(_meta, to: &dynamicContainer)
81+
try encodeExtraFields(
82+
extraFields, to: &dynamicContainer,
83+
excluding: Set(CodingKeys.allCases.map(\.rawValue)))
84+
}
85+
86+
public init(from decoder: Decoder) throws {
87+
let container = try decoder.container(keyedBy: CodingKeys.self)
88+
protocolVersion = try container.decode(String.self, forKey: .protocolVersion)
89+
capabilities = try container.decode(Server.Capabilities.self, forKey: .capabilities)
90+
serverInfo = try container.decode(Server.Info.self, forKey: .serverInfo)
91+
instructions = try container.decodeIfPresent(String.self, forKey: .instructions)
92+
93+
let dynamicContainer = try decoder.container(keyedBy: DynamicCodingKey.self)
94+
_meta = try decodeMeta(from: dynamicContainer)
95+
extraFields = try decodeExtraFields(
96+
from: dynamicContainer, excluding: Set(CodingKeys.allCases.map(\.rawValue)))
97+
}
4998
}
5099
}
51100

0 commit comments

Comments
 (0)