Desktop Management MCP Server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Desktop Management MCP Serverfind device by serial ABC123"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Desktop Management MCP Server
Standalone Model Context Protocol (MCP) servers for JAMF Pro and Microsoft Intune. AI assistants (Claude Code, Gemini CLI, OpenCode, etc.) connect over HTTP and call device-management tools.
Architecture
MCP Client ──HTTP──▶ jamf-server (port 3001) ──▶ JAMF Pro REST API
MCP Client ──HTTP──▶ intune-server (port 3002) ──▶ Microsoft Graph APIEach server is a stateless Express app using @modelcontextprotocol/sdk. Every POST to /mcp creates a fresh StreamableHTTPServerTransport. Secrets are injected at runtime by Bitwarden Secrets Manager — no credentials are stored in the repo.
Related MCP server: quads-mcp
Prerequisites
Node.js 24+
BWS CLI (
bwsin PATH)A Bitwarden Secrets Manager project with the secrets listed in
bws-secrets.mapJAMF Pro API client credentials (OAuth2)
Azure AD app registration with Microsoft Graph permissions (for Intune)
(Optional) A Microsoft Entra ID resource app registration with App Roles, if you want per-user OAuth instead of the shared static token — see Authentication
Quick Start
# Install dependencies and build
npm install && npm run build
# Set your BWS machine account access token
export BWS_ACCESS_TOKEN="<your-token>"
# Start whichever server(s) you need
./start-jamf.sh # JAMF MCP on http://localhost:3001/mcp
./start-intune.sh # Intune MCP on http://localhost:3002/mcpSee bws-secrets.map for the secret names to create in your BWS project.
Authentication
/mcp on both servers requires Authorization: Bearer <token> (not required on /health). Requests without a valid token get 401; if a server has no auth mechanism configured at all, every /mcp request fails closed with 503 rather than being let through unauthenticated. Two independent modes are accepted on the same endpoint:
Static bearer token —
JAMF_MCP_AUTH_TOKEN/INTUNE_MCP_AUTH_TOKEN(comma-separated to allow rotating without downtime). This is the only mechanism available to non-interactive automation (scripts, n8n), and grants that server's full tool set — every tool below, including the destructive ones. Treat this token as equivalent to full admin access to the corresponding backend.Entra ID OAuth (
ENTRA_OAUTH_ENABLED=true) — a JWT issued by Microsoft Entra ID, verified against Entra's JWKS. Tool visibility is driven by the token'srolesclaim (Jamf.Read,Jamf.Write,Intune.Read— seesrc/utils/roles.ts): a tool a caller's role doesn't grant is never registered on that request's server instance, so it's not just hidden, it's uncallable. This lets real staff authenticate as themselves instead of sharing one all-or-nothing token.
The write/mutating JAMF tools (computer/prestage/inventory-preload updates, MDM flush, plus the script/package/smart-group/policy/disk-encryption-config/app-installer create-and-update tools) are gated behind the Jamf.Write role under Entra auth (or the static token, which grants both read and write). Anyone holding a valid JAMF_MCP_AUTH_TOKEN can call all of them — the token itself is the access boundary for the automation path. (jamf_send_mdm_command, the one tool that was flagged destructiveHint: true, was retired 2026-07-27 — see CLAUDE.md for why.)
In production (podman02), network exposure is layered on top of this: internal-DNS-only hostnames, plus a Caddy IP allowlist. /mcp's bearer-token requirement is the one layer that's actual authentication rather than network-level exposure control, and the one that still holds if the other two are ever misconfigured — see CLAUDE.md for the full deployment/defense-in-depth writeup.
Setting up Entra ID OAuth (optional)
Register one Entra app to act as the OAuth resource server, e.g. "Desktop Management MCP". Give it an Application ID URI using a verified-domain form (a bare custom string like
api://desktop-mgmt-mcpis rejected by tenants that require a verified domain — Colgate's does; useapi://<verified-domain>/desktop-mgmt-mcpinstead), a delegatedaccess_as_userscope, and App Roles matchingsrc/utils/roles.ts(Jamf.Read,Jamf.Write,Intune.Read). Turn on "User assignment required" and assign roles to users or groups via Enterprise Applications.Also register each server's own literal MCP URL (e.g.
https://jamf-mcp.colgate.edu/mcp) as an additionalidentifierUrisentry on that same app. MCP-spec clients (OpenCode,mcp-remote) send an RFC 8707resourceparameter set to that literal URL; if it doesn't resolve to the same app as the requestedscope, Entra rejects the request withAADSTS9010010. One resource app can have multiple identifier URIs, so this doesn't require separate apps per server.Register one small public/native client app (PKCE, no secret) for interactive clients, pre-authorized for the resource app's
access_as_userscope so users skip the consent screen.Decode a real issued token before assuming the audience format: Entra sometimes audiences tokens to the resource app's GUID app ID rather than its Application ID URI, specifically when a client sends the
resourceparameter — confirmed live, not something therequestedAccessTokenVersion: 2setting prevents.createEntraVerifieraccepts both forms via the optionalENTRA_RESOURCE_APP_IDenv var.
See CLAUDE.md's Authentication and roles section for the code-level details, and the per-client config below for what actually works today.
Environment Variables
Injected by bws run from your BWS project (see bws-secrets.map):
JAMF Pro:
JAMF_URL— e.g.https://yourorg.jamfcloud.comJAMF_CLIENT_ID,JAMF_CLIENT_SECRETJAMF_MCP_AUTH_TOKEN— bearer token(s) MCP clients must present (comma-separated to allow rotation)JAMF_MCP_PUBLIC_URL— this server's externally-visible origin, e.g.https://jamf-mcp.colgate.edu(required only whenENTRA_OAUTH_ENABLED=true, used to build RFC 9728 resource metadata)JAMF_PACKAGE_UPLOAD_DIR— directoryjamf_upload_packagemay read files from on this server's own filesystem. Required for that tool; it refuses every call if unset.JAMF_PLATFORM_CLIENT_ID/JAMF_PLATFORM_CLIENT_SECRET— OAuth2 client-credentials pair for the Jamf Platform API Gateway, from a separate account.jamf.com Integration (not the same asJAMF_CLIENT_ID/JAMF_CLIENT_SECRET). Required byjamf_list_filevault_status, thejamf_*_compliance_*tools, and thejamf_*_blueprint*tools; seesrc/jamf/jamf-api.tsfor details.JAMF_PLATFORM_URL— token endpoint for the above credential (e.g.https://us.apigw.jamf.com/auth/token)JAMF_PLATFORM_TENANT_ID— tenant UUID for the Platform API Gateway (distinct fromJAMF_URL)JAMF_PLATFORM_REGION— optional, Platform API Gateway region ("us"default,"eu","apac")
Microsoft Intune:
AZURE_TENANT_ID,AZURE_CLIENT_ID,AZURE_CLIENT_SECRET— Graph app-only client credentials for device data; unrelated to the Entra auth vars belowINTUNE_MCP_AUTH_TOKEN— bearer token(s) MCP clients must present (comma-separated to allow rotation)INTUNE_MCP_PUBLIC_URL— this server's externally-visible origin, e.g.https://intune-mcp.colgate.edu(required only whenENTRA_OAUTH_ENABLED=true)
Entra ID auth (optional; shared by both servers — see CLAUDE.md for the caveat that a single shared deployment env file means both servers currently enable/disable together despite the per-service naming):
ENTRA_OAUTH_ENABLED—"true"to accept Entra-issued bearer tokens on/mcpin addition to the static tokenENTRA_TENANT_ID— Entra tenant GUID (deliberately separate fromAZURE_TENANT_IDabove — different app registration, different purpose, don't reuse one BWS secret for both)ENTRA_RESOURCE_APP_ID_URI— Application ID URI of the resource app, e.g.api://colgate.edu/desktop-mgmt-mcpENTRA_RESOURCE_APP_ID— GUID app ID of the same resource app; accepted as an alternate validaudvalue (see Authentication above)
Azure AD Graph Permissions
The Intune app registration needs these Application permissions (with admin consent):
Device.Read.AllDeviceManagementManagedDevices.Read.AllDeviceManagementApps.Read.AllDeviceManagementConfiguration.Read.AllGroup.Read.AllUser.Read.All
Client Configuration
Point clients at http://localhost:<port>/mcp for a locally-running server, or at the https:// hostname below when talking to the podman02 deployment (Caddy terminates TLS and reverse-proxies to the container over plain HTTP — the servers themselves never speak TLS directly).
Environment | JAMF URL | Intune URL |
Local ( |
|
|
podman02 (internal DNS only) |
|
|
The podman02 hostnames resolve only inside Colgate's network (internal DNS locale) — see IAC/ansible-servers/linux/apps/desktop-management-mcp.yml. Both deployments require a bearer token on /mcp (see Authentication above); several JAMF tools are destructive, so treat the token — not just the URL — as sensitive.
Claude Code — static token (works today)
{
"mcpServers": {
"jamf": {
"type": "http",
"url": "https://jamf-mcp.colgate.edu/mcp",
"headers": { "Authorization": "Bearer <JAMF_MCP_AUTH_TOKEN value>" }
},
"intune": {
"type": "http",
"url": "https://intune-mcp.colgate.edu/mcp",
"headers": { "Authorization": "Bearer <INTUNE_MCP_AUTH_TOKEN value>" }
}
}
}Or via the CLI: claude mcp add --transport http jamf https://jamf-mcp.colgate.edu/mcp --header "Authorization: Bearer <token>".
Claude Code — Entra ID OAuth (per-user identity)
Claude Code's native HTTP-transport OAuth (claude mcp add --transport http ... --client-id ...) does not send a scope parameter and fails against Entra with AADSTS900144 — confirmed live, not a hypothetical. Use the mcp-remote bridge instead, which supports an explicit scope:
npm install -g mcp-remote # or use npx, at the cost of a re-fetch each launch{
"type": "stdio",
"command": "mcp-remote",
"args": [
"https://jamf-mcp.colgate.edu/mcp",
"3334",
"--static-oauth-client-info",
"{\"client_id\":\"<public-client-id>\"}",
"--static-oauth-client-metadata",
"{\"scope\":\"openid profile offline_access <resource-app-id-uri>/access_as_user\"}"
]
}Add with claude mcp add-json jamf-remote -s user '<json above>', then trigger the OAuth flow with claude mcp get jamf-remote (or just start using its tools — first use triggers auth). Pin an explicit port per server (mcp-remote otherwise derives one from a hash of the server URL, which works but is less predictable) and register the exact resulting redirect URI — http://localhost:<port>/oauth/callback, note localhost not 127.0.0.1, and a different path than OpenCode uses below — on the Entra public client.
Gemini CLI (~/.gemini/settings.json)
{
"mcpServers": {
"jamf": { "httpUrl": "https://jamf-mcp.colgate.edu/mcp", "headers": { "Authorization": "Bearer <JAMF_MCP_AUTH_TOKEN value>" } },
"intune": { "httpUrl": "https://intune-mcp.colgate.edu/mcp", "headers": { "Authorization": "Bearer <INTUNE_MCP_AUTH_TOKEN value>" } }
}
}OpenCode (~/.config/opencode/opencode.json)
Static token:
{
"mcp": {
"jamf": {
"type": "remote",
"url": "https://jamf-mcp.colgate.edu/mcp",
"enabled": true,
"headers": { "Authorization": "Bearer <JAMF_MCP_AUTH_TOKEN value>" }
}
}
}Entra ID OAuth (confirmed working live end-to-end — opencode mcp auth jamf drives a normal browser PKCE flow):
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"jamf": {
"type": "remote",
"url": "https://jamf-mcp.colgate.edu/mcp",
"enabled": true,
"oauth": {
"clientId": "<public-client-id>",
"scope": "openid profile offline_access <resource-app-id-uri>/access_as_user"
}
}
}
}OpenCode's OAuth callback listens on a fixed http://127.0.0.1:19876/mcp/oauth/callback (not currently configurable) — register that exact redirect URI on the Entra public client. If OpenCode runs on a different machine than your browser (e.g. a remote dev VM), the redirect won't reach the listener; tunnel the port with ssh -L 19876:localhost:19876 <host> before running opencode mcp auth.
Swap in the http://localhost:<port>/mcp URLs from the table above for local development.
MCP Tools Reference
JAMF Pro Tools
The write tools below require the Jamf.Write role under Entra auth. jamf_create_script/jamf_upload_package/jamf_create_smart_group are upserts by name (re-running updates the existing object in place); jamf_create_policy always creates a new policy; jamf_update_policy handles enable/disable + scope widening for an existing one. See CLAUDE.md for the Classic-API-requires-XML detail, the known Delete-permission gap (Scripts/Policies/Smart Groups can be created/updated but not deleted by this API client — package deletion does work), and why jamf_send_mdm_command was retired (infeasible via this API client's OAuth auth, not a per-command privilege gap).
Tool | Description | Parameters |
| Computer details by name |
|
| Search/list computers, optionally by asset tag |
|
| Computer details by serial number |
|
| Macs by username / name / email |
|
| Mobile device details by name |
|
| Fleet-wide mobile device list/breakdown (type, managed, supervised, model) |
|
| List smart groups |
|
| Members of a smart group |
|
| Static computer groups | — |
| All JAMF sites | — |
| Scripts (with optional filter/pagination) |
|
| Packages (with optional filter/pagination) |
|
| Inventory preload records |
|
| Create/update a preload record by serial (pre-enrollment asset tag/building/room/user) |
|
| Computer prestage enrollment configs | — |
| Scope serials into a prestage's enrollment |
|
| Policies list |
|
| Policy details |
|
| Configuration profiles |
|
| Patch policies |
|
| Categories |
|
| Departments | — |
| FileVault encryption status |
|
| Bulk FileVault escrow/status across the fleet, one page per call |
|
| Update computer inventory fields |
|
| Flush pending/failed MDM commands |
|
| Create/update a script (upsert by name) |
|
| Upload a .pkg/.dmg and create/update its package object (upsert by name) |
|
| Create/update an Application Title+Version detection smart group (upsert by name) |
|
| Create a policy scoped to smart/static groups; script-only policies supported |
|
| Enable/disable + widen/narrow an existing policy's scope |
|
| List available mSCP baselines (CIS/NIST) for Compliance Benchmarks | — |
| List this tenant's configured Compliance Benchmarks | — |
| Benchmark detail: rules, scope, enforcement mode, compliance % |
|
| Per-rule device pass/fail drill-down for a benchmark |
|
| List Blueprints (declarative device management workflows) |
|
| Blueprint detail: scope, steps/components, deployment state, status report |
|
| Catalog of component types available to build blueprints from |
|
Compliance Benchmarks, Blueprints, and jamf_list_filevault_status all hit the Jamf Platform API Gateway — a different host and a different OAuth2 credential (JAMF_PLATFORM_*) from every other JAMF tool in this file. Confirmed live 2026-07-29 against a real account.jamf.com Integration credential; see bws-secrets.map and the "Platform API Gateway" section of src/jamf/jamf-api.ts for details.
Microsoft Intune Tools
Tool | Description | Parameters |
| Autopilot profile & status |
|
| Bulk-list Windows Autopilot device identities (fleet-wide counts/breakdowns) |
|
| Managed device by name (includes ownership/ |
|
| Managed device by serial number (includes ownership/ |
|
| All devices for a user |
|
| Fleet-wide device list/breakdown (OS, compliance, management agent, ownership) — |
|
| Entra Conditional Access policies (state, grant controls, targeted platforms) — needs | — |
| Device enrollment configurations (device-type/personal-device platform restrictions) | — |
| Device group memberships |
|
| Detected & assigned apps |
|
| Configuration policies (classic + settings catalog) |
|
| Device-level policy deployment diagnostics |
|
| Assignment targets for a policy, by ID or name |
|
| Correlates policy assignment + device state |
|
| Intune app deployments |
|
| Assignment targets for an app, by ID or name |
|
| Correlates app assignment + device app status |
|
Running Tests
export BWS_ACCESS_TOKEN="<your-token>"
bws run --access-token "$BWS_ACCESS_TOKEN" -- \
TEST_COMPUTER_NAME="<name>" \
TEST_COMPUTER_SERIAL="<serial>" \
TEST_USER_EMAIL="<email>" \
npm testAdd JAMF_TEST_WRITE=1 to enable destructive write tests (MDM commands, inventory updates) against live JAMF Pro. The script/smart-group/policy upsert tests additionally need a pre-existing, manually-created fixture object named via TEST_SCRIPT_NAME/TEST_SMART_GROUP_NAME/TEST_POLICY_NAME (each optional — the test skips if unset or if no object with that name exists yet, since this API client can create/update but not delete those object types). The package upsert test is fully self-cleaning via TEST_PACKAGE_PATH (also optional) pointing at a small .pkg/.dmg inside JAMF_PACKAGE_UPLOAD_DIR.
npm run test:unit runs just the auth/roles unit tests (test/auth.test.ts) — no live credentials or network access needed, since they exercise requireMcpAuth's fallback/fail-closed logic and the roles.ts helpers against fakes.
Project Structure
├── src/
│ ├── mcp/
│ │ ├── jamf-server.ts # JAMF standalone MCP server (port 3001)
│ │ └── intune-server.ts # Intune standalone MCP server (port 3002)
│ ├── jamf/
│ │ └── jamf-api.ts # JAMF Pro REST + Classic API client
│ ├── intune/
│ │ └── graph-api.ts # Microsoft Graph / Intune client
│ └── utils/
│ ├── auth.ts # requireMcpAuth: static-token / Entra JWT dual-mode middleware
│ ├── entra-jwt.ts # Entra JWT verification (jose) + RFC 9728 resource metadata
│ ├── roles.ts # Entra App Role constants and role-checking helpers
│ └── logger.ts # Output formatting helpers
├── test/
│ ├── jamf-api.test.ts # Live integration tests against real JAMF Pro
│ └── auth.test.ts # Auth/roles unit tests — no live credentials needed
├── start-jamf.sh # BWS-wrapped launcher for JAMF server
├── start-intune.sh # BWS-wrapped launcher for Intune server
└── bws-secrets.map # Required BWS secret names and descriptionsAdditional Resources
MCP Authorization spec (RFC 8707 resource indicators, RFC 9728 protected resource metadata)
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- FlicenseAqualityCmaintenanceAn MCP server that connects AI assistants to the NinjaOne remote monitoring and management platform via the REST API v2. It provides tools for device inventory, organization management, alert handling, maintenance scheduling, and automated job execution.Last updated221
- Alicense-qualityDmaintenanceMCP server for interacting with QUADS infrastructure systems via API, enabling resource management and automation through LLM applications.Last updatedMIT
- Alicense-qualityCmaintenanceAn async MCP server for Jamf Pro integration, providing AI assistants with tools for computer health analysis, inventory management, and policy monitoring.Last updated8Apache 2.0
- Alicense-qualityCmaintenanceMCP server for Microsoft Fabric REST APIs that enables data engineers and analysts to manage Fabric components using AI assistants.Last updated4AGPL 3.0
Related MCP Connectors
MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.
Official Microsoft MCP Server to query Microsoft Entra data using natural language
Personal assistant MCP server with search, execute, packages, jobs, secrets, and integrations.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/rcobb2/desktop-management-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server