swift-mcp-toolkit

0.2.0

Strongly typed MCP tools in Swift
ajevans99/swift-mcp-toolkit

What's New

v0.2.0

2025-10-09T20:43:20Z

What's Changed

  • Add response messaging customization hooks by @ajevans99 in #1

New Contributors

Full Changelog: v0.1.0...v0.2.0

swift-mcp-toolkit

CI

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.

Quick Start

Step 1: Define a Tool

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 -> CallTool.Result {
    let weather: String

    switch arguments.unit {
    case .fahrenheit:
      weather = "The weather in \(arguments.location) is 75°F and sunny."
    case .celsius:
      weather = "The weather in \(arguments.location) is 24°C and sunny."
    }

    return .init(content: [.text(weather)])
  }
}
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)])
    }
  }
}

Step 2: Register the Tool with a MCP Server

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.

Running the Example Server with MCP Inspector

MCP Inspector is an interactive development tool for MCP servers.

To install MCP Inspector, run:

npm install -g @modelcontextprotocol/inspector

Then you can run the example cli with either stdio or HTTP transport modes.

Stdio

To run the example server with stdio transport, use:

npx @modelcontextprotocol/inspector@latest swift run MCPToolkitExample --transport stdio

This will start the server and connect it to MCP Inspector.

MCP Inspector screenshot (STDIO mode)

HTTP

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 http

Then 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 http

MCP Inspector screenshot (HTTP mode)

Documentation

Full API documentation is available on Swift Package Index here.

Installation

Swift Package Manager

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")
  ]
)

Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines.

License

This project is licensed under the MIT License. See LICENSE for details.

Resources

Description

  • Swift Tools 6.0.0
View More Packages from this Author

Dependencies

Last updated: Sun Oct 19 2025 10:57:30 GMT-0900 (Hawaii-Aleutian Daylight Time)