Bezalu NinjaOne MCP Server
A Model Context Protocol (MCP) server that provides AI assistants with secure, user-delegated access to the NinjaOne RMM platform.
Features
- Embedded OAuth 2.0 Authorization Server with Dynamic Client Registration (DCR) — Spec-compliant with MCP 2025-11-25; works with any conformant MCP client even though NinjaOne and Entra ID do not support DCR
- Per-user delegated access — Actions are performed as the authenticated user; the server never uses a privileged app token for NinjaOne calls
- Cloud-neutral local persistence — Clients and tokens are stored on local disk under a configurable, volume-mountable path (no cloud dependency)
- High-value NinjaOne tools — Devices, Organizations, Locations, Alerts, Custom Fields, Software, Windows Services, Jobs, Tasks, Security & Patching
- Container-first — Distributed as a Linux container image via GitHub Container Registry
- Minimal footprint — Self-contained single-file publish, ideal for Azure Container Apps
Container Image
docker pull ghcr.io/bezalullc/ninjaone-mcp:latest
Configuration
The server requires the following environment variables:
| Variable | Required | Description |
|---|---|---|
NINJAONE_INSTANCE |
Yes | NinjaOne instance URL (e.g., https://app.ninjarmm.com, https://eu.ninjarmm.com) |
NINJAONE_CLIENT_ID |
Yes | Client ID of the single NinjaOne API application used to bridge user authentication |
NINJAONE_CLIENT_SECRET |
Yes | Client secret of that NinjaOne API application |
NINJAONE_SCOPES |
No | OAuth scopes requested upstream and advertised in metadata (default: monitoring management offline_access) |
MCP_DATA_PATH |
No | Directory for local persistence of registered clients and tokens (default: /app/data) |
PUBLIC_BASE_URL |
No | Explicit externally-visible base URL (e.g., https://mcp.example.com). When unset, it is derived from the request, honoring X-Forwarded-* headers |
Local persistence & volume mount
The server is its own OAuth authorization server, so it persists registered clients and issued
tokens locally — no cloud or external database is required. Storage is a small set of JSON files
under MCP_DATA_PATH (default /app/data):
| File | Contents |
|---|---|
clients.json |
Dynamically-registered MCP clients (RFC 7591) |
tokens.json |
Issued access/refresh tokens mapped to their upstream NinjaOne token set |
In-flight authorization sessions and single-use authorization codes are short-lived and kept in
memory only. Mount a volume at MCP_DATA_PATH to retain registrations and user sessions across
container restarts.
Token confidentiality at rest:
tokens.jsoncontains issued bearer tokens and the upstream NinjaOne access/refresh tokens in plaintext. Anyone able to read theMCP_DATA_PATHvolume gains the same NinjaOne access as the authenticated users. Restrict access to the volume to the runtime user only and back it with an encrypted volume (or encrypted host filesystem) in production.
Permissions: the container runs as a non-root user. The image pre-creates
/app/datawith the correct ownership, and Docker named volumes (as in the example below) inherit it automatically. If you instead use a host bind mount (e.g.-v /srv/mcp:/app/data), make the host directory writable by the runtime user first, for examplechown -R 1654:1654 /srv/mcp.
Running
docker run -d \
-p 8080:8080 \
-e NINJAONE_INSTANCE=https://app.ninjarmm.com \
-e NINJAONE_CLIENT_ID=your-ninjaone-client-id \
-e NINJAONE_CLIENT_SECRET=your-ninjaone-client-secret \
-v ninjaone-mcp-data:/app/data \
ghcr.io/bezalullc/ninjaone-mcp:latest
MCP Client Configuration
Connect your MCP client to the running server:
{
"servers": {
"ninjaone": {
"type": "http",
"url": "http://localhost:8080"
}
}
}
The MCP client discovers the server's OAuth authorization server metadata, dynamically registers itself (DCR), and runs the Authorization Code + PKCE flow automatically — the user authenticates with NinjaOne in their browser, and the resulting per-user token is used for all subsequent API calls.
Available Tools
Devices
| Tool | Description |
|---|---|
list_devices |
List all devices with pagination |
list_devices_detailed |
List devices with full hardware/OS details |
get_device |
Get a specific device by ID |
search_devices |
Search devices by name |
Organizations & Locations
| Tool | Description |
|---|---|
list_organizations |
List all organizations |
list_organizations_detailed |
List organizations with full details |
get_organization |
Get a specific organization by ID |
list_locations |
List locations (all or per-org) |
Alerts
| Tool | Description |
|---|---|
list_alerts |
List active alerts |
reset_alert |
Acknowledge/reset an alert |
Custom Fields
| Tool | Description |
|---|---|
list_custom_fields |
List custom field definitions |
get_device_custom_fields |
Get custom field values for a device |
update_device_custom_fields |
Update custom field values for a device |
Software & Patching
| Tool | Description |
|---|---|
query_installed_software |
Query installed software across all devices |
list_software_products |
List known software products |
query_software_patches |
Query pending software patches |
query_software_patch_installs |
Query software patch installation history |
query_os_patches |
Query pending OS patches |
query_os_patch_installs |
Query OS patch installation history |
Windows Services
| Tool | Description |
|---|---|
query_windows_services |
Query Windows services across fleet (filter by name/state) |
Jobs & Tasks
| Tool | Description |
|---|---|
list_jobs |
List scheduled/running jobs (scripts, patching, maintenance) |
list_tasks |
List automation tasks |
Security & Health
| Tool | Description |
|---|---|
query_antivirus_status |
Query AV protection status across devices |
query_antivirus_threats |
Query detected antivirus threats |
query_device_health |
Query overall device health status |
NinjaOne OAuth Setup
- In NinjaOne Administration → Apps → API, create an API application
- Set the Authorization Grant Type to "Authorization Code"
- Set the Redirect URI to this server's callback URL:
<PUBLIC_BASE_URL>/callback(e.g.,https://mcp.example.com/callbackorhttp://localhost:8080/callbackfor local testing) - Note the Client ID and Client Secret and provide them via
NINJAONE_CLIENT_ID/NINJAONE_CLIENT_SECRET - Set the appropriate scopes (monitoring, management, etc.)
Only this single NinjaOne API application credential is needed. MCP clients never see it — they register dynamically against this server and receive tokens this server issues.
OAuth Endpoints
The server exposes a spec-compliant OAuth 2.0 authorization server:
| Endpoint | Purpose |
|---|---|
GET /.well-known/oauth-authorization-server |
Authorization server metadata (RFC 8414) |
POST /register |
Dynamic Client Registration (RFC 7591) |
GET /authorize |
Authorization endpoint (PKCE required) |
GET /callback |
NinjaOne authorization-code callback |
POST /token |
Token endpoint (authorization_code, refresh_token) |
Development
# Set environment variables
export NINJAONE_INSTANCE=https://app.ninjarmm.com
export NINJAONE_CLIENT_ID=your-ninjaone-client-id
export NINJAONE_CLIENT_SECRET=your-ninjaone-client-secret
export MCP_DATA_PATH=./data
# Run locally
dotnet run
Architecture
MCP Client (e.g., VS Code, Claude Desktop)
│
├─ 1. Discover metadata + Dynamic Client Registration ─► MCP Server (this container)
│ │ (OAuth Authorization Server)
├─ 2. /authorize (PKCE) ─────────────────────────────────► │
│ ├─ Redirects user to NinjaOne
│ │
│ NinjaOne OAuth Server ◄── user authenticates ──────┤
│ │ │
│ └─► /callback (auth code) ───────────────►├─ Exchanges code (single app cred)
│ │ Stores per-user NinjaOne tokens
├─ 3. /token (code + PKCE verifier) ─────────────────────► │ Issues this server's token
│◄── access_token + refresh_token ──────────────────────────┤
│ │
├─ 4. Bearer <token> ──────────────────────────────────────►├─ Validates token, resolves the
│ │ user's NinjaOne token (refreshes
│ │ transparently), calls NinjaOne
│◄── Tool Results ──────────────────────────────────────────┘ as the user
License
See LICENSE for details.
More information
ASP.NET Core MCP servers use the ModelContextProtocol.AspNetCore package from the MCP C# SDK. For more information about MCP:
No comments yet
Be the first to share your take.