* 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:
Maksym Mova 2026-02-19 13:09:31 +02:00 • committed by GitHub
commit 6112a3995a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -0,0 +1,384 @@
/**
* Everything client - a single conformance test client that handles all scenarios.
*
* Usage: mcp-everything-client <server-url>
*
* The scenario name is read from the MCP_CONFORMANCE_SCENARIO environment variable,
* which is set by the conformance test runner.
*
* This client routes to the appropriate behavior based on the scenario name,
* consolidating all the individual test clients into one.
*/
import Foundation
import Logging
import MCP
// MARK: - Scenario Handlers
typealias ScenarioHandler = ([String]) async throws -> Void
// MARK: - Basic Scenarios
/// Basic client that connects, initializes, and lists tools
func runInitializeScenario(_ args: [String]) async throws {
var logger = Logger(
label: "mcp.conformance.client.initialize",
factory: { StreamLogHandler.standardError(label: $0) }
)
logger.logLevel = .debug
logger.debug("Starting initialize scenario")
// Get server URL from args
guard let serverURLString = args.last,
let serverURL = URL(string: serverURLString) else {
throw ConformanceError.invalidArguments("Valid server URL is required")
}
// Create HTTP transport
let transport = HTTPClientTransport(
endpoint: serverURL,
logger: logger
)
// Create client
let client = Client(name: "test-client", version: "1.0.0")
// Connect
let initResult = try await client.connect(transport: transport)
logger.debug("Successfully connected to MCP server", metadata: [
"serverName": "\(initResult.serverInfo.name)",
"serverVersion": "\(initResult.serverInfo.version)"
])
// List tools
let (tools, _) = try await client.listTools()
logger.debug("Successfully listed tools", metadata: [
"toolCount": "\(tools.count)"
])
// Disconnect
await client.disconnect()
logger.debug("Initialize scenario completed successfully")
}
/// Client that calls the add_numbers tool
func runToolsCallScenario(_ args: [String]) async throws {
var logger = Logger(
label: "mcp.conformance.client.tools_call",
factory: { StreamLogHandler.standardError(label: $0) }
)
logger.logLevel = .debug
logger.debug("Starting tools_call scenario")
// Get server URL from args
guard let serverURLString = args.last,
let serverURL = URL(string: serverURLString) else {
throw ConformanceError.invalidArguments("Valid server URL is required")
}
// Create HTTP transport
let transport = HTTPClientTransport(
endpoint: serverURL,
logger: logger
)
// Create client
let client = Client(name: "test-client", version: "1.0.0")
// Connect
try await client.connect(transport: transport)
logger.debug("Successfully connected to MCP server")
// List tools
let (tools, _) = try await client.listTools()
logger.debug("Successfully listed tools", metadata: [
"toolCount": "\(tools.count)"
])
// Call the add_numbers tool
if tools.contains(where: { $0.name == "add_numbers" }) {
let result = try await client.callTool(
name: "add_numbers",
arguments: ["a": 5, "b": 3]
)
logger.debug("Tool call result", metadata: [
"isError": "\(result.isError ?? false)",
"contentCount": "\(result.content.count)"
])
} else {
logger.warning("add_numbers tool not found")
}
// Disconnect
await client.disconnect()
logger.debug("Tools call scenario completed successfully")
}
// MARK: - SSE Scenarios
/// Handler for SSE-related scenarios (retry, reconnection, etc.)
func runSSEScenario(_ args: [String]) async throws {
var logger = Logger(
label: "mcp.conformance.client.sse",
factory: { StreamLogHandler.standardError(label: $0) }
)
logger.logLevel = .debug
logger.debug("Starting SSE scenario")
// Get server URL from args
guard let serverURLString = args.last,
let serverURL = URL(string: serverURLString) else {
throw ConformanceError.invalidArguments("Valid server URL is required")
}
// Create HTTP transport with streaming enabled
let transport = HTTPClientTransport(
endpoint: serverURL,
streaming: true,
logger: logger
)
// Create client
let client = Client(name: "test-client", version: "1.0.0")
// Connect - this will start the SSE stream in the background
let initResult = try await client.connect(transport: transport)
logger.debug("Successfully connected to MCP server", metadata: [
"serverName": "\(initResult.serverInfo.name)",
"serverVersion": "\(initResult.serverInfo.version)"
])
// Give the GET SSE stream time to establish
try await Task.sleep(for: .milliseconds(500))
// Call the test_reconnection tool to trigger SSE stream closure and retry test.
// The server will close the POST SSE stream without the response,
// then deliver it on the GET SSE stream after we reconnect.
logger.debug("Calling test_reconnection tool...")
let result = try await client.callTool(name: "test_reconnection", arguments: [:])
logger.debug("Tool call result received", metadata: [
"isError": "\(result.isError ?? false)",
"contentCount": "\(result.content.count)"
])
// Keep the connection open briefly for the test to collect timing data
try await Task.sleep(for: .seconds(2))
// Disconnect
await client.disconnect()
logger.debug("SSE scenario completed")
}
/// Client that handles elicitation-sep1034-client-defaults scenario
/// Tests that client properly applies default values for omitted fields
func runElicitationSEP1034ClientDefaults(_ args: [String]) async throws {
var logger = Logger(
label: "mcp.conformance.client.elicitation_client_defaults",
factory: { StreamLogHandler.standardError(label: $0) }
)
logger.logLevel = .debug
logger.debug("Starting elicitation-sep1034-client-defaults scenario")
// Get server URL from args
guard let serverURLString = args.last,
let serverURL = URL(string: serverURLString) else {
throw ConformanceError.invalidArguments("Valid server URL is required")
}
// Create HTTP transport with streaming enabled for bidirectional communication
let transport = HTTPClientTransport(
endpoint: serverURL,
streaming: true,
logger: logger
)
// Create client with elicitation capabilities
let client = Client(
name: "test-client",
version: "1.0.0",
capabilities: Client.Capabilities(
elicitation: Client.Capabilities.Elicitation(form: .init(), url: .init())
)
)
// Set up elicitation handler that accepts defaults BEFORE connecting
await client.withElicitationHandler { [logger] params in
let message: String
switch params {
case .form(let formParams):
message = formParams.message
case .url(let urlParams):
message = urlParams.message
}
logger.debug("Elicitation handler invoked", metadata: [
"message": "\(message)"
])
// Accept with default values applied
// The schema has optional fields with defaults:
// name: "John Doe", age: 30, score: 95.5, status: "active", verified: true
return CreateElicitation.Result(
action: .accept,
content: [
"name": "John Doe",
"age": 30,
"score": 95.5,
"status": "active",
"verified": true
]
)
}
// Connect
try await client.connect(transport: transport)
logger.debug("Successfully connected to MCP server")
// List tools
let (tools, _) = try await client.listTools()
logger.debug("Successfully listed tools", metadata: [
"toolCount": "\(tools.count)"
])
// Call the test_client_elicitation_defaults tool
if tools.contains(where: { $0.name == "test_client_elicitation_defaults" }) {
let result = try await client.callTool(
name: "test_client_elicitation_defaults",
arguments: [:]
)
logger.debug("Tool call result", metadata: [
"isError": "\(result.isError ?? false)",
"contentCount": "\(result.content.count)"
])
} else {
logger.warning("test_client_elicitation_defaults tool not found")
}
// Disconnect
await client.disconnect()
logger.debug("Elicitation client defaults scenario completed successfully")
}
// MARK: - Default Handler for Unimplemented Scenarios
/// Default handler that performs basic connection test for unimplemented scenarios
func runDefaultScenario(_ args: [String]) async throws {
var logger = Logger(
label: "mcp.conformance.client.default",
factory: { StreamLogHandler.standardError(label: $0) }
)
logger.logLevel = .debug
logger.debug("Running default scenario handler")
// Get server URL from args
guard let serverURLString = args.last,
let serverURL = URL(string: serverURLString) else {
throw ConformanceError.invalidArguments("Valid server URL is required")
}
// Create HTTP transport
let transport = HTTPClientTransport(
endpoint: serverURL,
logger: logger
)
// Create client
let client = Client(name: "test-client", version: "1.0.0")
// Connect
let initResult = try await client.connect(transport: transport)
logger.debug("Successfully connected to MCP server", metadata: [
"serverName": "\(initResult.serverInfo.name)",
"serverVersion": "\(initResult.serverInfo.version)"
])
// Disconnect
await client.disconnect()
logger.debug("Default scenario completed successfully")
}
// MARK: - Scenario Registry
nonisolated(unsafe) let scenarioHandlers: [String: ScenarioHandler] = [
"initialize": runInitializeScenario,
"tools_call": runToolsCallScenario,
"sse-retry": runSSEScenario,
"elicitation-sep1034-client-defaults": runElicitationSEP1034ClientDefaults,
// Note: Other scenarios (auth/*) will use the default handler
]
// MARK: - Error Types
enum ConformanceError: Error, CustomStringConvertible {
case missingScenario
case invalidArguments(String)
var description: String {
switch self {
case .missingScenario:
return "MCP_CONFORMANCE_SCENARIO environment variable not set"
case .invalidArguments(let message):
return "Invalid arguments: \(message)"
}
}
}
struct ConformanceClient {
static func run() async {
do {
// Get scenario from environment
guard let scenario = ProcessInfo.processInfo.environment["MCP_CONFORMANCE_SCENARIO"] else {
var stderr = StandardError()
print("Error: MCP_CONFORMANCE_SCENARIO environment variable not set", to: &stderr)
Foundation.exit(1)
}
// Get server URL from arguments (last argument)
let args = Array(CommandLine.arguments.dropFirst())
guard !args.isEmpty else {
var stderr = StandardError()
print("Usage: mcp-everything-client <server-url>", to: &stderr)
print("Error: Server URL is required", to: &stderr)
Foundation.exit(1)
}
// Get handler for scenario, or use default if not implemented
let handler = scenarioHandlers[scenario] ?? runDefaultScenario
// Log if using default handler
if scenarioHandlers[scenario] == nil {
var stderr = StandardError()
print("⚠️ Scenario '\(scenario)' not fully implemented - using default handler", to: &stderr)
}
// Run the scenario
try await handler(args)
Foundation.exit(0)
} catch {
var stderr = StandardError()
print("Error: \(error)", to: &stderr)
Foundation.exit(1)
}
}
}
// MARK: - Helpers
struct StandardError: TextOutputStream {
mutating func write(_ string: String) {
FileHandle.standardError.write(Data(string.utf8))
}
}
await ConformanceClient.run()