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 externalSwiftMCPCorepackage): Foundation-only JSON-RPC / MCP value types under theMCP.Core.Protocols.*namespace, shared with other MCP clients and servers. OnlySwiftMCPClientimports it. SwiftMCPTransporttheTransport.Channelseam: a protocol that moves one raw JSON-RPC frame (Data) at a time. Foundation-only, cross-platform.SwiftMCPClientAPItheClient.MCP/Client.Argumentseam: the verb contract a consumer depends on, so it can holdany Client.MCP(and a fake for tests) without importing the concrete client. Foundation-only.SwiftMCPSubprocessTransporta macOSTransport.Channelthat spawns a command (for examplecupertino serve) and frames JSON-RPC over its stdio, newline-delimited.SwiftMCPClientanactor-basedMCPClientthat conformsClient.MCPand speaks theinitializehandshake andtools/call/resources/readover any injectedTransport.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
- docs/DESIGN.md the architecture and the layer boundaries.
- docs/package-import-contract.md the per-target import contract.
- CONTRIBUTING.md setup, conventions, and workflow.
License
MIT.