agent-rules-kit
Version:
Bootstrap of **Cursor** rules (`.mdc`) and mirror documentation (`.md`) for AI agent-guided projects.
626 lines (522 loc) • 18.1 kB
Markdown
---
globs: <root>/**/*.swift,<root>/Package.swift,<root>/*.xcodeproj
alwaysApply: false
---
# MCP Swift SDK Implementation Guide
## Swift SDK Features
The Swift SDK provides modern, type-safe MCP implementations with async/await, strong memory management, and excellent Apple ecosystem integration.
### Package Configuration
```swift
// Package.swift
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "MCPSwiftServer",
platforms: [
.macOS(.v13),
.iOS(.v16),
],
products: [
.executable(name: "MCPSwiftServer", targets: ["MCPSwiftServer"]),
.library(name: "MCPSwiftSDK", targets: ["MCPSwiftSDK"]),
],
dependencies: [
.package(url: "https://github.com/modelcontextprotocol/swift-sdk", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-log", from: "1.5.0"),
.package(url: "https://github.com/vapor/vapor", from: "4.89.0"),
],
targets: [
.executableTarget(
name: "MCPSwiftServer",
dependencies: [
"MCPSwiftSDK",
.product(name: "Logging", package: "swift-log"),
]
),
.target(
name: "MCPSwiftSDK",
dependencies: [
.product(name: "ModelContextProtocol", package: "swift-sdk"),
.product(name: "Logging", package: "swift-log"),
]
),
.testTarget(
name: "MCPSwiftSDKTests",
dependencies: ["MCPSwiftSDK"]
),
]
)
```
### Basic Server Implementation
```swift
import ModelContextProtocol
import Foundation
import Logging
struct EchoArguments: Codable {
let message: String
}
struct CalculateArguments: Codable {
let expression: String
let precision: Int?
}
final class SwiftMCPServer: MCPServerHandler {
private let logger: Logger
private var resourceStorage: [String: String] = [:]
private let queue = DispatchQueue(label: "mcp.server.queue", qos: .userInitiated)
init(logger: Logger = Logger(label: "mcp.swift.server")) {
self.logger = logger
initializeResources()
}
private func initializeResources() {
resourceStorage["file://example.txt"] = "Swift MCP Server content"
resourceStorage["file://config.json"] = #"{"platform": "Swift", "version": "1.0.0"}"#
resourceStorage["file://data.plist"] = """
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>sample</key>
<string>data</string>
</dict>
</plist>
"""
}
// MARK: - Resource Management
func listResources() async throws -> [Resource] {
logger.info("Listing \(resourceStorage.count) resources")
return resourceStorage.keys.map { uri in
Resource(
uri: uri,
name: URL(string: uri)?.lastPathComponent ?? uri,
description: "Sample resource from Swift server",
mimeType: determineMimeType(for: uri)
)
}
}
func readResource(uri: String) async throws -> ResourceContent {
guard let content = resourceStorage[uri] else {
throw MCPError.resourceNotFound("Resource not found: \(uri)")
}
logger.info("Reading resource: \(uri)")
return ResourceContent(
uri: uri,
mimeType: determineMimeType(for: uri),
text: content
)
}
// MARK: - Tool Management
func listTools() async throws -> [Tool] {
return [
Tool(
name: "echo",
description: "Echo back the input message",
inputSchema: JSONSchema(
type: .object,
properties: [
"message": JSONSchema(
type: .string,
description: "Message to echo back"
)
],
required: ["message"]
)
),
Tool(
name: "calculate",
description: "Perform mathematical calculations",
inputSchema: JSONSchema(
type: .object,
properties: [
"expression": JSONSchema(
type: .string,
description: "Mathematical expression to evaluate"
),
"precision": JSONSchema(
type: .number,
description: "Decimal precision for the result",
defaultValue: 2
)
],
required: ["expression"]
)
),
Tool(
name: "system-info",
description: "Get system information",
inputSchema: JSONSchema(
type: .object,
properties: [:],
required: []
)
)
]
}
func callTool(name: String, arguments: [String: Any]) async throws -> ToolResult {
logger.info("Calling tool: \(name) with \(arguments.count) arguments")
switch name {
case "echo":
return try await handleEcho(arguments: arguments)
case "calculate":
return try await handleCalculate(arguments: arguments)
case "system-info":
return try await handleSystemInfo()
default:
throw MCPError.toolNotFound("Unknown tool: \(name)")
}
}
// MARK: - Tool Handlers
private func handleEcho(arguments: [String: Any]) async throws -> ToolResult {
guard let message = arguments["message"] as? String else {
throw MCPError.invalidRequest("Message parameter is required")
}
return ToolResult(
content: [
TextContent(
type: "text",
text: "Echo: \(message)"
)
]
)
}
private func handleCalculate(arguments: [String: Any]) async throws -> ToolResult {
guard let expression = arguments["expression"] as? String else {
throw MCPError.invalidRequest("Expression parameter is required")
}
let precision = arguments["precision"] as? Int ?? 2
do {
// Simple expression evaluation using NSExpression
let nsExpression = NSExpression(format: expression)
guard let result = nsExpression.expressionValue(with: nil, context: nil) as? NSNumber else {
throw MCPError.internalError("Failed to evaluate expression")
}
let formattedResult = String(format: "%.\(precision)f", result.doubleValue)
return ToolResult(
content: [
TextContent(
type: "text",
text: "Result: \(formattedResult)"
)
]
)
} catch {
logger.error("Error calculating expression: \(error)")
return ToolResult(
content: [
TextContent(
type: "text",
text: "Error: \(error.localizedDescription)"
)
],
isError: true
)
}
}
private func handleSystemInfo() async throws -> ToolResult {
let processInfo = ProcessInfo.processInfo
let systemInfo = """
System Information:
- OS: \(processInfo.operatingSystemVersionString)
- Host: \(processInfo.hostName)
- Process: \(processInfo.processName) (PID: \(processInfo.processIdentifier))
- Memory: \(ByteCountFormatter.string(fromByteCount: Int64(processInfo.physicalMemory), countStyle: .memory))
- Processors: \(processInfo.processorCount)
- Uptime: \(String(format: "%.2f", processInfo.systemUptime)) seconds
"""
return ToolResult(
content: [
TextContent(
type: "text",
text: systemInfo
)
]
)
}
// MARK: - Utilities
private func determineMimeType(for uri: String) -> String {
let url = URL(string: uri)
let pathExtension = url?.pathExtension.lowercased() ?? ""
switch pathExtension {
case "json":
return "application/json"
case "txt", "md":
return "text/plain"
case "plist":
return "application/x-plist"
case "xml":
return "application/xml"
case "swift":
return "text/x-swift"
default:
return "application/octet-stream"
}
}
}
// MARK: - Server Startup
@main
struct MCPServerApp {
static func main() async {
let logger = Logger(label: "mcp.swift.server.main")
do {
let serverHandler = SwiftMCPServer(logger: logger)
let server = MCPServer(
name: "swift-mcp-server",
version: "1.0.0",
capabilities: ServerCapabilities(
resources: ResourceCapabilities(
subscribe: true,
listChanged: true
),
tools: ToolCapabilities(
listChanged: true
)
),
handler: serverHandler
)
let transport = StdioTransport()
logger.info("Starting Swift MCP Server...")
try await server.start(transport: transport)
} catch {
logger.error("Server error: \(error)")
exit(1)
}
}
}
```
### Client Implementation
```swift
import ModelContextProtocol
import Foundation
import Logging
final class SwiftMCPClient {
private let client: MCPClient
private let logger: Logger
private var transport: StdioTransport?
init(logger: Logger = Logger(label: "mcp.swift.client")) {
self.logger = logger
self.client = MCPClient(
name: "swift-mcp-client",
version: "1.0.0",
capabilities: ClientCapabilities()
)
}
func connect(serverCommand: String, arguments: [String] = []) async throws {
transport = StdioTransport(command: serverCommand, arguments: arguments)
try await client.connect(transport: transport!)
logger.info("Connected to MCP server")
}
func listResources() async throws -> [Resource] {
return try await client.listResources()
}
func readResource(uri: String) async throws -> ResourceContent {
return try await client.readResource(uri: uri)
}
func listTools() async throws -> [Tool] {
return try await client.listTools()
}
func callTool(name: String, arguments: [String: Any]) async throws -> ToolResult {
return try await client.callTool(name: name, arguments: arguments)
}
func disconnect() async throws {
try await client.disconnect()
logger.info("Disconnected from MCP server")
}
}
// Usage example
func clientExample() async throws {
let client = SwiftMCPClient()
do {
try await client.connect(
serverCommand: "swift",
arguments: ["run", "MCPSwiftServer"]
)
// List resources
let resources = try await client.listResources()
print("Resources: \(resources.map(\.name).joined(separator: ", "))")
// Read a resource
if let firstResource = resources.first {
let content = try await client.readResource(uri: firstResource.uri)
print("Resource content: \(content.text?.prefix(100) ?? "No content")")
}
// List and call tools
let tools = try await client.listTools()
print("Available tools: \(tools.map(\.name).joined(separator: ", "))")
// Call echo tool
let echoResult = try await client.callTool(
name: "echo",
arguments: ["message": "Hello from Swift client!"]
)
print("Echo result: \(echoResult.content.first?.text ?? "No response")")
// Call system info tool
let systemResult = try await client.callTool(
name: "system-info",
arguments: [:]
)
print("System info: \(systemResult.content.first?.text ?? "No info")")
} catch {
print("Client error: \(error)")
} finally {
try? await client.disconnect()
}
}
```
### Advanced Features with Actors
```swift
import Foundation
actor ResourceCache {
private var cache: [String: CachedResource] = [:]
private let maxSize: Int
private let ttl: TimeInterval
struct CachedResource {
let content: String
let timestamp: Date
let accessCount: Int
}
init(maxSize: Int = 100, ttl: TimeInterval = 300) {
self.maxSize = maxSize
self.ttl = ttl
}
func get(_ key: String) -> String? {
guard let cached = cache[key] else { return nil }
// Check if expired
if Date().timeIntervalSince(cached.timestamp) > ttl {
cache.removeValue(forKey: key)
return nil
}
// Update access count
cache[key] = CachedResource(
content: cached.content,
timestamp: cached.timestamp,
accessCount: cached.accessCount + 1
)
return cached.content
}
func set(_ key: String, value: String) {
// Evict if at capacity
if cache.count >= maxSize {
evictLRU()
}
cache[key] = CachedResource(
content: value,
timestamp: Date(),
accessCount: 1
)
}
private func evictLRU() {
guard let lruKey = cache.min(by: { $0.value.accessCount < $1.value.accessCount })?.key else {
return
}
cache.removeValue(forKey: lruKey)
}
}
// Enhanced server with caching
final class CachedSwiftMCPServer: MCPServerHandler {
private let cache = ResourceCache()
private let logger: Logger
init(logger: Logger = Logger(label: "mcp.swift.cached")) {
self.logger = logger
}
func readResource(uri: String) async throws -> ResourceContent {
// Check cache first
if let cachedContent = await cache.get(uri) {
logger.info("Cache hit for resource: \(uri)")
return ResourceContent(
uri: uri,
mimeType: determineMimeType(for: uri),
text: cachedContent
)
}
// Load from storage
let content = try await loadResourceFromStorage(uri: uri)
// Cache the result
await cache.set(uri, value: content)
return ResourceContent(
uri: uri,
mimeType: determineMimeType(for: uri),
text: content
)
}
private func loadResourceFromStorage(uri: String) async throws -> String {
// Simulate async loading
try await Task.sleep(nanoseconds: 100_000_000) // 100ms
return "Loaded content for \(uri)"
}
}
```
### Testing with Swift Testing
```swift
import Testing
import Foundation
@testable import MCPSwiftSDK
("Server should list resources")
func testListResources() async throws {
let server = SwiftMCPServer()
let resources = try await server.listResources()
#expect(!resources.isEmpty)
#expect(resources.contains { $0.name == "example.txt" })
}
("Echo tool should return echoed message")
func testEchoTool() async throws {
let server = SwiftMCPServer()
let result = try await server.callTool(
name: "echo",
arguments: ["message": "test message"]
)
#expect(result.isError == false)
#expect(result.content.first?.text == "Echo: test message")
}
("Calculate tool should perform arithmetic")
func testCalculateTool() async throws {
let server = SwiftMCPServer()
let result = try await server.callTool(
name: "calculate",
arguments: ["expression": "2 + 2", "precision": 0]
)
#expect(result.isError == false)
#expect(result.content.first?.text?.contains("4") == true)
}
("Resource not found should throw error")
func testResourceNotFound() async throws {
let server = SwiftMCPServer()
await #expect(throws: MCPError.self) {
try await server.readResource(uri: "file://nonexistent.txt")
}
}
```
### Vapor Integration
```swift
import Vapor
import ModelContextProtocol
func configure(_ app: Application) throws {
let mcpHandler = SwiftMCPServer()
let mcpServer = MCPServer(
name: "vapor-mcp-server",
version: "1.0.0",
capabilities: ServerCapabilities(
resources: ResourceCapabilities(subscribe: true),
tools: ToolCapabilities(listChanged: true)
),
handler: mcpHandler
)
// Start MCP server on WebSocket transport
let wsTransport = WebSocketTransport(host: "localhost", port: 8080, path: "/mcp")
Task {
try await mcpServer.start(transport: wsTransport)
}
// Regular HTTP routes
app.get("health") { req in
return ["status": "running", "server": "vapor-mcp-server"]
}
}
@main
enum Entrypoint {
static func main() async throws {
var env = try Environment.detect()
try LoggingSystem.bootstrap(from: &env)
let app = Application(env)
defer { app.shutdown() }
try configure(app)
try await app.execute()
}
}
```
This Swift SDK implementation provides modern, type-safe MCP applications with excellent performance, memory safety, and seamless Apple ecosystem integration.