SwiftMCPClient

May 30, 2026 ยท View on GitHub

A small, transport-injectable Model Context Protocol client for Swift, over the neutral SwiftMCPCore wire types. Built to talk to the cupertino MCP server, but the client and transport seam carry no cupertino-specific types.

  • The wire types come from SwiftMCPCore (the external SwiftMCPCore package): Foundation-only JSON-RPC / MCP value types under the MCP.Core.Protocols.* namespace, shared with other MCP clients and servers. Only SwiftMCPClient imports it.
  • SwiftMCPTransport the Transport.Channel seam: a protocol that moves one raw JSON-RPC frame (Data) at a time. Foundation-only, cross-platform.
  • SwiftMCPClientAPI the Client.MCP / Client.Argument seam: the verb contract a consumer depends on, so it can hold any Client.MCP (and a fake for tests) without importing the concrete client. Foundation-only.
  • SwiftMCPSubprocessTransport a macOS Transport.Channel that spawns a command (for example cupertino serve) and frames JSON-RPC over its stdio, newline-delimited.
  • SwiftMCPClient an actor-based MCPClient that conforms Client.MCP and speaks the initialize handshake and tools/call / resources/read over any injected Transport.Channel, multiplexing concurrent requests by id with a per-request timeout.

Why a separate kit

The client deliberately does not build on a stdio-hardcoded MCP client. The transport is injected (any Transport.Channel), so the same client drives a subprocess on macOS today and a remote channel later, and is testable with a fake channel and no process at all. The wire core is isolated and Foundation-only so it can lift, unchanged, into a future neutral SwiftMCPCore package shared by more than one project.

Install

Add the package and depend on the products you need:

.package(url: "https://github.com/mihaelamj/SwiftMCPClient.git", from: "0.1.0"),
.target(
    name: "YourFeature",
    dependencies: [
        .product(name: "SwiftMCPClient", package: "SwiftMCPClient"),
        .product(name: "SwiftMCPSubprocessTransport", package: "SwiftMCPClient"), // macOS
    ],
)

Usage

import SwiftMCPClient
import SwiftMCPSubprocessTransport

// macOS: drive a local `cupertino serve` over stdio.
let transport = Transport.Subprocess(command: "cupertino", arguments: ["serve"])
let client = MCPClient(transport: transport)

try await client.connect()                                  // spawn + initialize
let markdown = try await client.callTool("list_frameworks", arguments: [:])
await client.disconnect()

To drive a different transport (remote, in-memory, test fake), conform to Transport.Channel and pass it to MCPClient(transport:). A consumer that only needs the verb surface can depend on the Client.MCP protocol and be tested with a fake.

Requirements

  • Swift 6.2+
  • macOS 13+ / iOS 16+ for the cross-platform products; the subprocess transport is macOS-only. The core, transport seam, and client also build on Linux.

Building

swift build
swift test
CUPERTINO_INTEGRATION=1 swift test    # opt-in live tests; needs `cupertino` on PATH

Documentation

License

MIT.