mcp-endpoint-central
# mcp-endpoint-central
An [MCP](https://modelcontextprotocol.io) (Model Context Protocol) server for **ManageEngine Endpoint Central — on-premise edition**. It lets MCP clients such as Claude Code, Claude Desktop, and other LLM agents query and (optionally) operate your Endpoint Central server through its REST API: managed computers, inventory, patch status, vulnerabilities, BitLocker, device control, DLP reports, and more.
> **On-prem only.** This server targets the self-hosted Endpoint Central product and its static API-token authentication. It does not implement Zoho OAuth2 and will not work against the Endpoint Central Cloud SaaS API.
## Highlights
- **Read-only by default.** Mutating tools (patch install/uninstall, agent uninstall, task changes, …) are *not registered at all* unless you explicitly opt in with `ENDPOINT_CENTRAL_ENABLE_WRITE_TOOLS=true`. In the default mode the connected LLM has no write surface to call.
- **Double confirmation on writes.** Even with write tools enabled, every mutating tool refuses to act unless the call includes `confirm: true`, so a client (or model) cannot trigger a destructive action in a single accidental step.
- **MCP tool annotations.** Every tool declares `readOnlyHint` / `destructiveHint` annotations, so clients that implement annotation-based policies (auto-approve reads, always prompt on destructive) work out of the box.
- **Context-friendly responses.** Endpoint Central's raw API returns dozens of DB-style fields per record. List tools project responses down to a curated field set by default (pass `raw: true` for full fidelity) and every paginated tool tells the model exactly how to fetch the next page.
- **Secure by design.** Static token is kept out of logs, error messages, and tool results; request paths are SSRF-guarded; internal-CA TLS is supported without ever disabling certificate verification.
## Requirements
- Node.js ≥ 18
- A reachable ManageEngine Endpoint Central **on-premise** server (default port 8383/HTTPS)
- An API token generated in the Endpoint Central console: **Admin → Integrations → API Key Management**
## Quick start
Four steps, about five minutes.
### 1. Install
```bash
git clone https://github.com/hdyrawan/mcp-endpoint-central.git
cd mcp-endpoint-central
npm install
```
`npm install` also builds the server, so there's no separate build step.
### 2. Get an API token
In the Endpoint Central console, go to **Admin → Integrations → API Key Management** and generate a key. Copy it somewhere safe.
Using Active Directory with MFA? That works fine — you complete MFA once here in the browser, and the key works headlessly from then on. See [Generating the API token](#generating-the-api-token) for the details and for choosing which account the key belongs to.
### 3. Add it to your MCP client
Use the absolute path to `dist/index.js` in this folder — run `pwd` to get it.
```bash
claude mcp add endpoint-central \
--env ENDPOINT_CENTRAL_BASE_URL=https://ec-server.corp.example:8383 \
--env ENDPOINT_CENTRAL_API_TOKEN=YOUR_TOKEN \
-- node /absolute/path/to/mcp-endpoint-central/dist/index.js
```
That's Claude Code. For **Claude Desktop, Codex CLI, OpenCode, or Hermes Agent**, see [client setup](#client-setup) below — same idea, different config file.
### 4. Verify
```bash
cp .env.example .env # fill in the same base URL + token
npm run check
```
This confirms the server is reachable and your token works, and tells you exactly what to fix if not. You're done — the server starts in read-only mode with 86 tools and no write access.
> **Want to use `npx` instead?** It works: `npx -y github:hdyrawan/mcp-endpoint-central`. It's slower to start and has a couple of caveats — see [alternative launch methods](#alternative-launch-methods).
## Client setup
Claude Code is covered in the [quick start](#3-add-it-to-your-mcp-client) above. Every client needs the same three things: the command `node`, the absolute path to `dist/index.js`, and the two environment variables.
### Claude Desktop
Add to `claude_desktop_config.json` (Settings → Developer → Edit Config):
```json
{
"mcpServers": {
"endpoint-central": {
"command": "node",
"args": ["/absolute/path/to/mcp-endpoint-central/dist/index.js"],
"env": {
"ENDPOINT_CENTRAL_BASE_URL": "https://ec-server.corp.example:8383",
"ENDPOINT_CENTRAL_API_TOKEN": "YOUR_TOKEN"
}
}
}
}
```
### Codex CLI
Add to `~/.codex/config.toml` (or use `codex mcp add`), then verify with `/mcp` in a session:
```toml
[mcp_servers.endpoint-central]
command = "node"
args = ["/absolute/path/to/mcp-endpoint-central/dist/index.js"]
[mcp_servers.endpoint-central.env]
ENDPOINT_CENTRAL_BASE_URL = "https://ec-server.corp.example:8383"
ENDPOINT_CENTRAL_API_TOKEN = "YOUR_TOKEN"
```
### OpenCode
Add to `opencode.json` in your project root (or globally in `~/.config/opencode/opencode.json`). Note OpenCode wants one `command` array and the key `environment`, not `env`:
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"endpoint-central": {
"type": "local",
"command": ["node", "/absolute/path/to/mcp-endpoint-central/dist/index.js"],
"enabled": true,
"environment": {
"ENDPOINT_CENTRAL_BASE_URL": "https://ec-server.corp.example:8383",
"ENDPOINT_CENTRAL_API_TOKEN": "YOUR_TOKEN"
}
}
}
}
```
### Hermes Agent
Add to `~/.hermes/config.yaml`, then restart Hermes or run `/reload-mcp` in an active session:
```yaml
mcp_servers:
endpoint-central:
command: "node"
args: ["/absolute/path/to/mcp-endpoint-central/dist/index.js"]
env:
ENDPOINT_CENTRAL_BASE_URL: "https://ec-server.corp.example:8383"
ENDPOINT_CENTRAL_API_TOKEN: "YOUR_TOKEN"
```
Hermes deliberately does not pass your shell environment to MCP subprocesses — only its safe baseline plus what you put in `env`. So every `ENDPOINT_CENTRAL_*` variable you need (including `ENDPOINT_CENTRAL_CA_CERT_PATH` or `ENDPOINT_CENTRAL_ENABLE_WRITE_TOOLS` if you use them) must be listed explicitly in this block.
### Any other MCP client
The server speaks MCP over stdio. Point your client at `node dist/index.js` with the environment variables below.
### Alternative launch methods
<details>
<summary>Using <code>npx</code>, a global command, or a pinned commit — and why the absolute path is the default</summary>
The MCP docs use two launch styles depending on where a server comes from:
- [Connecting a **published** server](https://modelcontextprotocol.io/docs/develop/connect-local-servers) → `"command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", …]`
- [Building **your own** server](https://modelcontextprotocol.io/docs/develop/build-server) → `"command": "node", "args": ["/ABSOLUTE/PATH/TO/…/build/index.js"]`
This server is the second kind and is deliberately unpublished, so `node` + absolute path is the documented match. All three of these work:
| Launch command | Start time | Notes |
|---|---|---|
| `node /abs/path/dist/index.js` **(default)** | 0.3s | Runs exactly the code you cloned and built. No network at startup. Update with `git pull && npm run build` — `dist/` is gitignored, so a pull alone leaves the old build in place. |
| `mcp-endpoint-central` (after `npm link`) | 0.3s | Same code, no paths in your config. One extra setup step. |
| `npx -y github:hdyrawan/mcp-endpoint-central` | 7s | No checkout to manage. Recompiles and re-resolves the git ref on every launch, needs git access to the private repo, and follows branch tip unless pinned. |
To pin npx to a specific commit, so a later push can't change what runs with your API token:
```
npx -y github:hdyrawan/mcp-endpoint-central#a27ce7a
```
**Why there's no npm package:** this server holds a credential that, with write tools enabled, can deploy patches to every managed endpoint. Fetching and executing freshly-resolved code at each startup widens that blast radius for no gain on a single-host deployment. So `npx mcp-endpoint-central` resolves to nothing — only the explicit `github:` form works.
</details>
### Local development run
```bash
cp .env.example .env # fill in your values; .env is gitignored
node --env-file=.env dist/index.js
```
### Try it
Once connected, ask your MCP client things like:
- *"List all computers whose agent hasn't contacted the server in over 30 days."*
- *"What critical vulnerabilities affect our network? Which systems are hit worst?"*
- *"Show the patch summary, then the missing patches with severity Important or higher."*
- *"Which computers still have BitLocker disabled on their system drive?"*
- *"What prohibited software was found, and on which machines?"*
## Configuration
All configuration is via environment variables (see `.env.example` for the commented reference):
| Variable | Required | Default | Purpose |
|---|---|---|---|
| `ENDPOINT_CENTRAL_BASE_URL` | yes | — | Base URL of your on-prem server, e.g. `https://ec-server.corp.example:8383`. No trailing path. |
| `ENDPOINT_CENTRAL_API_TOKEN` | yes | — | Static API token from **Admin → Integrations → API Key Management**. Treat like a password. |
| `ENDPOINT_CENTRAL_CA_CERT_PATH` | no | — | Path to a PEM CA bundle if the server's TLS certificate is issued by an internal/private CA. |
| `ENDPOINT_CENTRAL_DEFAULT_PAGE_LIMIT` | no | `50` | Default page size for list tools (server hard cap: 1000). |
| `ENDPOINT_CENTRAL_ENABLE_WRITE_TOOLS` | no | `false` | Set `true` to register mutating tools. Leave unset for a read-only deployment. |
### Generating the API token
Endpoint Central on-prem offers two ways to authenticate to its API. **This server uses the static API key**, which is the only one that works unattended:
| | Static API key | Login API (`/api/1.4/desktop/authentication`) |
|---|---|---|
| How you get it | Generated in the console, once | POST username + base64 password each time |
| With MFA enabled | You complete MFA **once**, in the browser, when generating the key | Every login needs a fresh OTP via `.../otpValidate` |
| Suitable for a headless MCP server | ✅ Yes | ❌ No — a human must read the OTP each time |
| Credentials at rest | One revocable key | Your AD account password |
Steps:
1. Decide which account the key belongs to. The key inherits that account's RBAC role, and on the API Key Management screen some on-prem builds also let you scope the key itself (per-module CRUD, e.g. `Inventory.read`, `Patch.update`) — check for a scope picker when you generate it. Either way, the account's role remains the outer bound: an unscoped key gets everything the role allows, and a scoped key can only narrow that further, never widen it. Prefer a dedicated service account over a person's day-to-day login, and the least-privileged role (and scope, if offered) that still covers the modules you need. (Note: generating an API key requires administrator privilege in the console, so verify with your Endpoint Central admin which roles/scopes are available to you.)
2. Log in to the console as that account and complete MFA normally.
3. Go to **Admin → Integrations → API Key Management**, generate the key, and copy it straight into your secret store. If the form offers an expiry date, note it — the key stops working that day.
4. Put it in `ENDPOINT_CENTRAL_API_TOKEN` and verify with `npm run check` (see below).
A key is invalidated by: regenerating it (the previous key dies immediately), deleting the user it belongs to, or reaching its expiry date if you set one.
### Active Directory + MFA deployments
If your console uses **AD authentication with MFA (email OTP, authenticator app, or similar)**, nothing changes for this server — that's the point of the static key. You authenticate interactively **once** (AD password + OTP) in the browser to generate the key; from then on the key is a self-contained credential and the MCP server never performs an interactive login, never sees your AD password, and never needs an OTP.
Consequences worth planning for:
- **Do not** try to feed AD credentials to this server. It has no username/password mode by design: storing a domain password in an MCP client's config would be a worse credential than a scoped, revocable key, and email OTP would break the flow on every restart anyway.
- **Service account vs. personal account.** A key tied to a person dies when their account is deleted or disabled — including when they leave. Ask your Endpoint Central admin for a dedicated service account where policy allows.
- **AD password rotation.** The key is stored separately from the AD password, but this isn't documented by ManageEngine either way. Test it after your first rotation, and keep `npm run check` handy for a 5-second verdict.
- **Revocation.** To kill access instantly, regenerate the key in the console (or disable the account). Then update `ENDPOINT_CENTRAL_API_TOKEN` wherever it's configured.
### What `npm run check` tells you
It runs two probes and reports exactly where a setup breaks:
1. An **unauthenticated** probe (`server_discover`) — isolates network/TLS problems from credential problems, and prints how your console authenticates (AD enabled, which domains, the default domain).
2. An **authenticated** call — proves the API token itself works.
Because step 1 runs first, a failure at step 2 is unambiguous: the server is reachable and it's the key that's wrong. Diagnoses unreachable host, TLS trust failure, 401 (bad/expired/superseded key) and 403 (valid key, insufficient role) separately, each with the specific fix. It never prints your token.
```
✓ MCP server started — 86 tools registered
✓ Reached the Endpoint Central server (unauthenticated probe)
Console auth: Active Directory enabled | AD domains: CORP, SUBSIDIARY | default: CORP
✓ Reached Endpoint Central and authenticated successfully
```
### TLS with an internal CA
If your Endpoint Central server uses a certificate from an internal CA, export the CA (PEM) and set `ENDPOINT_CENTRAL_CA_CERT_PATH=/path/to/ca.pem`. The server then trusts that CA **in addition to performing full certificate verification** — there is deliberately no "skip TLS verification" option.
## Tool catalog
**113 tools** across 11 modules: 86 read-only (always registered) and 27 mutating (registered only when `ENDPOINT_CENTRAL_ENABLE_WRITE_TOOLS=true`). Legend: 🔍 read-only · ✏️ mutating · 🗑️ destructive.
| Module | Tools | Covers |
|---|---|---|
| Scope of Management | 6 | Managed computers, SoM summary, remote offices; agent install/uninstall, computer removal |
| Inventory | 19 | Hardware, software, licenses, prohibited software, software metering, scan status; scan triggers |
| Patch management (read) | 14 | Patch/system status, scan details, deployment policies & configs, approval settings, APD task list, DB update status |
| Patch management (actions) | 16 | Approve/decline, scans, install/uninstall configurations, patch downloads, DB update, APD task lifecycle |
| Threats & vulnerabilities | 10 | CVEs, vulnerability-to-computer mapping, system/web-server misconfigurations, per-system reports |
| BitLocker | 3 | Encryption reports, recovery keys, TPM details |
| Device control (DCM) | 9 | Device/block audits, per-computer device status, exemptions, file trace & shadow |
| Data loss prevention (DLP) | 11 | Endpoint activity, justifications, false positives, deployed rules/devices/domains/printers |
| Reports & groups | 7 | Query reports (+data), custom reports (+data), custom groups, server properties, auth discovery |
| Custom fields | 11 | Custom column/data-type management, per-computer custom field reads & updates |
| Digital Employee Experience | 7 | DEX scores per device/profile, metric breakdowns, score generation times |
<details>
<summary><strong>Scope of Management (som_*)</strong></summary>
- 🔍 `som_computers_list` — managed computers with hostname, IP, OS, agent/live status; rich filters
- 🔍 `som_summary` — network-wide agent installation and live status summary
- 🔍 `som_remote_offices_list` — remote/branch offices with replication and distribution server details
- ✏️ `som_agent_install` — install the agent on computers already visible in SoM
- 🗑️ `som_agent_uninstall` — uninstall the agent from computers
- 🗑️ `som_computer_remove` — remove computers from the Scope of Management
</details>
<details>
<summary><strong>Inventory (inventory_*)</strong></summary>
- 🔍 `inventory_all_summary` — module-wide scan/software/hardware summary
- 🔍 `inventory_computer_details_summary` — per-computer inventory summary
- 🔍 `inventory_filter_params` — valid values for inventory filters
- 🔍 `inventory_hardware_list` / `inventory_software_list` — all detected hardware/software
- 🔍 `inventory_installed_software_list` — software installed on one computer
- 🔍 `inventory_licenses_list` / `inventory_license_software_list` — license records and compliance
- 🔍 `inventory_prohibited_software_list` — prohibited software and where it's installed
- 🔍 `inventory_scan_computers_list` — per-computer inventory scan status
- 🔍 `inventory_software_metering_summary` — metering rules with usage metrics
- 🔍 `inventory_computers_by_software` / `_by_hardware` / `_with_metering` / `_by_licensed_software` / `_by_license` / `_by_prohibited_software` — computers matching a specific inventory entity
- ✏️ `inventory_scan_computers` / `inventory_scan_all_computers` — trigger an asset scan (write-gated)
</details>
<details>
<summary><strong>Patch management — read (patch_*)</strong></summary>
- 🔍 `patch_summary` — installed/missing/applicable counts and severity breakdown
- 🔍 `patch_all_patches_list` / `patch_all_patch_details` — patch catalog and per-patch computer status
- 🔍 `patch_all_systems_list` — systems with patch health status
- 🔍 `patch_scan_details` — per-system patch scan status
- 🔍 `patch_system_report` — every patch applicable to one computer
- 🔍 `patch_deployment_policies_list` / `patch_view_config` — deployment templates and configurations
- 🔍 `patch_downloaded_patches_list` / `patch_supported_patches_list` — downloaded binaries, supported catalog
- 🔍 `patch_approval_settings` / `patch_health_policy` / `patch_db_update_status` — module settings and DB refresh status
- 🔍 `patch_apd_tasks_list` — Automatic Patch Deployment tasks (source of the `task_name` the APD action tools need)
</details>
<details>
<summary><strong>Patch management — actions (patch_*, apd_task_*)</strong> — write-gated</summary>
- ✏️ `patch_approve` / `patch_unapprove` / `patch_decline` — patch approval workflow
- ✏️ `patch_scan_computers` / `patch_scan_all_computers` — trigger patch scans
- ✏️ `patch_install_on_all_systems` / `patch_install_on_specific_systems` / `patch_install_all_missing_on_systems` — create/deploy install configurations
- 🗑️ `patch_uninstall` — deploy an uninstall-patch configuration
- ✏️ `patch_download` — stage patch binaries on the server without installing them
- ✏️ `patch_db_update` — refresh the server's patch/vulnerability catalog
- ✏️ `apd_task_create` / `apd_task_modify` / `apd_task_suspend` / `apd_task_resume` — Automatic Patch Deployment task lifecycle
- 🗑️ `apd_task_delete` — permanently delete an APD task
</details>
<details>
<summary><strong>Threats & vulnerabilities (threats_*)</strong></summary>
- 🔍 `threats_vulnerabilities_list` — network-wide CVEs with severity/CVSS, exploit status
- 🔍 `threats_vulnerability_computers_detail` — cursor-paged vulnerability-to-computer bulk export
- 🔍 `threats_patches_list` — applicable patches from the threats module
- 🔍 `threats_server_misconfigurations_list` / `threats_system_misconfigurations_list` — hardening findings
- 🔍 `threats_system_report` (+ `_patches`, `_vulnerabilities`, `_server_misconfigurations`, `_system_misconfigurations`) — per-system threat drill-downs
</details>
<details>
<summary><strong>BitLocker (bitlocker_*)</strong></summary>
- 🔍 `bitlocker_reports_list` — drive-level encryption status
- 🔍 `bitlocker_recovery_key_details` — recovery key records (key material excluded from default output; requires `raw: true`)
- 🔍 `bitlocker_tpm_report` — TPM presence/version details
</details>
<details>
<summary><strong>Device control (dcm_*)</strong></summary>
- 🔍 `dcm_device_audit_list` / `dcm_block_device_audit_list` — device activity and blocked-device events
- 🔍 `dcm_device_summary_list` — unique devices detected in the network
- 🔍 `dcm_computer_device_status_list` / `dcm_mac_computer_device_status_list` — per-computer device control status
- 🔍 `dcm_device_exemption_list` / `dcm_device_type_exemption_list` — temporary exemptions
- 🔍 `dcm_file_trace_list` / `dcm_file_shadow_list` — file activity and shadow-copy operations
</details>
<details>
<summary><strong>Data loss prevention (dlp_*)</strong></summary>
- 🔍 `dlp_endpoint_activity_report` — DLP activities on endpoints
- 🔍 `dlp_justification_report` — user business justifications for overrides
- 🔍 `dlp_network_cbfp_report` / `dlp_network_dcfp_report` — reported false positives
- 🔍 `dlp_network_rules_report` / `dlp_network_device_report` / `dlp_network_email_report` / `dlp_network_printer_report` / `dlp_network_usb_printer_report` / `dlp_network_product_report` / `dlp_network_web_domain_report` — deployed DLP entities with custom-group associations
</details>
<details>
<summary><strong>Reports & groups (reports_*, customgroup_list, server_properties)</strong></summary>
- 🔍 `reports_query_reports_list` / `reports_query_report_data` — SQL query reports and their rows
- 🔍 `reports_custom_reports_list` / `reports_custom_report_data` — custom report views and their rows
- 🔍 `customgroup_list` — custom groups with type and member counts
- 🔍 `server_properties` — domains, custom groups, and branch offices managed by the server
- 🔍 `server_discover` — login configuration: AD domains enabled, default domain, local-auth-only flag. The one endpoint needing no authentication, so it still answers when your token is wrong.
</details>
<details>
<summary><strong>Custom fields (custom_column_*, custom_field_*)</strong></summary>
- 🔍 `custom_column_data_types_list` / `custom_column_udt_length` / `custom_column_udt_name_exists` — custom data type metadata
- 🔍 `custom_field_values_get` / `custom_field_metadata_list` — per-resource custom field values and metadata
- ✏️ `custom_column_add` / `custom_column_modify` / `custom_column_value_modify` / `custom_column_data_type_create` — write-gated field management
- ✏️ `custom_field_computer_update` — update field values on one computer
- 🗑️ `custom_column_remove` — delete a custom field and its stored values
(File-upload endpoints — FILE-type field uploads and bulk CSV import — are intentionally not implemented; they require multipart uploads outside this server's JSON-only HTTP client.)
</details>
<details>
<summary><strong>Digital Employee Experience (dex_*)</strong></summary>
- 🔍 `dex_addon_info` / `dex_meta` — DEX module status and configuration
- 🔍 `dex_device_experience` / `dex_device_experience_metrics` — per-device scores and indicator breakdowns
- 🔍 `dex_latest_experience_scores` — latest score for a profile
- 🔍 `dex_last_score_update_time` / `dex_next_score_update_time` — score generation schedule
</details>
> **On-prem parity.** Most of this surface is confirmed against ManageEngine's [on-premise API reference](https://www.manageengine.com/products/desktop-central/api/): SoM, Inventory, Patch, Threats/Vulnerability, BitLocker, Device Control, DLP, and server properties/discovery all appear there.
>
> Four groups are **not** in the on-prem reference and were built from the broader published spec — treat them as unverified until you check your own server's **Admin → Integrations → API Explorer**: **DEX** (`/intelligence/api/*`), **custom fields/columns** (`/dcapi/customColumn`, `/dcapi/customFields`), **query & custom reports** (`/dcapi/reports/*`), and **custom groups** (`/api/1.4/customgroup/getCGList`). Absence from the docs isn't proof of absence from your build — ManageEngine states the published list is partial — but these are where a 404 is most likely.
>
> Conversely, several tools here are **on-premise only** and have no cloud equivalent: `patch_apd_tasks_list`, `patch_db_update`, `patch_download`. Mobile Device Management has its own on-prem API that this server does not cover yet.
## Working with results
- **Pagination:** list tools accept `page` and `pageLimit`. Results include a note like *"Showing 50 of 3210 total (page 1). 3160 more available — call again with page: 2."* so the model knows how to continue.
- **Curated fields:** list responses are projected to the most useful fields by default. Pass `raw: true` to any list tool for the complete, unprojected records.
- **Mutating tools:** only exist when `ENDPOINT_CENTRAL_ENABLE_WRITE_TOOLS=true`, and each call must include `confirm: true` or it is refused with an explanation before any network request is made.
## Security model
This server follows MCP security best practices; the design decisions in short:
| Measure | What it does |
|---|---|
| Read-only default | Write tools are not registered unless explicitly enabled — least privilege at the tool-surface level. |
| Per-call confirm gate | Every mutating tool requires `confirm: true`; refusal happens before any API request. |
| Tool annotations | `readOnlyHint`/`destructiveHint` on every tool let clients enforce their own approval policies. |
| Token hygiene | The token is read once from the environment, used only for the `Authorization` header, and never logged, echoed in errors, or returned in tool results. |
| RBAC-scoped account | Recommended setup binds the token to a dedicated least-privileged service account, bounding blast radius if it leaks. |
| SSRF guard | HTTP-layer refuses absolute URLs/protocol-relative paths; tools may only call hardcoded relative API paths against the one configured host. Dynamic path segments are allowlist-validated. |
| TLS verification always on | Internal CAs are supported via an *additional* trust anchor, never by disabling verification. |
| No secrets in the repo | `.env` is gitignored; `.env.example` documents variables without values. |
Additional deployment recommendations:
- Run the server on a host/network segment that is allowed to reach the Endpoint Central console anyway; the server initiates connections only to `ENDPOINT_CENTRAL_BASE_URL`.
- Prefer the read-only default in day-to-day use; enable write tools only in sessions where remediation is intended, then turn them back off.
- BitLocker recovery keys are secrets — the recovery-key tool keeps key material out of its default output; only request it when genuinely needed.
- Review your MCP client's permission settings so destructive-annotated tools always prompt.
## Troubleshooting
| Symptom | Likely cause / fix |
|---|---|
| Startup fails: `Missing required environment variable` | Set `ENDPOINT_CENTRAL_BASE_URL` / `ENDPOINT_CENTRAL_API_TOKEN` in the client's `env` block (stdio servers don't inherit your shell profile). |
| Server never starts: `spawn ENOENT`, "command not found" | The client can't resolve `node`/`npx` from its `PATH` — see [below](#command-not-found-or-spawn-enoent). |
| `Could not reach Endpoint Central server` | Wrong base URL/port, firewall, or TLS failure. For internal CAs set `ENDPOINT_CENTRAL_CA_CERT_PATH`. |
| `Authentication failed (401)` | Token invalid or revoked — regenerate under **Admin → Integrations → API Key Management**. |
| `Access denied (403)` | The token's account lacks the RBAC role for that module — adjust in **Admin → User Administration**. |
| A tool from the docs is missing | Write tools require `ENDPOINT_CENTRAL_ENABLE_WRITE_TOOLS=true`. Also, on-prem builds/licenses vary — verify the endpoint exists in your console under **Admin → Integrations → API Explorer**. |
| `npm run check` reports far fewer tools than expected, or `Tool <name> not found` | Stale `dist/` build — `dist/` is gitignored and isn't regenerated by `git pull` alone. Run `npm run build` (or `npm install`) after updating, then re-run `npm run check`. |
| Mutating call returns "requires confirm: true" | Working as intended — repeat the call with `confirm: true` once you're sure. |
### Command not found or spawn ENOENT
MCP clients launched from a desktop icon don't inherit your shell's `PATH`, so `node` or `npx` may not resolve even though they work in your terminal. This affects whichever command you name, so it isn't a reason to prefer one over the other. Use an absolute path to the binary:
```bash
which node # e.g. /home/you/.local/bin/node
```
```json
{ "command": "/home/you/.local/bin/node", "args": ["/abs/path/to/dist/index.js"] }
```
If you use [nvm](https://jonathanbossenger.com/2025/07/nvm-node-js-versions-and-mcp/), there's a second trap: `npx` can resolve to an older Node than the one you tested with. Point at the binary inside the version you want (`~/.nvm/versions/node/v22.x.x/bin/…`). This server needs Node ≥ 18.
## Development
```bash
npm run typecheck # tsc --noEmit
npm run build # tsc -> dist/ (also runs automatically on npm install)
npm run dev # tsx src/index.ts
npm run test:smoke # stdio smoke test in both modes (no real server needed):
# verifies read-only mode exposes zero write tools, all tools
# carry annotations, the confirm gate fires before network I/O,
# and the token never leaks into error output
```
Project layout:
```
src/
index.ts # entrypoint: config check, McpServer + stdio transport
config.ts # env-var config (validated once, cached)
httpClient.ts # undici-based client: auth header, custom CA, SSRF guard
errors.ts # API error mapping + confirm-gate helper
shaping.ts # response projection + pagination notes
tools/
annotations.ts # shared READ_ONLY / WRITE / DESTRUCTIVE annotations
som.ts … # one module per API area, registered in tools/index.ts
spec/oas-dc.json # OpenAPI reference — not committed, see AGENTS.md
```
Contributions should follow the conventions in [AGENTS.md](AGENTS.md) — notably: response shaping for every list tool, `assertConfirmed` + write-gating for every mutating tool, and relative-literal API paths only.
CI runs typecheck plus the smoke test on Node 18, 20, and 22 for every push and pull request.
The OpenAPI spec used as a development reference is **not** included in this repo — it is ManageEngine's proprietary document. See [AGENTS.md](AGENTS.md) for how to obtain it; nothing in the build or test suite requires it.
## License
[MIT](LICENSE) © hdyrawan
---
*This is an independent, unofficial integration. ManageEngine, Endpoint Central, and related marks are trademarks of Zoho Corporation, which does not endorse, sponsor, or support this project. Product names are used only to identify the API this server talks to.*
TDQS
Scored across 86 tools
Most tools are clearly differentiated by module prefix and specific resource/action (e.g., patch_summary vs patch_scan_details vs patch_all_systems_list). A few near-duplicates like dcm_device_audit_list vs dcm_block_device_audit_list and multiple DLP report variants could cause hesitation, but descriptions clarify the distinct scopes.
The naming is uniformly snake_case and mostly follows a noun_prefix + action_pattern, but the action words vary inconsistently (list, report, summary, details, get, exists). For example, custom_field_values_get is verb-final while most others are noun-final, and dex_meta/server_properties have no verb at all. Naming is readable but not fully predictable.
With 86 tools, this is far beyond the threshold where an agent can efficiently select among them. Even for a broad enterprise management server, the sheer volume is overwhelming and the server would be better split into several focused MCP servers or consolidated.
The tool surface is almost entirely read-only: every tool retrieves or lists data, with no create, update, delete, or action tools (e.g., no patch deployment, policy change, or device exemption actions). This severely limits the server's usefulness for actual endpoint management, even though the read-side coverage is broad.