jamf-platform-mcp-server

jamf-platform-mcp-server

MCP server giving AI assistants read-only access to a Jamf fleet via the Jamf Platform API Gateway using OAuth2 client credentials, with tools for fleet overview, device queries, group membership, automation auditing, and passthrough requests.

Category
访问服务器

README

jamf-platform-mcp-server: MCP server for the Jamf Platform API Gateway

Tier Upstream pre-commit Tests License SemVer Keep a Changelog Node

An MCP server that gives AI assistants read access to a Jamf fleet through the Jamf Platform API Gateway, authenticating with OAuth 2.0 client credentials rather than user-account tokens. That choice is the point of the project: scoped machine credentials mean the permission boundary is enforced by Jamf, so a read-only integration cannot mutate a fleet no matter what this code does. See JPM-0001. Canonical location: github.com/jackvaughanjr/jamf-platform-mcp-server.

Tier: Prototype — no production dependants, upstream API in public beta, minor versions may break.

The upstream API is a public beta. The Platform API Gateway has no published breaking-change protocol and no announced GA date. Pin dependencies and expect churn. One documented API group (Compliance Benchmarks) currently returns a gateway-side 500 (JPM-0006).

Provenance

This is an independent project, not a fork or a derivative. No shared git history, no upstream remote, and no copied code.

It is worth naming an influence, though: evaluating dbankscard/jamf-mcp-server (MIT) is what surfaced the compound-tool idea used here: answering a whole fleet question in one call rather than making a model loop over per-device requests. That is a design idea, freely reusable and not subject to any licence term, so this credit is courtesy rather than obligation and carries no requirement onward to anyone using this project. Reasoning: JPM-0002.

Related work worth knowing about:

  • Jamf-Concepts/mcp-hub — Jamf's own open-source MCP server (Python, Beta) for Jamf Pro, Protect and Security Cloud
  • developer.jamf.com/mcp — Jamf-hosted MCP server for documentation search, not tenant management

Structure

src/
  index.ts              MCP server: tool registration, stdio transport
  platform-client.ts    every gateway concern — auth, token cache, URL shapes, paging
  config.ts             environment validation (zod)
  fleet.ts              pure fleet aggregation — no client, no clock, no I/O
  automations.ts        script/policy auditing, criteria projection, inventory cost
  declaration-scope.ts  pure DDM rollup: one declaration across many devices
  references.ts         pure reference analysis + smart-group dependency graph
  *.test.ts             unit tests (vitest)
decisions/              architectural decision records, JPM- prefix, immutable
docs/
  capabilities.md       what the server can answer, by question rather than endpoint
  gateway-reference.md  observed gateway behaviour: paths, status semantics, paging
  endpoint-inventory.md documented endpoint surface, compiled from Jamf's llms.txt
fixtures/
  shapes/               type-only response schemas — committed
  raw/                  captured responses — GITIGNORED, live fleet data
  discovery-report.md   empirical record of what resolves
scripts/
  discover-gateway.sh   resolves service segments, enumerates hosting, derives shapes
  fetch-blueprints.sh   standalone Blueprints smoke test
  jamf                  cwd-independent wrapper: scripts/jamf <tool> ['<json>']
  call-tool.mjs         calls one MCP tool live; inherits env so `op run` works
  check-adr-immutability.sh
.githooks/pre-commit    rejects force-added ignored files; enforces ADR immutability

Current state (as of 2026-08-06)

Working and confirmed against a live tenant:

segment style resource notes
blueprints tenant blueprints totalCount-only envelope
blueprints tenant blueprint-components records keyed identifier, not id
devices tenant devices full paging envelope; spans macOS and iOS
device-groups tenant device-groups full envelope; exposes memberCount
pro tenant 300+ resources the Jamf Pro API in full
proclassic classic /tenant/{t}/{resource} Jamf Pro Classic — no version segment
ddm/report tenant devices/{id}/channels Declaration Reporting
ddm/report tenant devices/{id}/declarations per-device declaration state; filter required
ddm/report tenant declarations/{id}/devices the same state per declaration, across devices

Compliance Benchmarks has a correct, documented path but returns 500 {"error":"Upstream host lookup failed"}. The gateway routes it and cannot reach its own backend. A fault on Jamf's side, not something a client can work around.

An earlier revision of this file claimed Classic, Declaration Reporting and Compliance Benchmarks were simply not exposed. That was wrong; see JPM-0006, which supersedes JPM-0005 and explains how the error happened.

Tools: getFleetOverview, findDevices, findOutdatedDevices, findDeviceGroups, getDeviceGroupMembers, findExpensiveAutomations, getInventoryCollectionSettings and findCriteriaReferences (compound), listBlueprints and getDeviceDeclarationState, getDeclarationScope, findObjectReferences and findGroupDependencies and getComputerGroup (typed), and platformRequest (authenticated GET-only passthrough to any gateway route). Tool count stays deliberately small (JPM-0003).

Pagination is confirmed live: a real page-1 request returned different records with hasPrevious: true and totalPages: 13, so page is 0-based as assumed and query parameters survive the passthrough.

device-actions remains unverified, because every route in it is a write and no write scopes have been granted.

Setup

npm install                    # also points core.hooksPath at .githooks
cp .env.op.example .env.op     # edit to match your 1Password vault/item
npm run build

Create an integration in Jamf Account → Integrations. A read-only integration is sufficient and strongly preferred. The client secret is shown exactly once.

