A toolkit built on top of the official Swift SDK for Model Context Protocol server and clients that makes it easy to define strongly-typed tools.
Conform to MCPTool, describe your parameters using the JSONSchemaBuilder or @Schemable from swift-json-schema, and implement the call(with:) method.
struct WeatherTool: MCPTool {
let name = "weather"
let description: String? = "Return the weather for a location"
@Schemable
enum Unit {
case fahrenheit
case celsius
}
@Schemable
@ObjectOptions(.additionalProperties { false })
struct Parameters {
/// Location as city, like "Detroit" or "New York"
let location: String
/// Unit for temperature
let unit: Unit
}
func call(with arguments: Parameters) async throws(ToolError) -> Content {
switch arguments.unit {
case .fahrenheit:
"The weather in \(arguments.location) is 75°F and sunny."
case .celsius:
"The weather in \(arguments.location) is 24°C and sunny."
}
}
}Compare to see the vanilla swift-sdk approach
// Example/Sources/MCPToolkitExample/Tools/VanillaWeatherTool.swift
import MCP
struct VanillaWeatherTool {
static let name = "weather"
static func configure(server: Server) async {
await server.withMethodHandler(ListTools.self) { _ in
let tools = [
Tool(
name: Self.name,
description: "Return the weather for a location",
inputSchema: .object([
"type": .string("object"),
"additionalProperties": .bool(false),
"properties": .object([
"location": .object([
"type": .string("string"),
"description": .string("Location as city, like \"Detroit\" or \"New York\""),
]),
"unit": .object([
"type": .string("string"),
"enum": .array(["fahrenheit", "celsius"].map { .string($0) }),
"description": .string("Unit for temperature"),
]),
]),
"required": .array([.string("location"), .string("unit")]),
])
)
]
return .init(tools: tools)
}
await server.withMethodHandler(CallTool.self) { params async in
guard let arguments = params.arguments else {
return .init(
content: [.text("Missing arguments for tool \(Self.name)")],
isError: true
)
}
guard
case .string(let location)? = arguments["location"],
case .string(let unit)? = arguments["unit"]
else {
return .init(
content: [.text("Arguments for tool \(Self.name) failed validation.")],
isError: true
)
}
let summary: String
switch unit {
case "fahrenheit":
summary = "The weather in \(location) is 75°F and sunny."
case "celsius":
summary = "The weather in \(location) is 24°C and sunny."
default:
return .init(
content: [.text("Arguments for tool \(Self.name) failed validation.")],
isError: true
)
}
return .init(content: [.text(summary)])
}
}
}Create the same Server instance you would when using the swift-sdk, then call register(tools:) with your tool instance(s).
The optional messaging: parameter lets you customise every toolkit-managed response if you want to adjust tone, add metadata, or localise error messages.
import MCPToolkit
let server = Server(
name: "Weather Station",
version: "1.0.0",
capabilities: .init(tools: .init(listChanged: true))
)
await server.register(
tools: [WeatherTool()],
messaging: ResponseMessagingFactory.defaultWithOverrides { overrides in
overrides.toolThrew = { context in
CallTool.Result(
content: [
.text("Weather machine failure: \(context.error.localizedDescription)")
],
isError: true
)
}
}
)If you are happy with the toolkit's defaults, simply omit the messaging: argument.
The toolkit provides automatic error handling for tools. Any error thrown from call(with:) will be automatically converted to an error response with isError: true.
For custom error messages with structured content, throw a ToolError:
struct ValidatedTool: MCPTool {
let name = "validated"
@Schemable
struct Parameters {
let value: Int
}
func call(with arguments: Parameters) async throws(ToolError) -> Content {
guard arguments.value > 0 else {
throw ToolError {
"Invalid input: value must be positive"
"Received: \(arguments.value)"
"Please provide a value greater than 0"
}
}
return ["Success! Value is \(arguments.value)"]
}
}The ToolError supports the same @ToolContentBuilder syntax as the Content return type, allowing you to provide rich, multi-line error messages.
For simple single-line errors, use the convenience initializer:
throw ToolError("Value must be positive")MCP Inspector is an interactive development tool for MCP servers.
To install MCP Inspector, run:
npm install -g @modelcontextprotocol/inspectorThen you can run the example cli with either stdio or HTTP transport modes.
To run the example server with stdio transport, use:
npx @modelcontextprotocol/inspector@latest swift run MCPToolkitExample --transport stdioThis will start the server and connect it to MCP Inspector.
In HTTP mode, the CLI will spin up a Vapor web server (on port 8080 by default) with MCP tools at /mcp endpoint.
First start the Vapor server:
swift run MCPToolkitExample --transport httpThen in another terminal, start MCP Inspector and connect to the server:
npx @modelcontextprotocol/inspector@latest --server-url http://127.0.0.1:8080/mcp --transport httpMCP Resources allow servers to expose data that clients can read. This is useful for providing context like documentation, configuration files, or dynamic content.
Conform to MCPResource and use the @ResourceContentBuilder to define your content declaratively:
import MCPToolkit
struct DocumentationResource: MCPResource {
let uri = "docs://api/overview"
let name: String? = "API Overview"
let description: String? = "Complete API documentation"
let mimeType: String? = "text/markdown"
var content: Content {
"""
# API Documentation
Welcome to our API!
"""
}
}Use Group to combine multiple strings with optional custom separators and MIME types:
struct HTMLPageResource: MCPResource {
let uri = "ui://widget/page.html"
let name: String? = "Widget Page"
var content: Content {
// HTML content with default newline separator
Group {
"<!DOCTYPE html>"
"<html>"
"<head><title>My Widget</title></head>"
"<body><h1>Hello!</h1></body>"
"</html>"
}
.mimeType("text/html")
// CSS with custom separator
Group(separator: " ") {
".widget { color: blue; }"
".title { font-size: 20px; }"
}
.mimeType("text/css")
}
}Resources can provide binary content (images, PDFs, etc.) as base64-encoded strings:
struct ImageResource: MCPResource {
let uri = "data://images/logo.png"
let name: String? = "Company Logo"
var content: Content {
// Provide base64-encoded binary data
let base64PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB..."
ResourceContentItem.blob(base64PNG, mimeType: "image/png")
}
}
// Mix text and binary content
struct DocumentWithImagesResource: MCPResource {
let uri = "doc://report"
var content: Content {
Group {
"# Monthly Report"
"See the chart below."
}
.mimeType("text/markdown")
// Embed a chart image
ResourceContentItem.blob(chartImageBase64, mimeType: "image/png")
Group {
"## Summary"
"Data shows positive trends."
}
.mimeType("text/markdown")
}
}Register resources with your MCP server just like tools:
let server = Server(
name: "Documentation Server",
version: "1.0.0",
capabilities: .init(resources: .init(listChanged: true))
)
await server.register(resources: [
DocumentationResource(),
HTMLPageResource(),
ImageResource()
])Full API documentation is available on Swift Package Index here.
Add swift-mcp-toolkit to your Package.swift:
dependencies: [
.package(url: "https://github.com/ajevans99/swift-mcp-toolkit.git", from: "0.1.0")
]Then add the dependency to your target:
.target(
name: "YourTarget",
dependencies: [
.product(name: "MCPToolkit", package: "swift-mcp-toolkit")
]
)We welcome contributions! Please see CONTRIBUTING.md for guidelines.
This project is licensed under the MIT License. See LICENSE for details.

