Deploy Claude Desktop across enterprise fleets — including the 43 MDM keys outside Anthropic's public docs. An Electron app ships with 51 managed-preferences keys embedded in its zod schema; the public enterprise docs cover 8. The other 43 are how you actually point the desktop at Bedrock, Vertex, Foundry, an Anthropic-compatible gateway in front of DeepSeek / Qwen / GLM / MiniMax / Llama / Mistral, or a self-hosted vLLM / SGLang cluster — and lock down egress, MCP, telemetry, sandbox, and auto-update policy while you're at it.
cowork-mdmpulls the full schema out of the bundle, generates the.mobileconfig, lints the payload before it ships, and runs per-host diagnostics afterwards.
English · 中文
Status: v0.3 — CLI + Claude Code plugin. Schema pinned to Claude.app 1.5354.0. macOS end-to-end; Windows .reg tracked in #9. Not affiliated with Anthropic — everything below is reverse-engineered from the public desktop app.
Anthropic's Claude Desktop is an Electron app. Inside the app bundle, alongside the UI code, lives an embedded zod schema that declares every MDM key the renderer will read at launch: LLM provider, gateway URL, auth scheme, model allowlist, managed MCP servers, egress allowlist, telemetry endpoint, sandbox constraints, auto-update policy, credential-helper script — 51 keys in total.
Anthropic's public enterprise docs cover 8 of them. The other 43 — the ones you actually need if you don't intend to run Claude Desktop against api.anthropic.com — ship only as strings inside a minified Electron chunk.
IT teams hit this wall every time they try to:
- Route the desktop to an open-weights model family or compatible provider (DeepSeek, Qwen, Zhipu GLM, MiniMax, Llama, Mistral, and similar) served through an Anthropic-compatible endpoint.
- Route it to a hyperscaler-managed Bedrock / Vertex / Foundry deployment of Claude or another model.
- Route it to a self-hosted vLLM / SGLang cluster behind the same shim.
- Lock down egress, managed MCP, telemetry, sandbox, or auto-update policy at fleet scale — none of which is in the public docs.
- Ship company skills, slash commands, and plugin-bundled MCPs to every employee Mac, which the mobileconfig channel deliberately refuses to deliver (evidence).
cowork-mdm is the missing half of the enterprise story: a CLI that extracts the full schema, generates correct MDM profiles against it, lints them before you hand the file to Jamf / Intune / Kandji, and diagnoses the employee Mac when something doesn't take.
| What you get | |
|---|---|
| Schema | All 51 managed-preferences keys, extracted verbatim from the app's embedded zod schema (pinned to Claude.app 1.5354.0). Queryable by cowork-mdm schema list and schema show <key> — name, type, scope, appMin, allowed values, example. |
| Templates (9) | gateway · gateway-deepseek · gateway-glm · gateway-minimax · bedrock-basic · vertex · foundry · enterprise-cn-full (one-shot full enterprise scaffold: LLM + MCP + egress + telemetry + sandbox + auto-update) · mcp-only |
| Profile authoring | profile show-template NAME --out overrides.yaml → edit → profile new --from overrides.yaml --out company.mobileconfig. --template and --from are mutually exclusive by design — scaffolds are not production profiles. |
| Lint as a pre-distribution gate | profile validate is schema-only — it accepts REPLACE_WITH_YOUR_API_KEY as a valid string. profile lint scans for leftover REPLACE_* placeholders and exits non-zero. Run both, not one. |
| MDM delivery recipes | Jamf Pro · Microsoft Intune · Kandji — full Custom Settings payload + Shell Script payload recipes in docs/deployment-cn.md. |
| Plugin delivery (macOS) | marketplace add <repo> clones a Claude-Code-format plugin marketplace into /Library/Application Support/Claude/org-plugins/ and symlinks each plugin. Designed to run as a Shell Script payload alongside the mobileconfig push. |
| Per-host diagnostics | On macOS, cowork-mdm doctor runs 9 checks across app install, active plist, org-plugins symlinks, marketplace repo health, user sessions, and git availability — with --fix for the ones that are safe to auto-repair. Windows runs a smaller subset until #9 lands. |
| Claude Code plugin | 5 skills + 4 slash commands make the CLI self-driving inside an agent: /deploy, /new-profile, /doctor, /refresh-plugins. Ships zero new logic; requires the CLI on PATH. |
| Platforms | macOS (full), Windows (.reg encoder tracked in #9). |
| License | MIT. |
Pick the install channel that matches where cowork-mdm will run.
| Runs on | Install via |
|---|---|
| Admin workstation (authoring profiles) | brew install krislavten/tap/cowork-mdm |
| Employee Macs (Wave 2 Script payload) | Signed + notarized .pkg installer from Releases, pushed via your MDM's package-deployment mechanism. Lands at /opt/cowork-mdm/bin/ and symlinks to /usr/local/bin/cowork-mdm. Release pkgs are unsigned until Developer ID secrets are added (see Maintainer notes) — unsigned pkgs are fine for dev/CI but most MDM stacks will reject them for fleet delivery. |
| CI / throwaway machines | tar.gz from the Release page — unpack and run. |
# On the admin workstation — 6-command happy path for a gateway deployment:
cowork-mdm profile show-template enterprise-cn-full --out overrides.yaml
$EDITOR overrides.yaml # fill every REPLACE_* slot
cowork-mdm profile new --from overrides.yaml \
--payload-identifier-prefix com.acme.it \
--out company.mobileconfig
cowork-mdm profile lint company.mobileconfig # must exit 0 before you ship
cowork-mdm profile validate company.mobileconfig
# Push company.mobileconfig via Jamf / Intune / Kandji.
# For skills + slash commands + plugin-bundled MCPs, add a companion Script
# payload that runs `cowork-mdm marketplace add <your-org-plugins-repo>`.For hyperscaler-managed routes (Bedrock / Vertex / Foundry), substitute bedrock-basic / vertex / foundry for the template name and fill {{ACCOUNT}} / region / model-ID placeholders. Same downstream pipeline.
Full recipe: docs/deployment-cn.md — 8 sections covering prerequisites, provider selection, profile generation, lint + validate gates, MDM distribution (Jamf + Intune + Kandji, with Script payload for plugins), employee-machine verification, common failure modes, and the update path.
Claude Desktop is an Electron app. Its embedded zod schema declares every MDM key the renderer reads at launch: name, type, scope, appMin, allowed values. We extract that schema verbatim and pin it to a specific Claude.app version (currently 1.5354.0). cowork-mdm schema list and schema show <key> surface it; the profile encoder validates against it; the template library draws allowed values from it. Upstream version bumps → re-extract → re-pin → re-ship. No hand-maintained key lists drifting from reality.
Every built-in template ships as hand-written YAML under internal/profile/templates/. profile show-template NAME --out overrides.yaml dumps the exact same YAML into your own config repo — the CLI's embedded copy and the file you edit are byte-identical, so there's no version drift. You edit YAML, we encode .mobileconfig / plist / (soon) .reg. --template and --from are mutually exclusive on a single invocation: scaffolds are a starting point, not a deployable artifact.
profile validate is schema-only — it accepts REPLACE_WITH_YOUR_API_KEY as a valid string because the schema says inferenceGatewayApiKey: string. Every enterprise template leaves REPLACE_* placeholder tokens at every slot that must be filled before distribution. profile lint scans the encoded artifact for those tokens and exits non-zero on any finding. Run validate to catch schema bugs before you push the YAML; run lint as the final pre-distribution gate before you hand the mobileconfig to Jamf. Skipping either will ship a broken deployment that still "works" under one of the two checks.
The mobileconfig channel — Apple's managed-preferences mechanism — carries LLM config, standalone MCP servers, egress, telemetry, and sandbox policy. It cannot deliver skills, slash commands, hooks, or plugin-bundled MCPs. Those live under /Library/Application Support/Claude/org-plugins/ and are populated by cowork-mdm marketplace add <repo> against a Claude-Code-format plugin marketplace. Enterprise deployment is a two-wave push: Wave 1 — Custom Settings Payload (mobileconfig). Wave 2 — Shell Script Payload (marketplace add + marketplace update). Both scoped to the same device group, on the same cadence. Reverse-engineered evidence for why this is a hard constraint, not a workaround.
Grouped by intent. Every subcommand accepts --json for machine-readable output.
Author a profile
cowork-mdm profile templates # list built-in templates (9)
cowork-mdm profile show-template NAME [--out FILE] # dump template YAML source
cowork-mdm profile new --from overrides.yaml --out out.mobileconfig
cowork-mdm profile lint out.mobileconfig # flag REPLACE_* placeholders
cowork-mdm profile validate out.mobileconfig # schema checkInspect the schema
cowork-mdm schema list # all 51 keys
cowork-mdm schema show inferenceProvider # description, example, allowed values
cowork-mdm paths show [--os darwin|windows] # paths cowork-mdm reads per platformApply & verify on a host
cowork-mdm profile apply company.mobileconfig --dry-run # preview, no writes
cowork-mdm profile status # what's active right now
cowork-mdm doctor [--fix] # diagnose a broken installManage org plugins (macOS)
cowork-mdm marketplace add https://github.com/<org>/claude-org-plugins
cowork-mdm marketplace update
cowork-mdm plugin list
cowork-mdm plugin pruneSpec documents live under specs/; the full v0.2 task breakdown is at docs/execution/TASKS.md.
v0.3 ships a Claude Code plugin layer so an agent can drive the CLI on your behalf.
- Skills (5):
cowork-mdm,mdm-profile-authoring,mdm-profile-deploy,mdm-plugins,mdm-doctor. - Slash commands (4):
/deploy,/new-profile,/doctor,/refresh-plugins. - Zero new logic. Every skill shells out to the
cowork-mdmCLI onPATH.
Install:
/plugin marketplace add https://github.com/krislavten/cowork-mdm
/plugin install cowork-mdm@cowork-mdm
Full surface documented in specs/claude-plugin.md.
┌─────────────────────────────────────┐
│ Claude Desktop 1.5354.0 (Electron) │
│ embedded zod schema: 51 keys │
└────────────────┬────────────────────┘
│ extracted + pinned into internal/schema/schema.json
▼
┌──────────────────────────────────────────────────────────────┐
│ cowork-mdm CLI (Go 1.23, single static binary) │
│ schema ─────→ list / show / paths │
│ profile ────→ templates / show-template / new │
│ lint / validate / apply --dry-run / status │
│ marketplace → add / update / remove │
│ plugin ─────→ list / prune │
│ doctor ─────→ per-host diagnostics (9 checks on macOS) │
└─────────┬──────────────────────────────────┬─────────────────┘
▼ ▼
┌──────────────────────┐ ┌──────────────────────────┐
│ .mobileconfig │ │ org-plugins/ directory │
│ (Custom Settings) │ │ (Shell Script payload) │
│ LLM · MCP · egress │ │ skills · slash cmds │
│ telemetry · sandbox │ │ plugin-bundled MCPs │
└─────────┬────────────┘ └─────────┬────────────────┘
│ │
└───────────┬─────────────────────┘
▼
┌──────────────────────────────────┐
│ Jamf Pro · Intune · Kandji │
│ two-wave push to employee Macs │
└──────────────────────────────────┘
Development conventions in AGENTS.md. Specs in specs/. Issues and PRs welcome.
Releases are tag-triggered. Push a v* tag and .github/workflows/release.yml runs GoReleaser, then a follow-on macOS job builds the .pkg installers.
The release job publishes to three places:
- GitHub Releases on this repo — tar.gz + zip archives +
.pkginstallers (arm64 + x86_64) +checksums.txt. Uses the defaultGITHUB_TOKEN. - Homebrew tap at
krislavten/homebrew-tap— requires secretHOMEBREW_TAP_GITHUB_TOKENwith contents:write on that repo. Without it, the brew formula push step fails; everything else still succeeds. - macOS
.pkginstallers — built unsigned by default. If Apple Developer ID secrets are present (see below), they're signed + notarized automatically. Unsigned pkgs are useful for dev/CI testing and for orgs that re-sign with their own internal cert, but most MDM stacks (Jamf Pro, Kandji, Intune) require a Developer ID Installer signature — or an in-house cert trusted by the enrolled Macs — before they will deploy a.pkg.
Set the Homebrew secret once:
gh secret set HOMEBREW_TAP_GITHUB_TOKEN --repo krislavten/cowork-mdm
# paste the PAT when promptedApple signing is optional for the release pipeline itself — without secrets the job still emits unsigned pkgs — but it's a hard requirement for MDM delivery. When all seven secrets are set, every subsequent v* tag produces a Developer-ID-signed, Apple-notarized .pkg that passes Gatekeeper on user double-click and is accepted by Jamf Pro / Kandji / Intune.
Required secrets (all set via gh secret set NAME --repo krislavten/cowork-mdm):
| Secret | What it is | Where to get it |
|---|---|---|
MACOS_APP_CERT_P12 |
Developer ID Application cert, base64-encoded .p12 export |
Keychain Access → right-click cert → Export → set a password. Then base64 -i cert.p12 | pbcopy. |
MACOS_APP_CERT_PASSWORD |
The password you set when exporting the above | — |
MACOS_INSTALLER_CERT_P12 |
Developer ID Installer cert, base64-encoded .p12 export (separate cert from the Application one; signs the .pkg envelope) |
Same procedure. |
MACOS_INSTALLER_CERT_PASSWORD |
Password for the installer .p12 |
— |
APPLE_API_KEY_P8 |
App Store Connect API key contents (raw .p8 file text) |
App Store Connect → Users and Access → Integrations → App Store Connect API. Create a key with Developer role. Downloadable exactly once. |
APPLE_API_KEY_ID |
The 10-char Key ID shown next to the key name | Same page. |
APPLE_API_ISSUER_ID |
The Issuer UUID shown at the top of the same page | Same page (one per team). |
Verify activation by pushing a patch tag and watching the macos-pkg job in Actions — it should log build-pkg: signing binary with Developer ID Application (...) and build-pkg: notarized + stapled instead of the skipping messages.
If all certificate P12 secrets are absent, the signing path is skipped and the job emits unsigned pkgs. If the P12s are set but the matching passwords are missing or wrong, the keychain-import step fails before any pkg is built — the Release will show tar.gz + brew assets only, no pkg. continue-on-error: true keeps the workflow green in that case, but the fix is to set the passwords correctly and re-push the tag.
MIT — see LICENSE.