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.

↑ Top

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:

ProviderBacksShips inmacOS
SystemModelProviderApple's on-device modelCore26+
PCCModelProviderApple Private Cloud Compute — entitlement pending, not functional in this build (known issues)Core27
ClaudeModelProviderClaude, through the Foundation Models interfaceLocalLMLabSDKClaude27
MLXModelProviderOpen-weight models you download and run locallyLocalLMLabSDKInference27
RemoteModelProviderHosted providers over an OpenAI-compatible API — GPT, Claude's online API, OpenRouter, or any compatible server, with provider-native web searchLocalLMLabSDKRemote27*

*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:

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.

↑ Top

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).

↑ Top

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.

↑ Top

Start 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.

ExampleStart 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.

↑ Top

Licensed 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.

↑ Top

Coming 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:

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.

↑ Top

Get it

The SDK, the reference apps, and the full developer guide are in the LocalLM GitHub repo (1.0.0-GA branch):

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