An MCP client for your app
The Model Context Protocol client from LocalLM Lab, as a Swift package — no protocol or OAuth stack to write
LocalLM Lab is built on a Swift SDK, and its MCP client ships in
that SDK's LocalLMLabSDKCore. Link it into your own macOS app and
MCPServerManager gives you tool discovery, all the auth flows, Keychain-backed
token storage, and the current spec revision — wired, by default, straight to Apple's
on-device model. This page is the MCP-client piece; the
SDK overview covers the rest (connectors, workspace access, drop-in
SwiftUI).
Why local AI + MCP together is the point
Every MCP client in wide use today assumes a cloud model on the other end of the tool
call — Claude, GPT, Gemini — with the whole tool-calling loop running on someone else's
servers. This client is wired to Apple's on-device FoundationModels model
by default: the model choosing the tool, the request going out to a real MCP server,
and the result coming back all happen with the model running locally on the Mac's own
Neural Engine. Nothing about that — the prompt, the tool selection, the reasoning —
has to leave the device to reach a model provider just so your app can be AI-powered.
That's a different privacy and security posture than any cloud-model MCP client can
offer, and no per-token bill for the reasoning step itself.
The same client keeps working the same way if your app also offers a hosted model. Route a session to Claude's online API, GPT, or OpenRouter instead, and its tool calls still go through this MCP client, on the user's Mac, with the user's own OAuth tokens from their own Keychain — the hosted model sends back a tool call, your app is what actually reaches the server. A cloud model never talks to an MCP server directly here, whichever model is doing the reasoning.
What "a real MCP client" means here
MCP ships a dated revision every few months, and "supports MCP" can mean anything from the
2024 tool-calling core to the current spec. This client speaks
2025-11-25, the current revision, and negotiates downward
automatically: a server that only knows 2025-03-26 still connects and works.
You don't pick a version, and you don't touch the wire — addServer(url:) does
capability negotiation, the initialize handshake, session handling, and the
protocol-version header for you. The next revision (2026-07-28) is already
on the roadmap.
What that buys a developer who is not an MCP expert:
- All three auth models, auto-detected. Start every connection with
authType: .none. If the server needs OAuth, the client catches the 401, reads the challenge, runs the full OAuth 2.1 + PKCE browser flow, and stores the token in the Keychain (scoped to your bundle ID). If the server supports Dynamic Client Registration it registers on the fly; if not, you get a distinct error that's your cue to ask for a client ID. New in this revision: Client ID Metadata Documents — setMCPOAuthFlow.clientMetadataURLto a static JSON file describing your app and skip per-server registration entirely. - Structured tool results. A tool call returns an
MCPToolResult, not a bare string: text,structuredContent(JSON the server marked as data, validated against the tool's declared output schema),resourceLinks(pointers the model can ask to follow), anisErrorflag, and a truncation flag.renderedForModelfolds all of it into one context-sized string when that's all you need. - Server-initiated requests. Newer servers can pause a tool call to
ask the user for input (elicitation), ask your model to run a prompt
(sampling), or ask which files they may see (roots). Each is an
optional handler you register — nothing is advertised to a server unless you opt in.
Componentsships the elicitation UI (below); the rest are seams you fill only if you need them. - Untrusted by construction. Every server response is size-capped
(
MCPResponseLimits), newly discovered tools and resources default to disabled, and a tool description is treated as attacker-controlled text. Put aToolCallAuthorizerin front of anything high-impact. - Diagnostics for when a user reports a problem.
MCPDiagnostics— always-onos.Loggerlines plus an opt-in in-memory event buffer you dump as text — covers the connection, auth, and stream sequence. Strictly off-content: never prompt or response text, tokens redacted.
Drop-in SwiftUI, not just an API
Components ships ready-to-drop-in SwiftUI: an "add an MCP server" screen
supporting all three auth types, tool and resource enable/disable controls, an
OAuth-waiting view for the browser round trip, and a one-click summary export of what a
connected server offers. You don't have to build MCP management UI from scratch.
It also ships the elicitation sheet: when a server pauses a tool call to
ask the user for input, MCPElicitationPresenter +
.mcpElicitationSheet(_:) show a typed, validated form that names the server
asking and returns Submit / Decline / Cancel — three lines to wire, nothing to design.
See the elicitation UI for what it renders; conform to
MCPElicitationHandler yourself if you'd rather own the presentation.
Test it against a running server
The LocalLM Lab app is this same client with a UI on it — which makes it a way to check an MCP server you're building against an independent implementation and a range of models, with a wire-level protocol log. Testing your MCP server →
Or skip the app and build your own test tool straight on the SDK.
MCPServerManager's addServer / toolsForSession /
callTool are plain async methods with no model involved —
callTool(server:tool:arguments:) takes the arguments you supply and
hands back the result, so you can assert against a schema or an expected value without a
model ever deciding what to call. That's a small Swift CLI you run by hand during
development, a suite of scripted assertions for QA — or, on a macOS runner with
Xcode 27, a headless check in CI/CD: no GUI, no Apple Intelligence requirement, no model
download, fully deterministic. (Whether a model reliably picks and calls your tool is a
different question, and needs a real model in the loop — that's what the app itself, or
repo-qa below, are for.)
Start from working code
| Example | Start here if you want… |
|---|---|
repo-qa |
to connect to an MCP server you don't control — the smallest example, a CLI, no
signing or permissions. Builds a Tool from a live server's own schema. |
plate-today-tools |
an OAuth-gated MCP server (Todoist) alongside the connectors in a signed SwiftUI
app, using Core's ready-made Tools. |
components-demo |
to drop in the prebuilt SwiftUI MCP-server management UI instead of wiring one connection yourself. |
Docs
- docs/sdk-guide.md
— §3 is the MCP client: the three auth types + CIMD, the
2025-11-25capabilities (structured results, elicitation, diagnostics — §3a–§3e), Keychain storage, ready-made vs. hand-written tool-calling (§7a). - docs/mcp-diagnostics.md — the client's logging: the verbosity knob, reading Apple's unified log, the exportable buffer for a user bug report.
- examples/ · Components · the repo
Contact
Building on this, or hit a rough edge? neuron@thisbrain.ai or our Discord.
↑ Top