* fix: review fixes of the latest changes * fix: updated unit tests * chore: more conformance compliance improvements * chore: reverted protocol changes * chore: more compliance improvements * chore: more conformance compliance improvements * chore: improvements to the backcompat and updated to Tool unit tests * chore: ci update
233 lines
8.7 KiB
Swift
233 lines
8.7 KiB
Swift
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?
|
|
/// Schema describing the expected response content (required per spec)
|
|
public var requestedSchema: Elicitation.RequestSchema
|
|
/// Optional metadata
|
|
public var _meta: Metadata?
|
|
|
|
public init(
|
|
message: String,
|
|
mode: Elicitation.Mode? = nil,
|
|
requestedSchema: Elicitation.RequestSchema = .init(),
|
|
_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.decode(
|
|
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
|
|
}
|
|
}
|
|
}
|