Skip to content

Commit f7e83dc

Browse files
committed
Checkpoing
1 parent b4585ef commit f7e83dc

File tree

4 files changed

+111
-143
lines changed

4 files changed

+111
-143
lines changed

Sources/SwiftMCP/Models/Tools/ToolCallResponse.swift

Lines changed: 0 additions & 48 deletions
This file was deleted.

Sources/SwiftMCP/Models/Tools/ToolsListResponse.swift

Lines changed: 0 additions & 25 deletions
This file was deleted.

Sources/SwiftMCP/Protocols/MCPServer.swift

Lines changed: 71 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,39 @@
11
import Foundation
22
import AnyCodable
33

4-
/// Protocol defining the requirements for an MCP server
5-
public protocol MCPServer: AnyObject {
6-
/// Returns an array of all MCP tools defined in this type
4+
/// Protocol defining the interface for an MCP server
5+
public protocol MCPServer {
6+
/// The tools available on this server
77
var mcpTools: [MCPTool] { get }
88

9-
/// Returns an array of all MCP resources defined in this type
9+
/// The resources available on this server
1010
var mcpResources: [MCPResource] { get async }
11-
12-
/// Returns an array of all MCP resource templates defined in this type
13-
var mcpResourceTemplates: [MCPResourceTemplate] { get async }
14-
1511

16-
/// Calls a tool by name with the provided arguments
12+
/// The resource templates available on this server
13+
var mcpResourceTemplates: [MCPResourceTemplate] { get async }
14+
15+
/// The name of the server
16+
var name: String { get }
17+
18+
/// The version of the server
19+
var version: String { get }
20+
21+
/// The description of the server
22+
var serverDescription: String? { get }
23+
24+
/// Get a resource by its URI
25+
/// - Parameter uri: The URI of the resource to get
26+
/// - Returns: The resource content, or nil if not found
27+
func getResource(uri: URL) async throws -> MCPResourceContent?
28+
29+
/// Call a tool by name with arguments
1730
/// - Parameters:
1831
/// - name: The name of the tool to call
19-
/// - arguments: A dictionary of arguments to pass to the tool
32+
/// - arguments: The arguments to pass to the tool
2033
/// - Returns: The result of the tool call
21-
/// - Throws: MCPToolError if the tool doesn't exist or cannot be called
2234
func callTool(_ name: String, arguments: [String: Any]) async throws -> Codable
2335

24-
/// Gets a resource by URI
25-
/// - Parameter uri: The URI of the resource to get
26-
/// - Returns: The resource content, or nil if the resource doesn't exist
27-
/// - Throws: MCPResourceError if there's an error getting the resource
28-
func getResource(uri: URL) async throws -> MCPResourceContent?
29-
30-
/// Handles a JSON-RPC request
36+
/// Handle a JSON-RPC request
3137
/// - Parameter request: The JSON-RPC request to handle
3238
/// - Returns: The response as a string, or nil if no response should be sent
3339
func handleRequest(_ request: JSONRPCMessage) async -> JSONRPCMessage?
@@ -134,45 +140,53 @@ public extension MCPServer {
134140
/// Handles a tool call request
135141
/// - Parameter request: The JSON-RPC request for a tool call
136142
/// - Returns: The response as a string, or nil if no response should be sent
137-
private func handleToolCall(_ request: JSONRPCMessage) async -> JSONRPCMessage? {
138-
guard let params = request.params,
139-
let toolName = params["name"]?.value as? String else {
140-
// Invalid request: missing tool name
141-
return nil
142-
}
143-
144-
// Extract arguments from the request
145-
let arguments = (params["arguments"]?.value as? [String: Any]) ?? [:]
146-
147-
// Call the appropriate wrapper method based on the tool name
148-
do {
149-
let result = try await self.callTool(toolName, arguments: arguments)
150-
let responseText: String
151-
152-
// Use Mirror to check if the result is Void
153-
let mirror = Mirror(reflecting: result)
154-
if mirror.displayStyle == .tuple && mirror.children.isEmpty {
155-
responseText = "" // Convert Void to empty string
156-
} else {
157-
responseText = "\(result)"
158-
}
159-
160-
var response = JSONRPCMessage()
161-
response.id = request.id
162-
response.result = [
163-
"content": AnyCodable([
164-
["type": "text", "text": responseText]
165-
])
166-
]
167-
return response
168-
169-
} catch {
170-
var response = JSONRPCMessage()
171-
response.id = request.id
172-
response.error = .init(code: -32000, message: error.localizedDescription)
173-
return response
174-
}
175-
}
143+
private func handleToolCall(_ request: JSONRPCMessage) async -> JSONRPCMessage? {
144+
guard let params = request.params,
145+
let toolName = params["name"]?.value as? String else {
146+
// Invalid request: missing tool name
147+
return nil
148+
}
149+
150+
// Extract arguments from the request
151+
let arguments = (params["arguments"]?.value as? [String: Any]) ?? [:]
152+
153+
// Call the appropriate wrapper method based on the tool name
154+
do {
155+
let result = try await self.callTool(toolName, arguments: arguments)
156+
let responseText: String
157+
158+
// Use Mirror to check if the result is Void
159+
let mirror = Mirror(reflecting: result)
160+
if mirror.displayStyle == .tuple && mirror.children.isEmpty {
161+
responseText = "" // Convert Void to empty string
162+
} else {
163+
responseText = "\(result)"
164+
}
165+
166+
var response = JSONRPCMessage()
167+
response.jsonrpc = "2.0"
168+
response.id = request.id
169+
response.result = [
170+
"content": AnyCodable([
171+
["type": "text", "text": responseText]
172+
]),
173+
"isError": AnyCodable(false)
174+
]
175+
return response
176+
177+
} catch {
178+
var response = JSONRPCMessage()
179+
response.jsonrpc = "2.0"
180+
response.id = request.id
181+
response.result = [
182+
"content": AnyCodable([
183+
["type": "text", "text": error.localizedDescription]
184+
]),
185+
"isError": AnyCodable(true)
186+
]
187+
return response
188+
}
189+
}
176190

177191
/// Function to log a message to stderr
178192
func logToStderr(_ message: String) {

Tests/SwiftMCPTests/MCPServerTests.swift

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ func testToolCallRequest() async throws {
114114
#expect(response.id == 3)
115115
#expect(response.result != nil)
116116
#expect(response.params == nil)
117+
#expect(response.error == nil)
117118

118119
guard let result = response.result else {
119120
throw TestError("Result is missing")
@@ -123,7 +124,7 @@ func testToolCallRequest() async throws {
123124
throw TestError("Content not found or not an array")
124125
}
125126

126-
#expect(!content.isEmpty)
127+
#expect(content.count == 1)
127128
#expect(content[0]["type"] == "text")
128129
#expect(content[0]["text"] == "5")
129130
}
@@ -148,16 +149,29 @@ func testToolCallRequestWithError() async throws {
148149

149150
#expect(response.jsonrpc == "2.0")
150151
#expect(response.id == 4)
151-
#expect(response.error != nil)
152-
#expect(response.result == nil)
152+
#expect(response.result != nil)
153153
#expect(response.params == nil)
154+
#expect(response.error == nil)
154155

155-
guard let error = response.error else {
156-
throw TestError("Error is missing")
156+
guard let result = response.result else {
157+
throw TestError("Result is missing")
157158
}
158159

159-
#expect(error.code == -32000)
160-
#expect(error.message.contains("not found on the server"))
160+
guard let content = result["content"]?.value as? [[String: String]] else {
161+
throw TestError("Content not found or not an array")
162+
}
163+
164+
#expect(content.count == 1)
165+
#expect(content[0]["type"] == "text")
166+
guard let text = content[0]["text"] else {
167+
throw TestError("Text field missing in content")
168+
}
169+
#expect(text.contains("not found on the server"))
170+
171+
guard let isError = result["isError"]?.value as? Bool else {
172+
throw TestError("isError flag not found")
173+
}
174+
#expect(isError)
161175
}
162176

163177
@Test
@@ -183,16 +197,29 @@ func testToolCallRequestWithInvalidArgument() async throws {
183197

184198
#expect(response.jsonrpc == "2.0")
185199
#expect(response.id == 5)
186-
#expect(response.error != nil)
187-
#expect(response.result == nil)
200+
#expect(response.result != nil)
188201
#expect(response.params == nil)
202+
#expect(response.error == nil)
189203

190-
guard let error = response.error else {
191-
throw TestError("Error is missing")
204+
guard let result = response.result else {
205+
throw TestError("Result is missing")
192206
}
193207

194-
#expect(error.code == -32000)
195-
#expect(error.message.contains("expected type Int"))
208+
guard let content = result["content"]?.value as? [[String: String]] else {
209+
throw TestError("Content not found or not an array")
210+
}
211+
212+
#expect(content.count == 1)
213+
#expect(content[0]["type"] == "text")
214+
guard let text = content[0]["text"] else {
215+
throw TestError("Text field missing in content")
216+
}
217+
#expect(text.contains("expected type Int"))
218+
219+
guard let isError = result["isError"]?.value as? Bool else {
220+
throw TestError("isError flag not found")
221+
}
222+
#expect(isError)
196223
}
197224

198225
@Test("Custom Name and Version")

0 commit comments

Comments
 (0)