Skip to content

krislavten/cowork-mdm

Repository files navigation

cowork-mdm

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-mdm pulls the full schema out of the bundle, generates the .mobileconfig, lints the payload before it ships, and runs per-host diagnostics afterwards.

English · 中文

Quickstart Claude Code plugin License

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.


Why this exists

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.

At a glance

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.

Quickstart

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.

Four load-bearing ideas

1 · The schema is the source of truth — and it lives in the bundle, not the docs

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.

2 · Templates are YAML sources you own, not opaque scaffolds

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.

3 · validate and lint are different gates, and you need both

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.

4 · MDM delivers the config layer; a Script payload delivers the rest

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.

Command reference

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 check

Inspect 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 platform

Apply & 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 install

Manage 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 prune

Spec documents live under specs/; the full v0.2 task breakdown is at docs/execution/TASKS.md.

Claude Code plugin

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-mdm CLI on PATH.

Install:

/plugin marketplace add https://github.com/krislavten/cowork-mdm
/plugin install cowork-mdm@cowork-mdm

Full surface documented in specs/claude-plugin.md.

Deployment flow

         ┌─────────────────────────────────────┐
         │  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  │
           └──────────────────────────────────┘

Contributing

Development conventions in AGENTS.md. Specs in specs/. Issues and PRs welcome.

Maintainer notes

Releasing

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:

  1. GitHub Releases on this repo — tar.gz + zip archives + .pkg installers (arm64 + x86_64) + checksums.txt. Uses the default GITHUB_TOKEN.
  2. Homebrew tap at krislavten/homebrew-tap — requires secret HOMEBREW_TAP_GITHUB_TOKEN with contents:write on that repo. Without it, the brew formula push step fails; everything else still succeeds.
  3. macOS .pkg installers — 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 prompted

Enabling signed + notarized .pkg installers

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

License

MIT — see LICENSE.

About

Claude Desktop enterprise deployment toolkit — MDM config + plugin marketplace management for Mac/Windows IT admins

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors