V0.11.x (#199)
* 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 * feat: 2025-11-25 phase 1, includes Version, Icon, Progress (#181) * chore: fixed unit tests * feat: added icon and progess base structs * feat: added progress notification and meta to callTool * feat: added tests * feat: simplified RequestMeta encoding/decoding * tests: added unit test to track progress * chore: rebased v0.11.x * feat: implemented cancellation * feat: added overloaded callTool method * fix: encoding Response _meta fix * chore: added old version to the versions list * feat: 2025-11-25 phase 2; added Logging and Completions (#196) * feat: added Logging and Completions * feat: updated EmbeddedResource for the latest spec * chore: changed encoding of meta in Prompts to encodeIfPresent * feat: compliance tests + http server transport (#197) * feat: http server transport * feat: added http server transport * feat: removed unused files * feat: added conformance tests * feat: improved conformace testing * chore: removed redundant files * feat/2025-11-25: sampling, elicitation, roots (#198) * feat: Roots implementation * feat: added unit testing * feat: improved unit tests and fixed NetworkTransport crash * feat: implemented sampling and elicitation * feat: improved sampling tests * feat: improved elicitation tests * feat: improved confirmance tests * feat: improved Prompts unit tests * feat: removed test code * feat: updated conformance tests * chore: removed redundant commas * chore: removed 6.0.3 swift version from ci * chore: improved unit tests a little bit * chore: update README.md * chore: add server progress tracking example to README.md * chore: updated README.md * chore: updated transports table formatting * chore: updated MockNetworkConnection --------- Co-authored-by: Austin Evans <austinjevans@me.com> Co-authored-by: yehorsobko <yehorsobko@macpaw.com>
This commit is contained in:
parent
6fa1a97b41
commit
6112a3995a
1 changed files with 11854 additions and 828 deletions
233
Sources/MCP/Client/Elicitation.swift
Normal file
233
Sources/MCP/Client/Elicitation.swift
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
import Foundation
|
||||
|
||||
/// Types supporting the MCP elicitation flow.
|
||||
///
|
||||
/// Servers use elicitation to collect structured input from users via the client.
|
||||
/// The schema subset mirrors the 2025-11-25 revision of the specification.
|
||||
public enum Elicitation {
|
||||
/// Schema describing the expected response content.
|
||||
public struct RequestSchema: Hashable, Codable, Sendable {
|
||||
/// Supported top-level types. Currently limited to objects.
|
||||
public enum SchemaType: String, Hashable, Codable, Sendable {
|
||||
case object
|
||||
}
|
||||
|
||||
/// Schema title presented to users.
|
||||
public var title: String?
|
||||
/// Schema description providing additional guidance.
|
||||
public var description: String?
|
||||
/// Raw JSON Schema fragments describing the requested fields.
|
||||
public var properties: [String: Value]
|
||||
/// List of required field keys.
|
||||
public var required: [String]?
|
||||
/// Top-level schema type. Defaults to `object`.
|
||||
public var type: SchemaType
|
||||
|
||||
public init(
|
||||
title: String? = nil,
|
||||
description: String? = nil,
|
||||
properties: [String: Value] = [:],
|
||||
required: [String]? = nil,
|
||||
type: SchemaType = .object
|
||||
) {
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.properties = properties
|
||||
self.required = required
|
||||
self.type = type
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case title, description, properties, required, type
|
||||
}
|
||||
}
|
||||
|
||||
/// Elicitation mode indicating how user input is collected
|
||||
public enum Mode: String, Hashable, Codable, Sendable {
|
||||
/// Form-based elicitation (client displays UI)
|
||||
case form
|
||||
/// URL-based elicitation (client opens external URL)
|
||||
case url
|
||||
}
|
||||
}
|
||||
|
||||
/// To request information from a user, servers send an `elicitation/create` request.
|
||||
/// - SeeAlso: https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation
|
||||
public enum CreateElicitation: Method {
|
||||
public static let name = "elicitation/create"
|
||||
|
||||
public enum Parameters: Hashable, Sendable {
|
||||
/// Form-based elicitation parameters
|
||||
case form(FormParameters)
|
||||
/// URL-based elicitation parameters
|
||||
case url(URLParameters)
|
||||
|
||||
/// Parameters for form-based elicitation
|
||||
public struct FormParameters: Hashable, Codable, Sendable {
|
||||
/// Message displayed to the user describing the request
|
||||
public var message: String
|
||||
/// Elicitation mode (optional for backward compatibility, defaults to form)
|
||||
public var mode: Elicitation.Mode?
|
||||
/// Optional schema describing the expected response content
|
||||
public var requestedSchema: Elicitation.RequestSchema?
|
||||
/// Optional metadata
|
||||
public var _meta: Metadata?
|
||||
|
||||
public init(
|
||||
message: String,
|
||||
mode: Elicitation.Mode? = nil,
|
||||
requestedSchema: Elicitation.RequestSchema? = nil,
|
||||
_meta: Metadata? = nil
|
||||
) {
|
||||
self.message = message
|
||||
self.mode = mode
|
||||
self.requestedSchema = requestedSchema
|
||||
self._meta = _meta
|
||||
}
|
||||
}
|
||||
|
||||
/// Parameters for URL-based elicitation
|
||||
public struct URLParameters: Hashable, Codable, Sendable {
|
||||
/// Message displayed to the user describing the request
|
||||
public var message: String
|
||||
/// Elicitation mode (always "url")
|
||||
public var mode: Elicitation.Mode
|
||||
/// URL for the user to visit
|
||||
public var url: String
|
||||
/// Unique identifier for this elicitation
|
||||
public var elicitationId: String
|
||||
/// Optional metadata
|
||||
public var _meta: Metadata?
|
||||
|
||||
public init(
|
||||
message: String,
|
||||
url: String,
|
||||
elicitationId: String,
|
||||
_meta: Metadata? = nil
|
||||
) {
|
||||
self.message = message
|
||||
self.mode = .url
|
||||
self.url = url
|
||||
self.elicitationId = elicitationId
|
||||
self._meta = _meta
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct Result: Hashable, Codable, Sendable {
|
||||
/// Indicates how the user responded to the request.
|
||||
public enum Action: String, Hashable, Codable, Sendable {
|
||||
case accept
|
||||
case decline
|
||||
case cancel
|
||||
}
|
||||
|
||||
/// Selected action.
|
||||
public var action: Action
|
||||
/// Submitted content when action is `.accept`.
|
||||
public var content: [String: Value]?
|
||||
/// Optional metadata about this result
|
||||
public var _meta: Metadata?
|
||||
|
||||
public init(
|
||||
action: Action,
|
||||
content: [String: Value]? = nil,
|
||||
_meta: Metadata? = nil
|
||||
) {
|
||||
self.action = action
|
||||
self.content = content
|
||||
self._meta = _meta
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey, CaseIterable {
|
||||
case action, content, _meta
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(action, forKey: .action)
|
||||
try container.encodeIfPresent(content, forKey: .content)
|
||||
try container.encodeIfPresent(_meta, forKey: ._meta)
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
action = try container.decode(Action.self, forKey: .action)
|
||||
content = try container.decodeIfPresent([String: Value].self, forKey: .content)
|
||||
_meta = try container.decodeIfPresent(Metadata.self, forKey: ._meta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Codable
|
||||
|
||||
extension CreateElicitation.Parameters: Codable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case mode, message, requestedSchema, url, elicitationId
|
||||
case _meta
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
// Read mode field (may be missing for backward compatibility)
|
||||
let mode = try container.decodeIfPresent(Elicitation.Mode.self, forKey: .mode)
|
||||
|
||||
// Discriminate based on mode
|
||||
if mode == .url {
|
||||
// URL mode
|
||||
let message = try container.decode(String.self, forKey: .message)
|
||||
let url = try container.decode(String.self, forKey: .url)
|
||||
let elicitationId = try container.decode(String.self, forKey: .elicitationId)
|
||||
let _meta = try container.decodeIfPresent(Metadata.self, forKey: ._meta)
|
||||
self = .url(URLParameters(
|
||||
message: message,
|
||||
url: url,
|
||||
elicitationId: elicitationId,
|
||||
_meta: _meta))
|
||||
} else {
|
||||
// Form mode (default for backward compatibility)
|
||||
let message = try container.decode(String.self, forKey: .message)
|
||||
let requestedSchema = try container.decodeIfPresent(
|
||||
Elicitation.RequestSchema.self, forKey: .requestedSchema)
|
||||
let _meta = try container.decodeIfPresent(Metadata.self, forKey: ._meta)
|
||||
self = .form(FormParameters(
|
||||
message: message,
|
||||
mode: mode,
|
||||
requestedSchema: requestedSchema,
|
||||
_meta: _meta))
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
switch self {
|
||||
case .form(let params):
|
||||
try container.encode(params.message, forKey: .message)
|
||||
try container.encodeIfPresent(params.mode, forKey: .mode)
|
||||
try container.encodeIfPresent(params.requestedSchema, forKey: .requestedSchema)
|
||||
try container.encodeIfPresent(params._meta, forKey: ._meta)
|
||||
case .url(let params):
|
||||
try container.encode(params.message, forKey: .message)
|
||||
try container.encode(params.mode, forKey: .mode)
|
||||
try container.encode(params.url, forKey: .url)
|
||||
try container.encode(params.elicitationId, forKey: .elicitationId)
|
||||
try container.encodeIfPresent(params._meta, forKey: ._meta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Notification sent when a URL-based elicitation is complete
|
||||
public struct ElicitationCompleteNotification: Notification {
|
||||
public static let name = "notifications/elicitation/complete"
|
||||
|
||||
public struct Parameters: Hashable, Codable, Sendable {
|
||||
/// The elicitation ID that was completed
|
||||
public var elicitationId: String
|
||||
|
||||
public init(elicitationId: String) {
|
||||
self.elicitationId = elicitationId
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue