SDK
Build a native macOS AI app on the same foundation LocalLM Lab runs on
LocalLM Lab itself is built on a Swift SDK,
LocalLMLabSDKCore, and you can build on the same thing. It gives your app
one or more models it can actually call tools with, a real MCP client, native access to
the user's calendar / reminders / contacts / location / files, and drop-in SwiftUI for
the parts users configure — without an EventKit wrapper, an OAuth
stack, or an MCP implementation to write yourself.
1.0.0 is now generally available. LocalLMLabSDKCore
keeps a macOS 26 deployment floor — one build serves macOS 26 and 27,
with the hosted providers, Private Cloud Compute, and open-weight (MLX) models registered
behind #available(macOS 27, *). Consuming the SDK needs
Xcode 27 (the xcframeworks are built with the macOS 27 SDK).
1.0.0-beta.N made no API-stability promise; from 1.0.0-RC.1 onward,
releases are source compatible — additive changes only until 2.0.
Why build on it
Build for privacy and local control — and that holds even when you reach for a hosted model. Apple's on-device model and open-weight MLX models never leave the device: no cloud calls, no per-token bill for the reasoning step, a genuinely different security posture than a cloud-model app. But the control doesn't stop at reasoning. Route a session to a hosted provider instead — Claude's online API, GPT, OpenRouter — and its tool calls still run through the SDK's own MCP client and connectors, 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 Slack or GitHub. It never talks to those services directly, whichever model is doing the reasoning.
That's on top of the usual reasons to build on a shared SDK rather than your own stack:
one MCP client instead of a protocol and OAuth implementation to write and maintain,
ready-made connector Tools instead of an EventKit wrapper, and
a model layer that owns routing, residency, and download state so your app doesn't
reinvent a provider abstraction. It's not a demo dependency — LocalLM Lab itself
runs on this SDK.
What's in it
A model layer, a real MCP client, system connectors and scoped filesystem access, a security/authorization layer for what a model is allowed to do unattended, and drop-in SwiftUI for the parts users configure.
The model layer — new in 1.0
Offer more than one model without writing your own provider abstraction. Five built-in
providers sit behind one ModelProvider protocol — on-device, Apple's
cloud, a hosted provider, or an open-weight model you run yourself, all reached the
same way:
| Provider | Backs | Ships in | macOS |
|---|---|---|---|
SystemModelProvider | Apple's on-device model | Core | 26+ |
PCCModelProvider | Apple Private Cloud Compute — entitlement pending, not functional in this build (known issues) | Core | 27 |
ClaudeModelProvider | Claude, through the Foundation Models interface | LocalLMLabSDKClaude | 27 |
MLXModelProvider | Open-weight models you download and run locally | LocalLMLabSDKInference | 27 |
RemoteModelProvider | Hosted providers over an OpenAI-compatible API — GPT, Claude's online API, OpenRouter, or any compatible server, with provider-native web search | LocalLMLabSDKRemote | 27* |
*LocalLMLabSDKRemote itself has a macOS 26 manifest
floor — it links into a 26-deployment app fine — but RemoteModelProvider
is 27-only, same gate as the other three. Name models with routes
(.heavy, .light, …), pick one per session, keep one warm
between turns, and surface download and memory state in your UI. On macOS 26 the 27-only
providers simply aren't registered; lab.models.availability(for:) reports
them as .requiresOS("macOS 27") and ModelPickerView shows
them as disabled rows.
In practice that's a small adoption diff, not a rewrite. This is the entire change
repo-qa-local
makes over the Apple-model repo-qa it's built from — register an
MLXModelProvider, route a model ID to it, validate/download once if it
isn't local yet, then make the session the same way you would for any provider:
let mlx = MLXModelProvider(
residentModelLimit: 1,
pinnedRevisions: ["mlx-community/Qwen3-8B-4bit": "545dc4251c05440727734bcd94334791f6ab0192"], // the commit you reviewed
pinStore: MLXFilePinStore()) // a user's own choice is pinned on first download
let lab = LocalLMLab(configuration: .init(providers: [mlx, SystemModelProvider()]))
let modelID = ModelID(scheme: "mlx", rest: "mlx-community/Qwen3-8B-4bit")!
lab.models.route(.local, to: modelID)
if case .notDownloaded = lab.models.availability(for: modelID) {
_ = try await mlx.validate(modelID.rest) // fits this Mac's RAM? MLX format?
for try await event in mlx.download(modelID.rest) { … } // stream progress %
}
let session = try lab.makeSession(route: .local, tools: tools, instructions: instructions)
for try await partial in session.languageModelSession.streamResponse(to: task) { … }
makeSession merges your tools with the enabled MCP tools whatever the
route — your own Tools, the connectors, and the MCP client attach
to on-device, Claude, and MLX sessions alike. MLXModelProvider also owns the
memory story: validate preflights a model with no download,
capabilityProbe is the authoritative check of whether a downloaded model can
actually tool-call, and residentModelLimit caps how many stay in RAM. For a
starting shortlist of which open-weight models tool-call, see
tested-models.md.
A real MCP client
MCPServerManager connects your app to Model Context Protocol servers —
Slack, GitHub, Notion, Linear, and the rest — at the current spec revision
(2025-11-25, negotiating down for older servers). All three auth models
auto-detected, OAuth 2.1 + PKCE + DCR + CIMD, Keychain-backed tokens, structured tool
results, server-initiated elicitation, and off-content diagnostics. The same tool-calling
loop runs on top of whichever provider you route to — Apple on-device, Claude, or a
downloaded open-weight model. The client tracks the spec as it moves; the next revision
(2026-07-28) is already on the roadmap.
The MCP client →
Running downloaded models safely, tuned and paired
An app that downloads models at runtime inherits a real risk: a Hugging Face repo isn't a fixed,
vetted artifact. Its owner can change the files behind a name, anyone can publish a look-alike, and a
download can be corrupted or too big for the Mac. MLXModelProvider covers that end to end,
and you opt into as much of it as your app needs:
- Check before you fetch.
validate(_:)runs a preflight (repo reachable, MLX format, an architecture the runtime supports, size against memory, disk space) and names the stage that failed. A host-suppliedMLXModelTrustPolicydecides which repos may be fetched at all — the SDK ships no allow-list, that's your call. - Verify what you fetched. Every downloaded file is hashed against the hash Hugging
Face reports for it before it's kept (on by default), and an
MLXCacheLimitscap bounds the whole cache. - Pin every model to one exact commit.
pinnedRevisions:is the version you shipped;pinStore:captures the version a user first downloaded;managedPinStore:records a developer-vouched move of a shipped pin. - Update and clean up deliberately.
checkPinUpdatepreviews what would change;updatePin(_:to:beforeSwitch:)downloads and verifies, waits for your pause point, moves the pin, and evicts — all or nothing, so a running conversation is never left half on the old version and half on the new.snapshots(for:)andremoveSnapshotlet you list and safely remove old versions; the SDK never prunes on its own. - Tune and pair.
SessionOptionscarries temperature, top-p / top-k / min-p, a seed, a maximum output length, a repetition penalty and more into the model, and now reaches Apple's on-device model too.pairDraftModeladds a smaller speed helper (speculative decoding) andpairAdaptera LoRA adapter, each switchable per run.
mlx-control-room shows all of
it working, with a gauge beside each knob;
vistanova shows the minimal end
(one pinnedRevisions:). The walkthrough is
sdk-guide.md,
and the app's own tuning and pairing controls are described on the
AI Models page.
Mac system connectors
Calendar, Reminders, Contacts, Location, Clock, Weather, and a scoped Filesystem —
the data and Apple features already on the user's Mac, each with a ready-made
Tool so most apps don't hand-write a tool-calling adapter, and the real
macOS permission prompts handled for you. Each ready-made Tool carries an impact rating
(read, mutate, delete), so you decide which ones reach a session (see
Security below). Every connector stays on-device except Weather, which is a real call out to
Apple's own weather service — the one connector that isn't purely local.
What each connector does →
Scoped filesystem access
WorkspaceAccess / WorkspaceTools give the model read / write /
edit / search / patch over one folder the user grants — the security-scoped-bookmark
plumbing an App Sandbox app needs, done.
FileBackedTool and the "AIQL" data verbs go further: they let the model
pull data out of a source — an MCP server's dataset, a local file — and
analyze or process it entirely on the user's Mac, without the raw rows ever entering
the model's context. The mechanism is natural language → SQL: the model writes one
read-only query against the data, your app runs it, and only the query's shape (or a
resulting CSV) comes back — so a model can answer questions over rows it never
actually saw, and can't fabricate or miscopy a value it was never shown. See
aiql
for the full pipeline.
Security & tool authorization
Every tool Core ships carries an impact rating (read, mutate, delete) baked in, so you
can control what a model is allowed to do unattended without hand-rolling your own
classification. Sequence<any Tool>.limited(toMaxImpact:) caps which
tools even reach a session — hand the model a read-only slice, or exclude delete
tools outright. ConfirmingToolAuthorizer is the other lever: "ask a human
first," per call, wired to whatever confirmation UI you use.
RuleBasedToolAuthorizer composes both as pure logic (deny specific tools or
whole MCP servers) if a UI prompt isn't what you want. The two levers are independent —
which tools a session can see, and whether the ones it can see ask before running.
security-demo
is the runnable reference: a frontier model against Calendar and a Todoist MCP server,
with nothing but a Security panel and a Run button.
Drop-in SwiftUI (Components)
An "add an MCP server" screen with all three auth types, tool / resource toggles, an
OAuth-waiting view, the elicitation sheet, and a
tool-call confirmation sheet keyed off each tool's impact rating — the
ConfirmingToolAuthorizer UI, ready-made. For models you download, there's a
validate → download → pin onboarding stepper, an update view (check, update,
roll back) and a versions-on-disk view with a guarded Remove. Optional, layered strictly on
Core's public API.
Two paths to tool-calling
Drop in a ready-made Tool for each connector and for any MCP server tool
(built at runtime from its own schema) — or write your own adapter against the same
underlying calls for full control over names, schemas, and descriptions. Both ship in
Core; mixing them is fine.
Proven under App Sandbox, not assumed
Sandboxing an app breaks all sorts of things silently — network calls, background
permissions, subprocess spawning. Rather than assume the SDK would just work under
sandbox, it was built into a sandboxed test app, hit real breakage (a missing network
entitlement that silently killed weather lookups and MCP connections), fixed, and
re-verified: Calendar, Reminders, and the full MCP+OAuth+Keychain flow all confirmed
working under sandbox, with real system permission prompts. There's also a working path
to a Mac App Store submission: an Apple Distribution signing + provisioning pipeline that
produces a correctly signed .pkg with a signature chain verified up to
Apple's own root CA. A downloaded open-weight model runs inside the sandbox too, fetching
its weights into the app's own container
(workspace-buddy-local).
Dogfooded in a real, shipping app
LocalLM Lab doesn't keep a private copy of this code around — it depends on the SDK directly. When this page says Calendar or MCP access "works," it means it works in a real app real users run, not just in a sample project.
↑ TopStart from working code
Open-source reference apps ship with the SDK — use the closest one as a starting reference rather than writing from scratch. Each has a README with copy-paste setup.
| 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. |
plate-today /
-tools |
connectors (Calendar, Reminders) plus an OAuth-gated MCP server (Todoist) in a signed SwiftUI app — the same app on the hand-written and ready-made tool-calling paths, to diff. |
workspace-buddy |
to give the model scoped access to a folder of files under App Sandbox —
WorkspaceTools plus the security-scoped-bookmark pattern. |
components-demo |
to drop in the prebuilt SwiftUI MCP-server management UI instead of wiring one connection yourself. |
repo-qa-local |
the smallest "download and run an open-weight model" — repo-qa
with an MLX model instead of Apple's, about 30 lines different, including the pin. |
code-buddy |
the full model-layer pattern: a CLI coding agent with .heavy /
.light routes to two local models, Workspace + host Process
tools, an MCP docs server, and a persistent >> session. |
workspace-buddy-local |
a sandboxed GUI app running a downloaded model — the Mac App Store shape, model download working inside the App Sandbox container. |
model-switch |
hosted providers — GPT / Claude's online API / OpenRouter and on-device behind one chat call site, with provider-run web search and citations. |
security-demo |
to see the security/authorization layer in isolation — a frontier model against Calendar and a Todoist MCP server, gated by nothing but a Security panel. |
aiql |
plain-English → one read-only SQL SELECT → CSV over an MCP
dataset; rows never enter the model's context, so nothing is fabricated. |
vistanova |
a small search engine on local models and an MCP server (Tavily), with the MLX summary model pinned to an exact commit so an upstream change never silently changes what your app runs. |
mlx-control-room |
every MLX knob with a gauge that proves it changed something, plus the supply-chain flow made visible: validate, download, pin, update, roll back, clean up, and speed-helper / adapter pairing. |
components-updates-demo |
the Components onboarding, update and versions-on-disk views, driven by simulated
sources so every state (a denied preflight, a failed update, a rollback) is reachable. |
os-matrix |
one .macOS("26.0") build that runs on both macOS 26 and 27, with the 27-only
model families gated by ModelAvailability.requiresOS — no source #if. |
annotated-examples.md walks every one line by line, SDK touchpoints marked inline.
↑ TopLicensed to actually be used commercially
Apache 2.0 — chosen specifically for its patent grant, which matters if your legal team has to sign off on embedding a third-party SDK into a commercial product. Versioned binary releases are published on GitHub with checksums.
↑ TopComing from 0.8.x?
Mostly additive — the MCP client, connectors, and Keychain storage carry over, and the
deployment floor stays at platforms: [.macOS("26.0")]. The source-breaks:
MCPServerManager.callToolnow returns anMCPToolResultinstead of aString; useresult.renderedForModelfor the old string. (Going throughMCPToolorFileBackedToolneeds no change.)MCPConnectionStatusandMCPServerErrorare non-frozen, so an exhaustiveswitchneeds@unknown default.- A custom
ModelProviderconformance changes:languageModel(for:)→makeSession(for:…).
Coming from a 1.0 beta? The changes since beta.4 that can break a build (tool-call
SessionEvent payloads, InstalledModel's initializer, a required
cancelDownload(_:), a throwing RemoteModelProvider initializer) are listed
under Changed — breaking (pre-GA) in the
CHANGELOG. Full detail in
migrating-to-1.0.md.
Get it
The SDK, the reference apps, and the full developer guide are in the
LocalLM GitHub repo
(1.0.0-GA branch):
- docs/sdk-guide.md — the full developer guide: linking Core, the model layer (§6a), entitlements, the MCP client (§3), connectors, App Sandbox / Mac App Store signing, ready-made vs. hand-written tool-calling (§7a), and a full API reference.
- docs/migrating-to-1.0.md — upgrading from the 0.8.x line.
- docs/tested-models.md — which open-weight (MLX) models actually tool-call.
- docs/mcp-diagnostics.md — the MCP client's logging.
- docs/api-surface.md · CHANGELOG.md · examples/ · Components
Point knownSDKReleases at 1.0.0-GA with the checksum from
the release's .sha256 asset. Each example pins an explicit SDK version:
LOCALLM_SDK_VERSION=1.0.0-GA swift build
↑ Top
Contact
Questions, or building something with this? neuron@thisbrain.ai or our Discord.
↑ Top