Variable Required Notes
JAMF_CLIENT_ID yes from the integration
JAMF_CLIENT_SECRET yes shown once at creation
JAMF_TENANT_ID yes appears in every gateway path
JAMF_GATEWAY_BASE_URL no defaults to https://us.apigw.jamf.com
JAMF_TOKEN_URL no defaults to <base>/auth/token
JAMF_READ_ONLY no defaults to true; a backstop, not the guarantee

Write posture

A read-only integration (above) is this project's supported configuration, not a starter mode to graduate from (JPM-0007). Scopes that can erase or unmanage a device are never granted to this server — not gated, not granted, in any configuration. That work belongs in Jamf Pro's own interface, where it is attributed to a named person and lands in Jamf's audit log.

JAMF_READ_ONLY above is a backstop, not the guarantee: it is on unless the value is the literal string false, so a typo fails closed. The real boundary is what the credential's scopes permit, which Jamf enforces, not this code. A fork that grants write scopes is making that decision, and owns what follows from it.

platformRequest offers no method or body parameter, so the passthrough cannot express a mutation at all, not even with write scopes granted. A passthrough write is unreviewable in a way a typed tool's write is not, since method, path and body would all be caller-composed with no schema constraining any of them. Any future write is a named tool with a narrow schema, so the set of possible mutations stays enumerable by reading src/index.ts.

Credentials are injected at runtime so the secret never lands on disk:

op run --env-file=.env.op -- npm run dev

Register with Claude Code

claude mcp add jamf-platform -- node /absolute/path/to/dist/index.js

Conventions

  • ISO dates (YYYY-MM-DD) everywhere, including in dated snapshots above.
  • ADRs are immutable once committed. Correct one by superseding it, never by editing. Enforced by scripts/check-adr-immutability.sh via the pre-commit hook; ADR_ALLOW_EDIT=1 covers the sanctioned exceptions. Prefix: JPM-.
  • Decisions vs findings. decisions/ holds decisions and is immutable. docs/gateway-reference.md holds observations about a beta API and is expected to change. Do not mix them.
  • Never git add -f. The ignore list is a data-handling boundary guarding the client secret and captured fleet data; a pre-commit hook enforces it.
  • Never commit a captured API response. Only type-only shapes (JPM-0004).
  • Confirm routes empirically. A Jamf docs section is not evidence a route exists. The documentation has been wrong about this gateway four times, each retraction recorded in docs/gateway-reference.md.
  • Commit messages explain why, and state explicitly when they retract an earlier conclusion. Descriptive imperative subjects; not Conventional Commits, which is why there is no badge claiming otherwise.

Versioning

SemVer 2.0.0. package.json is the single source of the version. Changes are recorded in CHANGELOG.md per Keep a Changelog. Releases are tagged vX.Y.Z.

While the gateway remains in public beta, minor versions may carry breaking changes, because the upstream contract offers no stability guarantee, so strict SemVer against it would be a false promise.

Testing

npm test              # vitest, 270 tests
npm run typecheck
DRY_RUN=1 ./scripts/discover-gateway.sh    # probe matrix, no credentials needed

To exercise a tool against a live tenant, from any directory:

scripts/jamf tools/list
scripts/jamf getFleetOverview
scripts/jamf findDevices '{"query":"MacBook"}'

scripts/jamf wraps op run with an absolute --env-file path, because op resolves that against the caller's cwd and fails with a bare "open .env.op: no such file or directory" otherwise. It also unsets OP_SERVICE_ACCOUNT_TOKEN, which the committed .envrc handles inside the repo but cannot outside it, and resolves symlinks so it can be linked onto PATH for a shorter handle:

ln -s "$PWD/scripts/jamf" ~/.local/bin/jamf-mcp
jamf-mcp getFleetOverview

Not npm run inspector under op run. The MCP Inspector spawns the server as a child process without forwarding the parent environment, so injected credentials never reach it and the server exits on config validation. Its -e flag would work but puts the client secret on a command line where ps can read it. scripts/call-tool.mjs spawns the server with the environment inherited, so credentials go straight to the process that needs them. Its output can contain live fleet data, so redirect to a gitignored path if you keep it.

Tests never reach the gateway: fetch is stubbed per test and credentials are fixtures. The suite is mutation-checked rather than assumed useful: each of these deliberate breakages causes failures: removing the totalCount pagination fallback, making paging 1-based, disabling the read-only guard, misclassifying iPads as Macs, treating an unparseable timestamp as a recent check-in, letting an empty search query match every device, and counting an absent managed flag as unmanaged.

The contribution contract

CONTRIBUTING.md is written as a contract, separating what a guard will stop you doing from what a human reviews. Three rules are mechanically enforced by a pre-commit hook and by CI:

Rule Enforced by
No live identifiers in tracked files — including test fixtures scripts/check-no-identifiers.sh
No captured API responses committed; never git add -f .githooks/pre-commit
Committed ADRs are immutable — supersede, never edit scripts/check-adr-immutability.sh + CI base-branch diff

src/conventions.test.ts additionally asserts the conventions that drifted during early development: the test-count badge matches reality, ADR numbering is sequential and fully indexed, every superseded record names its successor, and the README does not cite a superseded ADR as guidance.

Test UUIDs use the reserved deadbeef- prefix, so an identifier copied out of live output is visible rather than plausible. That rule exists because a real device id reached a test file exactly that way.

Pull request and review policy

Single maintainer at present, so changes land directly on main. On a second contributor: branch protection on main, one non-author approval, and decisions/ changes reviewed by someone other than the author.

Every change should pass npm test, npm run typecheck, and npm run build. Anything touching src/platform-client.ts should also be exercised against a live tenant, since no test can confirm the gateway's actual behaviour.

Cross-reference

License

MIT — see LICENSE.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选