Move Sampling from Server to Client directory (#121)

This commit is contained in:
Mattt 2025-05-26 06:36:23 -07:00 • committed by GitHub
commit 14fddc44c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View 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
}
}
}