tannerdsilva/swift-mcp
A Swift framework for building MCP (Model Context Protocol) servers with a declarative, property-wrapper-based API.
Overview
swift-mcp lets you define MCP tools using property wrappers (@Argument, @Option, @Flag, @OptionGroup) and macros (@MCPCommand, @FuncTool, @MCPApplication, @MCPOptionGroup), then host them with an MCPServer run through Swift Service Lifecycle.
Quick Start
import MCP
enum MyTools {
@FuncTool(description: "Greet someone by name")
static func greet(name: String, count: Int = 1, formal: Bool = false) -> String {
let greeting = formal ? "Greetings" : "Hello"
return Array(repeating: "\(greeting), \(name)!", count: count)
.joined(separator: "\n")
}
}
let server = MCPServer(name: "demo", version: "1.0.0") {
MyTools.greetTool()
}
try await server.runService()Features
@MCPCommandmacro — generate anMCPToolconformance from a struct with arun()method@FuncToolmacro — generate MCP tools from plain functions@MCPApplicationmacro — full server entry point generation with exhaustive, type-preserving dispatch- Property wrappers —
@Argument,@Option,@Flag,@OptionGroup - Automatic JSON Schema — tool parameters are described in JSON Schema Draft 7
- Compile-time discovery — parameters are discovered via macro-generated code, no runtime reflection
- Codable parameter types — custom enums, structs, and optionals decode from their JSON representation
- Option groups — share common parameters across tools with
@OptionGroup+@MCPOptionGroup - Stdio transport — standard MCP transport for subprocess-based clients
- TCP transport — IPv4, IPv6, dual-stack, and Unix domain socket support
- Access control — per-tool access levels with IP-based resolution
- Enum constraints — optional
enumValuesparameter for JSON Schema enum constraints - Dynamic registration — register and unregister tools at runtime with
register(:)andunregister(:) - Structured logging —
trace/debug/info/warning/errorvia Swift Logging - Actor-based concurrency —
TransportMessageHandlerfor serialized, cancellable message processing
Table of Contents
Installation
Add the package to your Package.swift:
dependencies: [
.package(url: "https://github.com/tannerdsilva/swift-mcp.git", from: "1.0.0")
],
targets: [
.target(
name: "MyTool",
dependencies: [
.product(name: "MCP", package: "swift-mcp"),
]
),
]The macro implementation ships with the MCP product — no separate macro dependency is required.
Usage
Direct MCPTool conformance
import MCP
struct GetWeather: MCPTool {
static let configuration = MCPToolConfiguration(
description: "Get the current weather for a location"
)
@Argument(description: "The city name")
var city: String = ""
@Option(description: "Temperature unit (celsius/fahrenheit)")
var unit: String = "celsius"
func invoke(context: MCPContext) async throws -> MCPToolResult {
.text("The weather in \(city) is sunny and 22°\(unit == "celsius" ? "C" : "F")")
}
}@FuncTool (function-based)
Apply @FuncTool to a function to get an MCPTool-conforming struct:
enum MyTools {
@FuncTool(description: "Calculate the sum of two numbers")
static func add(a: Double, b: Double) -> String {
"\(a + b)"
}
}
// Parameters without defaults → @Argument (required)
// Parameters with defaults → @Option (optional)
// Bool parameters with default false → @Flag
let server = MCPServer(name: "calc", version: "1.0.0") {
MyTools.addTool()
}
try await server.runService()The generated struct is named {FunctionName}Tool (e.g. addTool). Because @FuncTool is a peer macro, the annotated function must be a static member of a type — the compiler forbids arbitrary-name peer macros at file scope, and the generated tool calls the function unqualified. Any return type is supported (rendered via String(describing:)); Void-returning functions produce an empty text block. See the Macro Guide for the parameter constraints.
@MCPCommand (struct-based)
Use @MCPCommand to generate an MCPTool conformance from a struct:
@MCPCommand(description: "Calculate")
struct Calculate {
@Argument(description: "First number")
var a: Double = 0
@Argument(description: "Second number")
var b: Double = 0
@Option(description: "Operation: add, subtract, multiply, divide")
var operation: String = "add"
func run() async throws -> String {
switch operation {
case "add": return "\(a + b)"
case "subtract": return "\(a - b)"
case "multiply": return "\(a * b)"
case "divide":
guard b != 0 else { throw MCPError.internalError("Division by zero") }
return "\(a / b)"
default:
throw MCPError.internalError("Unknown operation: \(operation)")
}
}
}
let server = MCPServer(name: "calc", version: "1.0.0") { Calculate() }Option groups
Share common parameters across tools:
@MCPOptionGroup
struct SharedOptions {
@Option(description: "Enable verbose output")
var verbose: Bool = false
@Option(description: "Output path")
var outputPath: String = "."
}
@MCPCommand(description: "Process data")
struct ProcessData {
@OptionGroup var options: SharedOptions
@Argument(description: "Input file")
var inputFile: String = ""
func run() async throws -> String {
// options.verbose, options.outputPath available
return "Processing \(inputFile)..."
}
}Server setup
// Imperative registration
let server = MCPServer(name: "myserver", version: "1.0.0")
server.register(GetWeather())
server.register(Greet())
try await server.runService()
// Declarative registration with result builder
let server = MCPServer(name: "myserver", version: "1.0.0") {
GetWeather()
Greet()
Calculate()
}
try await server.runService()
// Dynamic registration and unregistration
server.register(GetWeather())
server.unregister("getWeather") // Remove a tool at runtime
// TCP binding
let server = MCPServer(name: "myserver", version: "1.0.0",
address: .localhostIPv4(port: 8080)) {
GetWeather()
}Full application entry point
@main
@MCPApplication(name: "myserver", version: "1.0.0", address: .localhostIPv4(port: 8080))
struct MyApp {
@Tool var weather = GetWeather()
@Tool var greet = Greet()
}Documentation
The DocC catalog contains the full guide set:
- Getting Started — first steps with swift-mcp
- Tool Definition — defining tools with property wrappers and macros
- Macro Guide —
@MCPCommand,@FuncTool,@MCPOptionGroup,@MCPApplication - Option Groups — sharing parameters across tools
- Server Configuration — transports, addresses, lifecycle, logging
- MCP Protocol — supported protocol methods and message flow
- Transport Design — stdio/TCP transports and custom transports
- Access Control — the access-level model and IP-based resolution
- Lifecycle Management — Swift Service Lifecycle integration
- Architecture — framework architecture and design decisions
- Migration Guide — upgrading between versions
Examples
A family of comprehensive, working examples lives in Sources/MCP/Documentation.docc/Examples/:
| Article | Covers | |---|---| | BasicTools | Sync/async, return types, error handling, all parameter types | | Example: Server Configuration | Stdio, TCP (IPv4/IPv6/dual-stack), Unix sockets, ServiceGroup | | AdvancedTools | Option groups, access control, complex types, composition | | IntegrationPatterns | Hummingbird, Vapor, clients, testing, Docker, systemd | | RealWorldScenarios | File server, DB proxy, AI assistant, build system, monitor, config |
Testing
swift testThe suite covers:
- Tool parameter discovery and argument injection (including custom
Codabletypes) - JSON Schema generation
- Error handling (missing arguments, type mismatches, parse errors, access denied)
- Option group flattening and argument application
- Macro expansion and diagnostics for all macros
- Server message handling (initialize, tools/list, tools/call, ping, notifications)
- Stdio and TCP transports end-to-end (EOF, shutdown, real-socket round trips)
- Default access-resolver behavior (IPv4/IPv6 loopback)
@MCPApplicationmacro (ToolID enum, debug-only tools, address and transport binding)- Dynamic tool registration, unregistration, and concurrent-registry safety
- Enum value constraints, spec-compliant resource content shapes
License
This project is licensed under the MIT License. See LICENSE.txt for details.
Package Metadata
Repository: tannerdsilva/swift-mcp
Default branch: master
README: README.md