Skip to main content
Glama
jagalliers

appd-mcp

by jagalliers
README.md
# appd-mcp

[![CI](https://img.shields.io/github/actions/workflow/status/jagalliers/appd-mcp/ci.yml?branch=main&label=CI&logo=github)](https://github.com/jagalliers/appd-mcp/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Node](https://img.shields.io/badge/node-%E2%89%A522-brightgreen)](package.json)

> **What is this?**
> A [Model Context Protocol](https://modelcontextprotocol.io) server,
> written in TypeScript, that exposes 12 read-only AppDynamics
> capabilities (applications, topology, metrics, snapshots, health
> rules, anomalies, events, alerting config, analytics events,
> dependency map) as task-oriented MCP tools an LLM agent can call
> directly. stdio transport, OAuth API-client credentials, no
> writes, no persistence beyond a local `.env`.

> `appd-mcp` is **not** affiliated with, endorsed by, sponsored by,
> or certified by Cisco Systems, Inc., Splunk LLC, or AppDynamics,
> LLC. See [`TRADEMARKS.md`](TRADEMARKS.md).

## Quickstart (60 seconds)

Requires **Node 22+** and **pnpm**.

```bash
git clone https://github.com/jagalliers/appd-mcp.git
cd appd-mcp
pnpm install
pnpm build
pnpm wizard      # interactive wizard: prompts, validates, writes .env, registers in MCP host
```

> **v0.1.1 and earlier docs may say `pnpm setup`.** That name was
> renamed to `pnpm wizard` in v0.1.2 because `setup` collides with
> pnpm's built-in command (it configures `PNPM_HOME` / `PATH`, not
> our wizard).

That's it. The wizard probes your live AppDynamics Controller before
saving anything, refuses `http://` URLs, won't run unattended (TTY
required), and writes `.env` with `0600` perms plus a timestamped
backup. Re-runs are idempotent.

If you'd rather configure by hand, see
[Documentation → Manual configuration](#manual-configuration) below.

## Tools

| Tool | Capability |
| --- | --- |
| `appd_list_applications` | List business applications |
| `appd_get_application_model` | Topology: business transactions, tiers, nodes, backends |
| `appd_get_metric_hierarchy` | Discover valid metric paths |
| `appd_query_metrics` | Query metric-data-v2 with rollup + time range |
| `appd_get_transaction_snapshots` | Slow/error/diagnostic snapshots with optional exit calls |
| `appd_get_health_rule_violations` | Active/recent health rule violations |
| `appd_get_anomaly_violations` | Anomaly violations + suspected causes |
| `appd_get_events` | Event timeline (deployments, errors, custom, etc.) |
| `appd_list_health_rules` | List/inspect health rule definitions |
| `appd_get_alerting_config` | Composite: policies + actions + schedules + health-rules |
| `appd_query_analytics_events` | ADQL queries against the Events Service |
| `appd_get_dependency_map` | Synthesized service dependency map |

## Status

**Phase 1: read-only MVP.** All 12 tools are read-only against the
public AppDynamics Controller, Alerting, Anomaly Violation, and
Events Service APIs.

## Limitations and non-goals

This is intentionally a small, focused project. The following are
**out of scope** for the current release:

- **No write/change tools.** Nothing in this server mutates
  AppDynamics state. Phase 3 will introduce write tools with
  per-tool allowlist + dryRun + confirm + audit gating; until then
  the server is provably read-only by construction.
- **No sensitive-read tools.** Audit logs, RBAC details, and PII
  surfaces are deferred to Phase 2.
- **No on-prem auth modes yet.** OAuth API-client credentials are
  the only auth path. Username + access-key, SAML federation, and
  on-prem proxy patterns are open questions in
  [AGENT_BOOTSTRAP.md §8](AGENT_BOOTSTRAP.md#8-open-questions).
- **No Synthetic.** AppDynamics Synthetic monitoring tools are not
  in this MVP.
- **No persistent server-side state.** Caches are in-process LRU,
  resetting on restart.
- **No production hosting opinion.** The intended deployment is
  per-developer, per-MCP-host (Cursor or Claude Desktop on a
  workstation). Multi-tenant, internet-facing deployment is not a
  goal.

See [AGENT_BOOTSTRAP.md](AGENT_BOOTSTRAP.md) for the full phase
roadmap.

## Documentation

- [AGENT_BOOTSTRAP.md](AGENT_BOOTSTRAP.md) — canonical onboarding
  document (architecture, tool catalog, conventions, decision log,
  open questions). New devs/agents start here.
- [CONTRIBUTING.md](CONTRIBUTING.md) — dev setup, conventions, how
  to add a tool, PR expectations.
- [SECURITY.md](SECURITY.md) — vulnerability disclosure policy.
- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) — Contributor Covenant
  v2.1.
- [TRADEMARKS.md](TRADEMARKS.md) — independence statement and
  third-party trademark disclaimers.
- [NOTICE](NOTICE) and
  [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) — attribution
  for ported upstream code (the setup wizard ports from the
  Apache-2.0 [`spl-bridge`](https://github.com/jagalliers/spl-bridge)
  project).
- [docs/wizard-pty-validation/](docs/wizard-pty-validation/) —
  archived POSIX-only PTY harness for hand-iterating wizard
  prompts; not part of CI.

### Setup wizard

`pnpm wizard` runs an interactive wizard that drops you into a
working install in five steps:

1. **Prerequisites** — checks Node version, that `dist/index.js`
   exists, and that `.env` is writable. Detects Cursor / Claude
   Desktop installations.
2. **Collect** — prompts for Controller URL, account name
   (auto-suggested from the URL's leftmost label), API Client ID,
   and API Client Secret. Optionally collects Events Service URL
   and key. Always re-prompts secrets across retries.
3. **Probe** — exercises the OAuth endpoint, lists applications via
   the Controller, and (if events were configured) issues a smoke
   ADQL. A failure shows the exact endpoint and offers Edit /
   Save-anyway / Quit, bounded to 3 attempts. A 500
   `Error provisioning account on any cluster` on the Events probe
   degrades to **SKIP** (the tenant simply isn't entitled for
   Analytics) instead of failing the wizard.
4. **Persist** — atomic-write `.env` with a timestamped
   `.bak.<ISO>` and `chmod 0600`. Pre-validates against the Zod
   runtime schema before writing.
5. **MCP host wiring** — pick from `Cursor (project)`,
   `Cursor (user)`, or `Print snippet only`. The chosen target
   uses Cursor's native `envFile` field so no secrets land in
   `mcp.json`.

The wizard refuses to run when stdin is not a TTY (no piping
secrets) and rejects `http://` URLs as a hard-stop. Exit codes:
`0` success, `1` user-driven abort, `2` safety-gate decline /
non-TTY.

To verify a saved `.env` against the live Controller without
reconfiguring anything, run:

```bash
pnpm wizard-live-probe
```

### Manual configuration

If you don't want to use `pnpm wizard`, all configuration is via
environment variables. See [.env.example](.env.example) for the
full list. Required:

- `APPD_CONTROLLER_URL` — the AppDynamics Controller base URL.
- `APPD_ACCOUNT_NAME` — your tenant name.
- `APPD_API_CLIENT_ID` / `APPD_API_CLIENT_SECRET` — OAuth API
  Client credentials (create in the Controller UI under
  **Settings → Administration → API Clients**).

Optional:

- `APPD_EVENTS_SERVICE_URL` / `APPD_EVENTS_API_KEY` — only needed
  for `appd_query_analytics_events`.
- `APPD_TIMEOUT_MS`, `APPD_LOG_LEVEL`, `APPD_TOOLS_ENABLED` —
  tuning.

Then start the server directly:

```bash
node --env-file=.env dist/index.js
```

### Wiring into a host

#### Cursor (recommended path)

Cursor supports both **workspace-scoped**
(`<repo>/.cursor/mcp.json`, ships with the repo) and
**user-scoped** (`~/.cursor/mcp.json`, applies globally) MCP
config. For dev-on-this-repo use, the workspace scope is cleanest
because it uses Cursor's `${workspaceFolder}` variable and Cursor's
native `envFile` support — no absolute paths.

`<repo>/.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "appdynamics": {
      "type": "stdio",
      "command": "node",
      "args": ["${workspaceFolder}/dist/index.js"],
      "envFile": "${workspaceFolder}/.env"
    }
  }
}
```

Then, in Cursor:

1. Run `pnpm install && pnpm build` once, and create `.env` (see
   [Manual configuration](#manual-configuration)).
2. Restart Cursor (or reload the window).
3. Open **Settings → Tools & MCP** (Cmd+Shift+J → Features → Model
   Context Protocol). Toggle the `appdynamics` server on.
4. The 12 `appd_*` tools become available to the agent. The agent
   will ask for confirmation before invoking them by default;
   `Output → MCP Logs` is the place to debug if a server fails to
   start.

This repo's default `.gitignore` excludes `.cursor/`, so
`.cursor/mcp.json` does not get committed — paste the snippet above
into your own checkout.

User-scoped config (`~/.cursor/mcp.json`) is functionally similar
but `${workspaceFolder}` doesn't help, so use absolute paths there.
See [the Cursor MCP docs](https://cursor.com/docs/mcp) for details.

`pnpm wizard` writes either of these for you.

#### Claude Desktop

Add to
`~/Library/Application Support/Claude/claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "appdynamics": {
      "command": "node",
      "args": [
        "--env-file=/absolute/path/to/appd_mcp/.env",
        "/absolute/path/to/appd_mcp/dist/index.js"
      ]
    }
  }
}
```

Note that Claude Desktop does not currently honor an `envFile`
field, so absolute paths are required.

### Development

```bash
pnpm install         # one-time
pnpm lint            # biome check
pnpm typecheck       # tsc --noEmit
pnpm test            # vitest run (uses undici.MockAgent — no live AppD calls)
pnpm test:watch      # vitest in watch mode
pnpm build           # tsc -p tsconfig.build.json → dist/
pnpm smoke           # spawn dist/index.js, drive initialize + tools/list (no live AppD calls)
pnpm test:wizard     # in-process behavioral spot-checks for the setup wizard (requires build)
```

CI (GitHub Actions, see [`.github/workflows/ci.yml`](.github/workflows/ci.yml))
runs lint → typecheck → test → build → smoke → test:wizard →
`pnpm audit --audit-level high` on every push/PR to `main`.

Architecture, conventions, decision log, and open questions all
live in [AGENT_BOOTSTRAP.md](AGENT_BOOTSTRAP.md). New agents/devs
should start there.

### Live integration testing

Once you have an AppDynamics tenant + API client credentials, you
can exercise all 12 tools end-to-end against a real Controller. The
integration runner spawns `dist/index.js` via the MCP client SDK
(the same transport Claude Desktop / Cursor use) and drives a
17-step scenario across health, per-app deep walk, alerting,
aggregation, skippable surfaces, and negative paths.

```bash
# 1. Drop creds into a local .env (gitignored) — or use `pnpm wizard`.
cp .env.example .env
# edit .env:
#   APPD_CONTROLLER_URL=https://<tenant>.saas.appdynamics.com
#   APPD_API_CLIENT_ID=<api-client-name>
#   APPD_API_CLIENT_SECRET=<secret>

# 2. Confirm the controller's accountName by probing the OAuth endpoint.
#    Tries the subdomain first (e.g. `<tenant>` for SaaS), then `customer1`.
#    If APPD_ACCOUNT_NAME is already set, only that one is tried.
pnpm probe-account
# → prints `APPD_ACCOUNT_NAME=<resolved>` on success.

# 3. Build (ensures dist/ matches src/) and run the full integration walk.
pnpm build
pnpm integration
```

Per-step JSON artifacts and a combined `report.json` land under
`tmp/integration/<ISO-timestamp>/` (gitignored). The runner also
captures the spawned server's stderr and asserts that the literal
`APPD_API_CLIENT_SECRET` value never appears in it (defense-in-depth
on top of the pino redaction unit tests).

The runner does **not** run in CI: it requires a long-lived secret
and hits a live tenant. Run it locally before any release.

After a successful run, **rotate the API client secret** in the
Controller UI (Administration → API Clients → Generate Secret) —
especially if the credential ever transited a chat or shared
terminal.

## Security

- **No hardcoded secrets.** All credentials via env vars.
- **OAuth tokens are never logged** (pino redaction is mandatory).
- **stdio transport only** — stdout reserved for MCP frames, logs
  go to stderr.
- **TLS verification on by default.** `APPD_INSECURE_SKIP_VERIFY=true`
  exists for dev-only and must never be used in production.

For vulnerability disclosure, see [SECURITY.md](SECURITY.md). The
full list of conventions lives in
[AGENT_BOOTSTRAP.md §6 Conventions](AGENT_BOOTSTRAP.md#6-conventions-non-negotiable).

## License

[MIT](LICENSE). Portions of [`src/setup/`](src/setup/) are ported
from the Apache-2.0 licensed
[`spl-bridge`](https://github.com/jagalliers/spl-bridge) project;
see [NOTICE](NOTICE) and
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).

TDQS

A3.9/5.0

Scored across 12 tools

Disambiguation5/5

Each tool targets a distinct AppDynamics resource or function (e.g., alerting config, anomaly violations, application model, dependency map, events, health rule violations, metric hierarchy, transaction snapshots, applications list, health rules list, analytics events, metrics query). No overlapping purposes; descriptions clearly differentiate the tools.

Naming Consistency5/5

All tools follow a consistent 'appd_verb_noun' pattern in snake_case. Verbs are predominantly 'get', 'list', or 'query', applied appropriately to each operation. The naming is predictable and aids agent selection.

Tool Count5/5

12 tools is well-scoped for an AppDynamics monitoring server. The set covers essential monitoring and RCA workflows without being overwhelming. Each tool serves a clear purpose and earns its place.

Completeness4/5

The tool surface covers key monitoring operations: application discovery, model, health rule violations, anomaly violations, events, metrics, transaction snapshots, dependencies, alerting config, health rules, metric hierarchy, and analytics events. Minor gaps exist (e.g., no tool for dashboard retrieval or detailed health rule by ID), but core workflows are fully supported.

Maintenance

ActivityInactive
ResponsivenessNo issues