Skip to content

ajevans99/swift-mcp-toolkit

Repository files navigation

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

About

Strongly typed MCP tools in Swift

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •