Skip to content

Commit 93d631f

Browse files
committed
Now with async support the MCPServer can also be an actor
1 parent 21ae9ef commit 93d631f

File tree

5 files changed

+60
-66
lines changed

5 files changed

+60
-66
lines changed

Demos/SwiftMCPDemo/Calculator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import SwiftMCP
55
A Calculator for simple math doing additionals, subtractions etc.
66
*/
77
@MCPServer(name: "SwiftMCP Demo")
8-
class Calculator {
8+
actor Calculator {
99
/// Adds two integers and returns their sum
1010
/// - Parameter a: First number to add
1111
/// - Parameter b: Second number to add

Sources/SwiftMCP/Protocols/MCPServer.swift

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ public protocol MCPServer: AnyObject {
77
var mcpTools: [MCPTool] { get }
88

99
/// Returns an array of all MCP resources defined in this type
10-
var mcpResources: [MCPResource] { get }
10+
var mcpResources: [MCPResource] { get async }
1111

1212
/// Returns an array of all MCP resource templates defined in this type
13-
var mcpResourceTemplates: [MCPResourceTemplate] { get }
13+
var mcpResourceTemplates: [MCPResourceTemplate] { get async }
1414

1515

1616
/// Calls a tool by name with the provided arguments
@@ -25,7 +25,7 @@ public protocol MCPServer: AnyObject {
2525
/// - Parameter uri: The URI of the resource to get
2626
/// - Returns: The resource content, or nil if the resource doesn't exist
2727
/// - Throws: MCPResourceError if there's an error getting the resource
28-
func getResource(uri: URL) throws -> MCPResourceContent?
28+
func getResource(uri: URL) async throws -> MCPResourceContent?
2929

3030
/// Handles a JSON-RPC request
3131
/// - Parameter request: The JSON-RPC request to handle
@@ -62,13 +62,13 @@ public extension MCPServer {
6262
return createToolsResponse(id: request.id ?? 0)
6363

6464
case "resources/list":
65-
return createResourcesListResponse(id: request.id ?? 0)
65+
return await createResourcesListResponse(id: request.id ?? 0)
6666

6767
case "resources/templates/list":
68-
return createResourceTemplatesListResponse(id: request.id ?? 0)
68+
return await createResourceTemplatesListResponse(id: request.id ?? 0)
6969

7070
case "resources/read":
71-
return createResourcesReadResponse(id: request.id ?? 0, request: request)
71+
return await createResourcesReadResponse(id: request.id ?? 0, request: request)
7272

7373
case "tools/call":
7474
return await handleToolCall(request)
@@ -101,9 +101,9 @@ public extension MCPServer {
101101
/// Creates a resources list response
102102
/// - Parameter id: The request ID
103103
/// - Returns: The resources list response
104-
func createResourcesListResponse(id: Int) -> JSONRPC.Response {
104+
func createResourcesListResponse(id: Int) async -> JSONRPC.Response {
105105
// Convert MCPResource objects to dictionaries
106-
let resourceDicts = mcpResources.map { resource -> [String: Any] in
106+
let resourceDicts = await mcpResources.map { resource -> [String: Any] in
107107
return [
108108
"uri": resource.uri.absoluteString,
109109
"name": resource.name,
@@ -194,7 +194,7 @@ public extension MCPServer {
194194
/// - id: The request ID
195195
/// - request: The original JSON-RPC request
196196
/// - Returns: The resources read response
197-
func createResourcesReadResponse(id: Int, request: JSONRPCRequest) -> JSONRPC.Response {
197+
func createResourcesReadResponse(id: Int, request: JSONRPCRequest) async -> JSONRPC.Response {
198198
// Extract the URI from the request params
199199
guard let uriString = request.params?["uri"]?.value as? String,
200200
let uri = URL(string: uriString) else {
@@ -207,7 +207,7 @@ public extension MCPServer {
207207

208208
do {
209209
// Try to get the resource content
210-
if let resourceContent = try getResource(uri: uri) {
210+
if let resourceContent = try await getResource(uri: uri) {
211211
// Convert MCPResourceContent to dictionary
212212
var contentDict: [String: Any] = [
213213
"uri": resourceContent.uri.absoluteString
@@ -252,9 +252,9 @@ public extension MCPServer {
252252
/// Creates a resource templates list response
253253
/// - Parameter id: The request ID
254254
/// - Returns: The resource templates list response
255-
func createResourceTemplatesListResponse(id: Int) -> JSONRPC.Response {
255+
func createResourceTemplatesListResponse(id: Int) async -> JSONRPC.Response {
256256
// Convert MCPResourceTemplate objects to dictionaries
257-
let templateDicts = mcpResourceTemplates.map { template -> [String: Any] in
257+
let templateDicts = await mcpResourceTemplates.map { template -> [String: Any] in
258258
return [
259259
"uriTemplate": template.uriTemplate.absoluteString,
260260
"name": template.name,
@@ -296,7 +296,7 @@ public extension MCPServer {
296296
}
297297

298298
/// Default implementation
299-
func getResource(uri: URL) throws -> MCPResourceContent? {
299+
func getResource(uri: URL) async throws -> MCPResourceContent? {
300300
return nil
301301
}
302302

Sources/SwiftMCPMacros/MCPServerDiagnostics.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,26 @@ import SwiftSyntaxMacros
1212
*/
1313
enum MCPServerDiagnostic: DiagnosticMessage {
1414
/// Error when the macro is applied to a non-class declaration
15-
case requiresClass(typeName: String, actualType: String)
15+
case requiresReferenceType(typeName: String)
1616

1717
var message: String {
1818
switch self {
19-
case .requiresClass(let typeName, let actualType):
20-
return "MCPServer can only be applied to classes, but '\(typeName)' is a \(actualType)"
19+
case .requiresReferenceType(let typeName):
20+
return "'\(typeName)' must be a reference type (class or actor)"
2121
}
2222
}
2323

2424
var severity: DiagnosticSeverity {
2525
switch self {
26-
case .requiresClass:
26+
case .requiresReferenceType:
2727
return .error
2828
}
2929
}
3030

3131
var diagnosticID: MessageID {
3232
switch self {
33-
case .requiresClass:
34-
return MessageID(domain: "SwiftMCP", id: "requiresClass")
33+
case .requiresReferenceType:
34+
return MessageID(domain: "SwiftMCPMacros", id: "RequiresReferenceType")
3535
}
3636
}
3737
}
@@ -43,14 +43,14 @@ enum MCPServerFixItMessage: FixItMessage {
4343
var message: String {
4444
switch self {
4545
case .replaceWithClass(let keyword):
46-
return "Replace '\(keyword)' with 'class'"
46+
return "Change '\(keyword)' to 'class'"
4747
}
4848
}
4949

5050
var fixItID: MessageID {
5151
switch self {
5252
case .replaceWithClass:
53-
return MessageID(domain: "SwiftMCP", id: "replaceWithClass")
53+
return MessageID(domain: "SwiftMCPMacros", id: "ReplaceWithClass")
5454
}
5555
}
5656
}

Sources/SwiftMCPMacros/MCPServerMacro.swift

Lines changed: 37 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import SwiftDiagnostics
1414
/**
1515
Implementation of the MCPServer macro.
1616

17-
This macro adds MCPServer protocol conformance to a class and generates the necessary
17+
This macro adds MCPServer protocol conformance and generates the necessary
1818
infrastructure for handling MCP tools.
1919

2020
Example usage:
@@ -29,14 +29,28 @@ import SwiftDiagnostics
2929
}
3030
```
3131

32-
- Note: The server description is automatically extracted from the class's documentation comment.
32+
Or with an actor:
33+
```swift
34+
/// A server that provides calculator functionality
35+
@MCPServer(
36+
name: "calculator",
37+
version: "1.0"
38+
)
39+
actor CalculatorServer {
40+
// MCP tool functions go here
41+
}
42+
```
43+
44+
- Note: The server description is automatically extracted from the documentation comment.
3345

3446
- Parameters:
35-
- name: The name of the server. Defaults to the class name.
47+
- name: The name of the server. Defaults to the declaration name.
3648
- version: The version of the server. Defaults to "1.0".
3749

38-
- Attention: This macro can only be applied to classes. Using it on a struct or actor
39-
will result in a diagnostic with a fix-it to convert to a class.
50+
- Throws: MCPToolError if a tool cannot be found or called
51+
52+
- Attention: This macro can only be applied to reference types (classes or actors).
53+
Using it on a struct will result in a diagnostic with a fix-it to convert to a class.
4054
*/
4155
public struct MCPServerMacro: MemberMacro, ExtensionMacro {
4256
/**
@@ -54,44 +68,24 @@ public struct MCPServerMacro: MemberMacro, ExtensionMacro {
5468
providingMembersOf declaration: some DeclGroupSyntax,
5569
in context: some MacroExpansionContext
5670
) throws -> [DeclSyntax] {
57-
// Check if the declaration is a class
58-
guard declaration.is(ClassDeclSyntax.self) else {
59-
// If it's a struct or actor, emit a diagnostic with a fix-it
60-
if let structDecl = declaration.as(StructDeclSyntax.self) {
61-
let diagnostic = SwiftDiagnostics.Diagnostic(
62-
node: Syntax(structDecl.structKeyword),
63-
message: MCPServerDiagnostic.requiresClass(typeName: structDecl.name.text, actualType: "struct"),
64-
fixIts: [
65-
FixIt(
66-
message: MCPServerFixItMessage.replaceWithClass(keyword: "struct"),
67-
changes: [
68-
.replace(
69-
oldNode: Syntax(structDecl.structKeyword),
70-
newNode: Syntax(TokenSyntax.keyword(.class))
71-
)
72-
]
73-
)
74-
]
75-
)
76-
context.diagnose(diagnostic)
77-
} else if let actorDecl = declaration.as(ActorDeclSyntax.self) {
78-
let diagnostic = SwiftDiagnostics.Diagnostic(
79-
node: Syntax(actorDecl.actorKeyword),
80-
message: MCPServerDiagnostic.requiresClass(typeName: actorDecl.name.text, actualType: "actor"),
81-
fixIts: [
82-
FixIt(
83-
message: MCPServerFixItMessage.replaceWithClass(keyword: "actor"),
84-
changes: [
85-
.replace(
86-
oldNode: Syntax(actorDecl.actorKeyword),
87-
newNode: Syntax(TokenSyntax.keyword(.class))
88-
)
89-
]
90-
)
91-
]
92-
)
93-
context.diagnose(diagnostic)
94-
}
71+
// Check if the declaration is a class or actor
72+
if let structDecl = declaration.as(StructDeclSyntax.self) {
73+
let diagnostic = SwiftDiagnostics.Diagnostic(
74+
node: Syntax(structDecl.structKeyword),
75+
message: MCPServerDiagnostic.requiresReferenceType(typeName: structDecl.name.text),
76+
fixIts: [
77+
FixIt(
78+
message: MCPServerFixItMessage.replaceWithClass(keyword: "struct"),
79+
changes: [
80+
.replace(
81+
oldNode: Syntax(structDecl.structKeyword),
82+
newNode: Syntax(TokenSyntax.keyword(.class))
83+
)
84+
]
85+
)
86+
]
87+
)
88+
context.diagnose(diagnostic)
9589
return []
9690
}
9791

Sources/SwiftMCPMacros/MCPToolMacro.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ public struct MCPToolMacro: PeerMacro {
250250
var wrapperMethod = """
251251
252252
/// Autogenerated wrapper for \(functionName) that takes a dictionary of parameters
253-
nonisolated func __mcpCall_\(functionName)(_ params: [String: Any]) async throws -> Codable {
253+
func __mcpCall_\(functionName)(_ params: [String: Any]) async throws -> Codable {
254254
"""
255255

256256
// Add parameter extraction code

0 commit comments

Comments
 (0)