From cowork-mdm
Deploying a Claude Desktop MDM profile to real user machines — choosing between MDM channel push (Jamf/Intune/Kandji/Mosyle) vs local apply, verifying status on a target host, and debugging profiles that don't take effect. Load when the user wants to push, install, activate, or verify an already-authored profile — phrases like "apply this mobileconfig", "push via Jamf/Intune", "profile status", "why isn't my config taking effect", "Claude Desktop isn't reading my settings", or anything about `/Library/Managed Preferences/`.
How this skill is triggered — by the user, by Claude, or both
Slash command
/cowork-mdm:mdm-profile-deployThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill covers **pushing an already-authored profile to hosts** and
This skill covers pushing an already-authored profile to hosts and
verifying it took effect. For authoring, see mdm-profile-authoring.
Direct writes to
/Library/Managed Preferences/are CLOBBERED bymanagedappconfigdon the next MDM sync.
macOS's managedappconfigd owns that directory. It rewrites it every time
the system checks in with its MDM profile source. If you sudo cp a plist
there:
Production deployments must go through an MDM channel. Period. The
correct artifact to hand the IT admin is a signed .mobileconfig, not a
plist and not a shell command.
Local apply exists only for developer loops on a dev machine — and
even then, --dry-run is almost always what you want.
.mobileconfig with cowork-mdm profile new … --out profile.mobileconfig.cowork-mdm profile validate profile.mobileconfig..mobileconfig to the IT admin for upload:
managedappconfigd writes the plist
into /Library/Managed Preferences/… and keeps it there.cowork-mdm profile status (next section).cowork-mdm profile apply writes the plist directly. Use only to iterate
on a profile on your own machine:
sudo cowork-mdm profile apply profile.mobileconfig --dry-run
# inspect the preview, then:
sudo cowork-mdm profile apply profile.mobileconfig
Two safety rails:
--dry-run first and confirm the preview matches intent.sudo on the user's behalf — hand them the command,
let them run it. This plugin has no business elevating.If the user asks you to sudo apply in production, push back with the rule
above and offer to hand them a .mobileconfig instead.
cowork-mdm profile status reads the active plist / registry key and
reports what's currently live. The JSON form is the source of truth for
the field contract; the human form is a rendering for eyeballs and may
omit fields that are absent / null.
JSON output (parse this to reason programmatically):
cowork-mdm profile status --json
Shape:
{
"platform": "darwin" | "windows",
"targetPath": string, // plist path or registry key
"present": bool,
"profile": {
"name": string, // "" when decoded from an on-disk plist
"values": { "<key>": <value>, ... } // map, NOT array — jq with .profile.values["KEY"]
},
"unknownKeys": [ {"key":"…","raw":"…"}, ... ] | null,
"parseError": string // "" when parse succeeded
}
Notes that bite parsers:
profile.values is a JSON object (map keyed by preference name),
not an array. Access with jq '.profile.values.inferenceProvider'.unknownKeys is null when there are none, not []. Guard with
jq '.unknownKeys // [] | length'.unknown: 0 line — only
unknownKeys in JSON carries that data.com.anthropic.claudefordesktop (the
app's bundle identifier). Intune/Jamf upload flows sometimes let you pick
wrong payload identifiers.sudo profiles renew -type configuration forces it.cowork-mdm doctor includes plist.exists and plist.schema checks
— switch to mdm-doctor skill for the full decision tree.cmd-Q, not just close-window) and relaunch.appMin of each key with cowork-mdm schema show KEY. If the
user's Claude Desktop is older than the appMin, that key is silently
ignored.unknownKeys: cowork-mdm profile status --json | jq '.unknownKeys // []'. A non-empty list means your plist has a typo or the schema
drifted; null or [] is clean.Claude Desktop reads both:
/Library/Managed Preferences/com.anthropic.claudefordesktop.plist
(system — applies to all users)/Library/Managed Preferences/<username>/com.anthropic.claudefordesktop.plist
(per-user — overrides system for that user)Most deployments use the system plist. The per-user variant is useful when
different groups need different inferenceProfile values. cowork-mdm profile status reports whichever it finds; with --json both surface if
both exist.
cowork-mdm profile validate profile.mobileconfig # schema clean; exit 0 on success
cowork-mdm profile apply profile.mobileconfig --dry-run # preview exact bytes
cowork-mdm profile status --json # what's already live on this host
If any of these three fail, do not ship.
managedMcpServers — when an MDM profile
is replaced by a new version, the old plist is rewritten. If you depend
on old tokens being invalidated, also rotate them at the provider side;
the plist rewrite alone doesn't clear app caches.cowork-mdm profile new emits
unsigned .mobileconfig. Most MDMs re-sign during upload. If you
need to test locally by double-click, expect a "Profile unsigned" warning
— that's expected, not a bug.disableDeploymentModeChooser: true gates hard on the provider being
fully configured. If you ship this flag but inferenceProvider /
inferenceBedrockRegion / inferenceBedrockProfile are missing, users
hit a dead screen with no way to recover. Keep the chooser enabled until
you're confident the profile is complete, then flip the flag.npx claudepluginhub krislavten/cowork-mdm --plugin cowork-mdmProvides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.