Skip to content

Commit a59e59d

Browse files
committed
Merge branch 'main' into musl
# Conflicts: # Package.swift
2 parents fe31430 + e76d611 commit a59e59d

File tree

14 files changed

+904
-231
lines changed

14 files changed

+904
-231
lines changed

Package.resolved

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,16 @@ import PackageDescription
77
var dependencies: [Package.Dependency] = [
88
.package(url: "https://github.com/apple/swift-system.git", from: "1.0.0"),
99
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.0"),
10+
.package(url: "https://github.com/mattt/eventsource.git", from: "1.1.0")
1011
]
1112

1213
// Target dependencies needed on all platforms
1314
var targetDependencies: [Target.Dependency] = [
1415
.product(name: "SystemPackage", package: "swift-system"),
1516
.product(name: "Logging", package: "swift-log"),
17+
.product(name: "EventSource", package: "eventsource", condition: .when(platforms: [.macOS, .iOS, .tvOS, .visionOS, .watchOS]))
1618
]
1719

18-
// Add EventSource only on Apple platforms (non-Linux)
19-
#if !os(Linux)
20-
dependencies.append(
21-
.package(url: "https://github.com/loopwork-ai/eventsource.git", from: "1.1.0"))
22-
targetDependencies.append(
23-
.product(
24-
name: "EventSource", package: "eventsource",
25-
condition: .when(platforms: [.macOS, .iOS, .tvOS, .watchOS, .visionOS])))
26-
#endif
27-
2820
let package = Package(
2921
name: "mcp-swift-sdk",
3022
platforms: [

README.md

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Add the following to your `Package.swift` file:
2525

2626
```swift
2727
dependencies: [
28-
.package(url: "https://github.com/modelcontextprotocol/swift-sdk.git", from: "0.9.0")
28+
.package(url: "https://github.com/modelcontextprotocol/swift-sdk.git", from: "0.10.0")
2929
]
3030
```
3131

@@ -98,7 +98,7 @@ Tools represent functions that can be called by the client:
9898

9999
```swift
100100
// List available tools
101-
let tools = try await client.listTools()
101+
let (tools, cursor) = try await client.listTools()
102102
print("Available tools: \(tools.map { $0.name }.joined(separator: ", "))")
103103

104104
// Call a tool with arguments
@@ -335,15 +335,15 @@ Improve performance by sending multiple requests in a single batch:
335335

336336
```swift
337337
// Array to hold tool call tasks
338-
var toolTasks: [Task<CallTool.Result, Error>] = []
338+
var toolTasks: [Task<CallTool.Result, Swift.Error>] = []
339339

340340
// Send a batch of requests
341341
try await client.withBatch { batch in
342342
// Add multiple tool calls to the batch
343343
for i in 0..<10 {
344344
toolTasks.append(
345345
try await batch.addRequest(
346-
CallTool.request(.init(name: "square", arguments: ["n": i]))
346+
CallTool.request(.init(name: "square", arguments: ["n": Value(i)]))
347347
)
348348
)
349349
}
@@ -428,7 +428,7 @@ Register tool handlers to respond to client tool calls:
428428

429429
```swift
430430
// Register a tool list handler
431-
server.withMethodHandler(ListTools.self) { _ in
431+
await server.withMethodHandler(ListTools.self) { _ in
432432
let tools = [
433433
Tool(
434434
name: "weather",
@@ -454,7 +454,7 @@ server.withMethodHandler(ListTools.self) { _ in
454454
}
455455

456456
// Register a tool call handler
457-
server.withMethodHandler(CallTool.self) { params in
457+
await server.withMethodHandler(CallTool.self) { params in
458458
switch params.name {
459459
case "weather":
460460
let location = params.arguments?["location"]?.stringValue ?? "Unknown"
@@ -485,24 +485,24 @@ Implement resource handlers for data access:
485485

486486
```swift
487487
// Register a resource list handler
488-
server.withMethodHandler(ListResources.self) { params in
488+
await server.withMethodHandler(ListResources.self) { params in
489489
let resources = [
490490
Resource(
491-
uri: "resource://knowledge-base/articles",
492491
name: "Knowledge Base Articles",
492+
uri: "resource://knowledge-base/articles",
493493
description: "Collection of support articles and documentation"
494494
),
495495
Resource(
496-
uri: "resource://system/status",
497496
name: "System Status",
497+
uri: "resource://system/status",
498498
description: "Current system operational status"
499499
)
500500
]
501501
return .init(resources: resources, nextCursor: nil)
502502
}
503503

504504
// Register a resource read handler
505-
server.withMethodHandler(ReadResource.self) { params in
505+
await server.withMethodHandler(ReadResource.self) { params in
506506
switch params.uri {
507507
case "resource://knowledge-base/articles":
508508
return .init(contents: [Resource.Content.text("# Knowledge Base\n\nThis is the content of the knowledge base...", uri: params.uri)])
@@ -528,7 +528,7 @@ server.withMethodHandler(ReadResource.self) { params in
528528
}
529529

530530
// Register a resource subscribe handler
531-
server.withMethodHandler(SubscribeToResource.self) { params in
531+
await server.withMethodHandler(ResourceSubscribe.self) { params in
532532
// Store subscription for later notifications.
533533
// Client identity for multi-client scenarios needs to be managed by the server application,
534534
// potentially using information from the initialize handshake if the server handles one client post-init.
@@ -544,7 +544,7 @@ Implement prompt handlers:
544544

545545
```swift
546546
// Register a prompt list handler
547-
server.withMethodHandler(ListPrompts.self) { params in
547+
await server.withMethodHandler(ListPrompts.self) { params in
548548
let prompts = [
549549
Prompt(
550550
name: "interview",
@@ -568,7 +568,7 @@ server.withMethodHandler(ListPrompts.self) { params in
568568
}
569569

570570
// Register a prompt get handler
571-
server.withMethodHandler(GetPrompt.self) { params in
571+
await server.withMethodHandler(GetPrompt.self) { params in
572572
switch params.name {
573573
case "interview":
574574
let position = params.arguments?["position"]?.stringValue ?? "Software Engineer"
@@ -618,8 +618,8 @@ do {
618618
.user("Analyze this data and suggest next steps")
619619
],
620620
systemPrompt: "You are a helpful data analyst",
621-
maxTokens: 150,
622-
temperature: 0.7
621+
temperature: 0.7,
622+
maxTokens: 150
623623
)
624624

625625
// Use the LLM completion in your server logic
@@ -649,8 +649,8 @@ try await server.start(transport: transport) { clientInfo, clientCapabilities in
649649
}
650650

651651
// You can also inspect client capabilities
652-
if clientCapabilities.tools == nil {
653-
print("Client does not support tools")
652+
if clientCapabilities.sampling == nil {
653+
print("Client does not support sampling")
654654
}
655655

656656
// Perform any server-side setup based on client info
@@ -720,19 +720,18 @@ let server = Server(
720720
prompts: .init(listChanged: true),
721721
resources: .init(subscribe: true, listChanged: true),
722722
tools: .init(listChanged: true)
723-
),
724-
logger: logger
723+
)
725724
)
726725

727726
// Add handlers directly to the server
728-
server.withMethodHandler(ListTools.self) { _ in
727+
await server.withMethodHandler(ListTools.self) { _ in
729728
// Your implementation
730729
return .init(tools: [
731730
Tool(name: "example", description: "An example tool")
732731
])
733732
}
734733

735-
server.withMethodHandler(CallTool.self) { params in
734+
await server.withMethodHandler(CallTool.self) { params in
736735
// Your implementation
737736
return .init(content: [.text("Tool result")], isError: false)
738737
}
@@ -777,6 +776,7 @@ The Swift SDK provides multiple built-in transports:
777776
|-----------|-------------|-----------|----------|
778777
| [`StdioTransport`](/Sources/MCP/Base/Transports/StdioTransport.swift) | Implements [stdio transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#stdio) using standard input/output streams | Apple platforms, Linux with glibc | Local subprocesses, CLI tools |
779778
| [`HTTPClientTransport`](/Sources/MCP/Base/Transports/HTTPClientTransport.swift) | Implements [Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) using Foundation's URL Loading System | All platforms with Foundation | Remote servers, web applications |
779+
| [`InMemoryTransport`](/Sources/MCP/Base/Transports/InMemoryTransport.swift) | Custom in-memory transport for direct communication within the same process | All platforms | Testing, debugging, same-process client-server communication |
780780
| [`NetworkTransport`](/Sources/MCP/Base/Transports/NetworkTransport.swift) | Custom transport using Apple's Network framework for TCP/UDP connections | Apple platforms only | Low-level networking, custom protocols |
781781

782782
### Custom Transport Implementation
@@ -790,13 +790,13 @@ import Foundation
790790
public actor MyCustomTransport: Transport {
791791
public nonisolated let logger: Logger
792792
private var isConnected = false
793-
private let messageStream: AsyncThrowingStream<Data, Error>
794-
private let messageContinuation: AsyncThrowingStream<Data, Error>.Continuation
793+
private let messageStream: AsyncThrowingStream<Data, any Swift.Error>
794+
private let messageContinuation: AsyncThrowingStream<Data, any Swift.Error>.Continuation
795795

796796
public init(logger: Logger? = nil) {
797797
self.logger = logger ?? Logger(label: "my.custom.transport")
798798

799-
var continuation: AsyncThrowingStream<Data, Error>.Continuation!
799+
var continuation: AsyncThrowingStream<Data, any Swift.Error>.Continuation!
800800
self.messageStream = AsyncThrowingStream { continuation = $0 }
801801
self.messageContinuation = continuation
802802
}
@@ -816,7 +816,7 @@ public actor MyCustomTransport: Transport {
816816
// Implement your message sending logic
817817
}
818818

819-
public func receive() -> AsyncThrowingStream<Data, Error> {
819+
public func receive() -> AsyncThrowingStream<Data, any Swift.Error> {
820820
return messageStream
821821
}
822822
}
@@ -862,7 +862,7 @@ LoggingSystem.bootstrap { label in
862862
let logger = Logger(label: "com.example.mcp")
863863

864864
// Pass to client/server
865-
let client = Client(name: "MyApp", version: "1.0.0", logger: logger)
865+
let client = Client(name: "MyApp", version: "1.0.0")
866866

867867
// Pass to transport
868868
let transport = StdioTransport(logger: logger)

Sources/MCP/Base/Error.swift

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -243,12 +243,3 @@ extension MCPError: Hashable {
243243
}
244244
}
245245
}
246-
247-
// MARK: -
248-
249-
/// This is provided to allow existing code that uses `MCP.Error` to continue
250-
/// to work without modification.
251-
///
252-
/// The MCPError type is now the recommended way to handle errors in MCP.
253-
@available(*, deprecated, renamed: "MCPError", message: "Use MCPError instead of MCP.Error")
254-
public typealias Error = MCPError

Sources/MCP/Base/Transports/HTTPClientTransport.swift

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,14 @@ import Logging
3232
/// ```swift
3333
/// import MCP
3434
///
35-
/// // Create a streaming HTTP transport
35+
/// // Create a streaming HTTP transport with bearer token authentication
3636
/// let transport = HTTPClientTransport(
37-
/// endpoint: URL(string: "http://localhost:8080")!,
37+
/// endpoint: URL(string: "https://api.example.com/mcp")!,
38+
/// requestModifier: { request in
39+
/// var modifiedRequest = request
40+
/// modifiedRequest.addValue("Bearer your-token-here", forHTTPHeaderField: "Authorization")
41+
/// return modifiedRequest
42+
/// }
3843
/// )
3944
///
4045
/// // Initialize the client with streaming transport
@@ -60,6 +65,9 @@ public actor HTTPClientTransport: Transport {
6065
/// Maximum time to wait for a session ID before proceeding with SSE connection
6166
public let sseInitializationTimeout: TimeInterval
6267

68+
/// Closure to modify requests before they are sent
69+
private let requestModifier: (URLRequest) -> URLRequest
70+
6371
private var isConnected = false
6472
private let messageStream: AsyncThrowingStream<Data, Swift.Error>
6573
private let messageContinuation: AsyncThrowingStream<Data, Swift.Error>.Continuation
@@ -74,19 +82,22 @@ public actor HTTPClientTransport: Transport {
7482
/// - configuration: URLSession configuration to use for HTTP requests
7583
/// - streaming: Whether to enable SSE streaming mode (default: true)
7684
/// - sseInitializationTimeout: Maximum time to wait for session ID before proceeding with SSE (default: 10 seconds)
85+
/// - requestModifier: Optional closure to customize requests before they are sent (default: no modification)
7786
/// - logger: Optional logger instance for transport events
7887
public init(
7988
endpoint: URL,
8089
configuration: URLSessionConfiguration = .default,
8190
streaming: Bool = true,
8291
sseInitializationTimeout: TimeInterval = 10,
92+
requestModifier: @escaping (URLRequest) -> URLRequest = { $0 },
8393
logger: Logger? = nil
8494
) {
8595
self.init(
8696
endpoint: endpoint,
8797
session: URLSession(configuration: configuration),
8898
streaming: streaming,
8999
sseInitializationTimeout: sseInitializationTimeout,
100+
requestModifier: requestModifier,
90101
logger: logger
91102
)
92103
}
@@ -96,12 +107,14 @@ public actor HTTPClientTransport: Transport {
96107
session: URLSession,
97108
streaming: Bool = false,
98109
sseInitializationTimeout: TimeInterval = 10,
110+
requestModifier: @escaping (URLRequest) -> URLRequest = { $0 },
99111
logger: Logger? = nil
100112
) {
101113
self.endpoint = endpoint
102114
self.session = session
103115
self.streaming = streaming
104116
self.sseInitializationTimeout = sseInitializationTimeout
117+
self.requestModifier = requestModifier
105118

106119
// Create message stream
107120
var continuation: AsyncThrowingStream<Data, Swift.Error>.Continuation!
@@ -152,7 +165,7 @@ public actor HTTPClientTransport: Transport {
152165
streamingTask = Task { await startListeningForServerEvents() }
153166
}
154167

155-
logger.info("HTTP transport connected")
168+
logger.debug("HTTP transport connected")
156169
}
157170

158171
/// Disconnects from the transport
@@ -180,7 +193,7 @@ public actor HTTPClientTransport: Transport {
180193
initialSessionIDContinuation?.resume()
181194
initialSessionIDContinuation = nil
182195

183-
logger.info("HTTP clienttransport disconnected")
196+
logger.debug("HTTP clienttransport disconnected")
184197
}
185198

186199
/// Sends data through an HTTP POST request
@@ -211,6 +224,9 @@ public actor HTTPClientTransport: Transport {
211224
request.addValue(sessionID, forHTTPHeaderField: "Mcp-Session-Id")
212225
}
213226

227+
// Apply request modifier
228+
request = requestModifier(request)
229+
214230
#if os(Linux)
215231
// Linux implementation using data(for:) instead of bytes(for:)
216232
let (responseData, response) = try await session.data(for: request)
@@ -440,7 +456,7 @@ public actor HTTPClientTransport: Transport {
440456
"Initial sessionID already available. Proceeding with SSE streaming task immediately."
441457
)
442458
} else {
443-
logger.info(
459+
logger.trace(
444460
"Proceeding with SSE connection attempt; sessionID is nil. This might be expected for stateless servers or if initialize hasn't provided one yet."
445461
)
446462
}
@@ -480,6 +496,9 @@ public actor HTTPClientTransport: Transport {
480496
request.addValue(sessionID, forHTTPHeaderField: "Mcp-Session-Id")
481497
}
482498

499+
// Apply request modifier
500+
request = requestModifier(request)
501+
483502
logger.debug("Starting SSE connection")
484503

485504
// Create URLSession task for SSE

0 commit comments

Comments
 (0)