Move Sampling from Server to Client directory (#121)
This commit is contained in:
parent
9742933d2f
commit
14fddc44c4
1 changed files with 0 additions and 0 deletions
238
Sources/MCP/Client/Sampling.swift
Normal file
238
Sources/MCP/Client/Sampling.swift
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
import Foundation
|
||||
|
||||
/// The Model Context Protocol (MCP) allows servers to request LLM completions
|
||||
/// through the client, enabling sophisticated agentic behaviors while maintaining
|
||||
/// security and privacy.
|
||||
///
|
||||
/// - SeeAlso: https://modelcontextprotocol.io/docs/concepts/sampling#how-sampling-works
|
||||
public enum Sampling {
|
||||
/// A message in the conversation history.
|
||||
public struct Message: Hashable, Codable, Sendable {
|
||||
/// The message role
|
||||
public enum Role: String, Hashable, Codable, Sendable {
|
||||
/// A user message
|
||||
case user
|
||||
/// An assistant message
|
||||
case assistant
|
||||
}
|
||||
|
||||
/// The message role
|
||||
public let role: Role
|
||||
/// The message content
|
||||
public let content: Content
|
||||
|
||||
/// Creates a message with the specified role and content
|
||||
@available(
|
||||
*, deprecated, message: "Use static factory methods .user(_:) or .assistant(_:) instead"
|
||||
)
|
||||
public init(role: Role, content: Content) {
|
||||
self.role = role
|
||||
self.content = content
|
||||
}
|
||||
|
||||
/// Private initializer for convenience methods to avoid deprecation warnings
|
||||
private init(_role role: Role, _content content: Content) {
|
||||
self.role = role
|
||||
self.content = content
|
||||
}
|
||||
|
||||
/// Creates a user message with the specified content
|
||||
public static func user(_ content: Content) -> Message {
|
||||
return Message(_role: .user, _content: content)
|
||||
}
|
||||
|
||||
/// Creates an assistant message with the specified content
|
||||
public static func assistant(_ content: Content) -> Message {
|
||||
return Message(_role: .assistant, _content: content)
|
||||
}
|
||||
|
||||
/// Content types for sampling messages
|
||||
public enum Content: Hashable, Sendable {
|
||||
/// Text content
|
||||
case text(String)
|
||||
/// Image content
|
||||
case image(data: String, mimeType: String)
|
||||
}
|
||||
}
|
||||
|
||||
/// Model preferences for sampling requests
|
||||
public struct ModelPreferences: Hashable, Codable, Sendable {
|
||||
/// Model hints for selection
|
||||
public struct Hint: Hashable, Codable, Sendable {
|
||||
/// Suggested model name/family
|
||||
public let name: String?
|
||||
|
||||
public init(name: String? = nil) {
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
|
||||
/// Array of model name suggestions that clients can use to select an appropriate model
|
||||
public let hints: [Hint]?
|
||||
/// Importance of minimizing costs (0-1 normalized)
|
||||
public let costPriority: UnitInterval?
|
||||
/// Importance of low latency response (0-1 normalized)
|
||||
public let speedPriority: UnitInterval?
|
||||
/// Importance of advanced model capabilities (0-1 normalized)
|
||||
public let intelligencePriority: UnitInterval?
|
||||
|
||||
public init(
|
||||
hints: [Hint]? = nil,
|
||||
costPriority: UnitInterval? = nil,
|
||||
speedPriority: UnitInterval? = nil,
|
||||
intelligencePriority: UnitInterval? = nil
|
||||
) {
|
||||
self.hints = hints
|
||||
self.costPriority = costPriority
|
||||
self.speedPriority = speedPriority
|
||||
self.intelligencePriority = intelligencePriority
|
||||
}
|
||||
}
|
||||
|
||||
/// Context inclusion options for sampling requests
|
||||
public enum ContextInclusion: String, Hashable, Codable, Sendable {
|
||||
/// No additional context
|
||||
case none
|
||||
/// Include context from the requesting server
|
||||
case thisServer
|
||||
/// Include context from all connected MCP servers
|
||||
case allServers
|
||||
}
|
||||
|
||||
/// Stop reason for sampling completion
|
||||
public enum StopReason: String, Hashable, Codable, Sendable {
|
||||
/// Natural end of turn
|
||||
case endTurn
|
||||
/// Hit a stop sequence
|
||||
case stopSequence
|
||||
/// Reached maximum tokens
|
||||
case maxTokens
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Codable
|
||||
|
||||
extension Sampling.Message.Content: Codable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case type, text, data, mimeType
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let type = try container.decode(String.self, forKey: .type)
|
||||
|
||||
switch type {
|
||||
case "text":
|
||||
let text = try container.decode(String.self, forKey: .text)
|
||||
self = .text(text)
|
||||
case "image":
|
||||
let data = try container.decode(String.self, forKey: .data)
|
||||
let mimeType = try container.decode(String.self, forKey: .mimeType)
|
||||
self = .image(data: data, mimeType: mimeType)
|
||||
default:
|
||||
throw DecodingError.dataCorruptedError(
|
||||
forKey: .type, in: container,
|
||||
debugDescription: "Unknown sampling message content type")
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
switch self {
|
||||
case .text(let text):
|
||||
try container.encode("text", forKey: .type)
|
||||
try container.encode(text, forKey: .text)
|
||||
case .image(let data, let mimeType):
|
||||
try container.encode("image", forKey: .type)
|
||||
try container.encode(data, forKey: .data)
|
||||
try container.encode(mimeType, forKey: .mimeType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ExpressibleByStringLiteral
|
||||
|
||||
extension Sampling.Message.Content: ExpressibleByStringLiteral {
|
||||
public init(stringLiteral value: String) {
|
||||
self = .text(value)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ExpressibleByStringInterpolation
|
||||
|
||||
extension Sampling.Message.Content: ExpressibleByStringInterpolation {
|
||||
public init(stringInterpolation: DefaultStringInterpolation) {
|
||||
self = .text(String(stringInterpolation: stringInterpolation))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
/// To request sampling from a client, servers send a `sampling/createMessage` request.
|
||||
/// - SeeAlso: https://modelcontextprotocol.io/docs/concepts/sampling#how-sampling-works
|
||||
public enum CreateSamplingMessage: Method {
|
||||
public static let name = "sampling/createMessage"
|
||||
|
||||
public struct Parameters: Hashable, Codable, Sendable {
|
||||
/// The conversation history to send to the LLM
|
||||
public let messages: [Sampling.Message]
|
||||
/// Model selection preferences
|
||||
public let modelPreferences: Sampling.ModelPreferences?
|
||||
/// Optional system prompt
|
||||
public let systemPrompt: String?
|
||||
/// What MCP context to include
|
||||
public let includeContext: Sampling.ContextInclusion?
|
||||
/// Controls randomness (0.0 to 1.0)
|
||||
public let temperature: Double?
|
||||
/// Maximum tokens to generate
|
||||
public let maxTokens: Int
|
||||
/// Array of sequences that stop generation
|
||||
public let stopSequences: [String]?
|
||||
/// Additional provider-specific parameters
|
||||
public let metadata: [String: Value]?
|
||||
|
||||
public init(
|
||||
messages: [Sampling.Message],
|
||||
modelPreferences: Sampling.ModelPreferences? = nil,
|
||||
systemPrompt: String? = nil,
|
||||
includeContext: Sampling.ContextInclusion? = nil,
|
||||
temperature: Double? = nil,
|
||||
maxTokens: Int,
|
||||
stopSequences: [String]? = nil,
|
||||
metadata: [String: Value]? = nil
|
||||
) {
|
||||
self.messages = messages
|
||||
self.modelPreferences = modelPreferences
|
||||
self.systemPrompt = systemPrompt
|
||||
self.includeContext = includeContext
|
||||
self.temperature = temperature
|
||||
self.maxTokens = maxTokens
|
||||
self.stopSequences = stopSequences
|
||||
self.metadata = metadata
|
||||
}
|
||||
}
|
||||
|
||||
public struct Result: Hashable, Codable, Sendable {
|
||||
/// Name of the model used
|
||||
public let model: String
|
||||
/// Why sampling stopped
|
||||
public let stopReason: Sampling.StopReason?
|
||||
/// The role of the completion
|
||||
public let role: Sampling.Message.Role
|
||||
/// The completion content
|
||||
public let content: Sampling.Message.Content
|
||||
|
||||
public init(
|
||||
model: String,
|
||||
stopReason: Sampling.StopReason? = nil,
|
||||
role: Sampling.Message.Role,
|
||||
content: Sampling.Message.Content
|
||||
) {
|
||||
self.model = model
|
||||
self.stopReason = stopReason
|
||||
self.role = role
|
||||
self.content = content
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue