-
Notifications
You must be signed in to change notification settings - Fork 3
Add psd-infrastructure plugin: Aruba, FortiAnalyzer, Freshservice, DocBot #80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| { | ||
| "$schema": "https://cdn.jsdelivr.net/npm/@anthropic-ai/claude-code@latest/plugin.schema.json", | ||
| "name": "psd-infrastructure", | ||
| "description": "PSD network and systems infrastructure tools — Aruba wireless, FortiAnalyzer log search, Freshservice tickets, and DocBot documentation, as MCP servers with companion skills. Server code auto-updates from psd401 GitHub repos on launch.", | ||
| "author": { | ||
| "name": "Reese Herber", | ||
| "email": "herberr@psd401.net", | ||
| "organization": "Peninsula School District" | ||
| }, | ||
| "repository": "https://github.com/psd401/psd-claude-plugins", | ||
| "keywords": [ | ||
| "infrastructure", | ||
| "networking", | ||
| "aruba", | ||
| "fortianalyzer", | ||
| "freshservice", | ||
| "documentation", | ||
| "psd", | ||
| "education" | ||
| ], | ||
| "license": "MIT", | ||
| "mcpServers": { | ||
| "aruba": { | ||
| "type": "stdio", | ||
| "command": "bash", | ||
| "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/run-server.sh", "aruba"], | ||
| "env": { "PSD_INFRA_HOME": "${CLAUDE_PLUGIN_DATA}" } | ||
| }, | ||
| "fortianalyzer": { | ||
| "type": "stdio", | ||
| "command": "bash", | ||
| "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/run-server.sh", "fortianalyzer"], | ||
| "env": { "PSD_INFRA_HOME": "${CLAUDE_PLUGIN_DATA}" } | ||
| }, | ||
| "freshservice": { | ||
| "type": "stdio", | ||
| "command": "bash", | ||
| "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/run-server.sh", "freshservice"], | ||
| "env": { "PSD_INFRA_HOME": "${CLAUDE_PLUGIN_DATA}" } | ||
| }, | ||
| "docbot": { | ||
| "type": "stdio", | ||
| "command": "bash", | ||
| "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/run-server.sh", "docbot"], | ||
| "env": { "PSD_INFRA_HOME": "${CLAUDE_PLUGIN_DATA}" } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| # PSD Infrastructure | ||
|
|
||
| Network and systems tooling for Peninsula School District IT, packaged as MCP | ||
| servers plus companion skills that teach Claude the workflows and pitfalls for | ||
| each system. | ||
|
|
||
| ## What's included | ||
|
|
||
| | System | MCP server | Skill | Tools | | ||
| |---|---|---|---| | ||
| | Aruba wireless controllers | `aruba` | `aruba-wireless` | 6 (APs, clients, config, logs — read-only) | | ||
| | FortiAnalyzer | `fortianalyzer` | `fortianalyzer-logs` | 25 (log search, alerts, reports, devices) | | ||
| | Freshservice | `freshservice` | `freshservice-tickets` | 15 (tickets, replies, context, KB, similar-ticket matching) | | ||
| | DocBot (BookStack + FS docs) | `docbot` | `docbot-docs` | 30 (audits, propose/approve/apply changes, screenshots) | | ||
|
|
||
| Server source lives in internal psd401 repos: | ||
| [aruba-mcp](https://github.com/psd401/aruba-mcp), | ||
| [fortianalyzer-mcp](https://github.com/psd401/fortianalyzer-mcp), | ||
| [freshservice-mcp](https://github.com/psd401/freshservice-mcp), | ||
| [DocBot](https://github.com/psd401/DocBot). | ||
|
|
||
| ## Install (one time) | ||
|
|
||
| ``` | ||
| /plugin marketplace add psd401/psd-claude-plugins # skip if already added | ||
| /plugin install psd-infrastructure@psd-claude-plugins | ||
| ``` | ||
|
|
||
| Then: | ||
|
|
||
| 1. **Enable auto-update** for the marketplace: `/plugin` → Marketplaces tab → | ||
| psd-claude-plugins → Enable auto-update. Third-party marketplaces ship with | ||
| auto-update off, so without this step you only get updates by running | ||
| `/plugin marketplace update psd-claude-plugins` manually. | ||
| 2. **Set up credentials**: see [SECRETS-SETUP.md](./SECRETS-SETUP.md). | ||
| Requires `gh` (authenticated), `bun`, and `uv` — install commands are in | ||
| that file too. | ||
|
|
||
| ## How updates work | ||
|
|
||
| Two layers update independently, and both are automatic after the one-time | ||
| setup above: | ||
|
|
||
| 1. **Server code.** On every launch, the launcher (`scripts/run-server.sh`) | ||
| does a fast-forward `git pull` of the server repo before starting it. Push | ||
| to a server repo's default branch and everyone runs the new code on their | ||
| next Claude Code session. Offline or failed pulls fall back to the cached | ||
| copy (kept under Claude's plugin data dir, surviving plugin updates). | ||
| 2. **Skills and plugin config.** With marketplace auto-update enabled, Claude | ||
| Code checks for updates shortly after each session starts and either | ||
| prompts `/reload-plugins` or applies them on the next launch. This plugin | ||
| deliberately has **no `version` field**, so for git-based installs every | ||
| pushed commit counts as a new version — no version bump needed, and no | ||
| forgotten-bump silent staleness. | ||
|
|
||
| Team repos can auto-prompt installation for everyone who trusts the folder by | ||
| committing this to the repo's `.claude/settings.json`: | ||
|
|
||
| ```json | ||
| { | ||
| "extraKnownMarketplaces": { | ||
| "psd-claude-plugins": { | ||
| "source": { "source": "github", "repo": "psd401/psd-claude-plugins" } | ||
| } | ||
| }, | ||
| "enabledPlugins": { | ||
| "psd-infrastructure@psd-claude-plugins": true | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Org-wide, managed settings can additionally set `"autoUpdate": true` on the | ||
| marketplace entry so nobody has to flip the toggle themselves. | ||
|
|
||
| ## First launch is slow | ||
|
|
||
| The first session after install clones each server repo and installs its | ||
| dependencies (a few minutes total). Later launches start in seconds. | ||
|
|
||
| ## Not included (yet) | ||
|
|
||
| `zabbix` and `onesync` MCP servers exist but are excluded from this plugin | ||
| until connection issues are resolved. The `mac-mini` bridge and `pinchtab` | ||
| browser servers are personal tooling and stay out of the shared plugin. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| # Secrets Setup for PSD Infrastructure | ||
|
|
||
| Every server in this plugin talks to a real PSD system and needs credentials. | ||
| Nothing works until you set these up. Credentials never live in this repo. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - `gh` CLI authenticated to GitHub (`gh auth login`) — the server code lives in | ||
| internal psd401 repos and is cloned on first launch | ||
| - `bun` (`brew install oven-sh/bun/bun`) — runs fortianalyzer, freshservice, docbot | ||
| - `uv` (`brew install uv`) — runs the Aruba server | ||
|
|
||
| ## Which servers need which keys? | ||
|
|
||
| | Server | Required | Optional | | ||
| |---|---|---| | ||
| | `aruba` | `ARUBA_HOST`, `ARUBA_USERNAME`, `ARUBA_PASSWORD` | `ARUBA_PORT` (default 4343), `ARUBA_VERIFY_SSL` (default false) | | ||
| | `fortianalyzer` | `FAZ_HOST`, `FAZ_USERNAME`, `FAZ_PASSWORD` | `FAZ_ADOM` | | ||
| | `freshservice` | `FRESHSERVICE_DOMAIN`, `FRESHSERVICE_API_KEY`, `FRESHSERVICE_AGENT_EMAIL` | | | ||
| | `docbot` | `BOOKSTACK_URL`, `BOOKSTACK_TOKEN_ID`, `BOOKSTACK_TOKEN_SECRET` | `FRESHSERVICE_ENABLED`, `FRESHSERVICE_DOMAIN`, `FRESHSERVICE_API_KEY`, `GEMINI_API_KEY`, `DOCBOT_DB_PATH`, `DOCBOT_LOG_LEVEL` | | ||
|
|
||
| Ask Reese Herber (herberr@psd401.net) for host values and service accounts. | ||
| Freshservice API keys are per-agent: Freshservice → Profile Settings → API Key. | ||
|
|
||
| ## Option A: Shared env file (recommended) | ||
|
|
||
| Create `~/.config/psd-infrastructure/.env`: | ||
|
|
||
| ```bash | ||
| mkdir -p ~/.config/psd-infrastructure | ||
| cat > ~/.config/psd-infrastructure/.env <<'EOF' | ||
| ARUBA_HOST=... | ||
| ARUBA_USERNAME=... | ||
| ARUBA_PASSWORD=... | ||
|
|
||
| FAZ_HOST=... | ||
| FAZ_USERNAME=... | ||
| FAZ_PASSWORD=... | ||
|
|
||
| FRESHSERVICE_DOMAIN=... | ||
| FRESHSERVICE_API_KEY=... | ||
| FRESHSERVICE_AGENT_EMAIL=you@psd401.net | ||
|
|
||
| BOOKSTACK_URL=... | ||
| BOOKSTACK_TOKEN_ID=... | ||
| BOOKSTACK_TOKEN_SECRET=... | ||
| EOF | ||
| chmod 600 ~/.config/psd-infrastructure/.env | ||
| ``` | ||
|
|
||
| The launcher script loads this file automatically before starting each server. | ||
| Set `PSD_INFRA_ENV` to point somewhere else if you keep secrets elsewhere. | ||
|
|
||
| ## Option B: Shell profile | ||
|
|
||
| Export the same variables from `~/.zshrc`. Both options work; the env file | ||
| keeps infrastructure credentials separate from your general shell environment. | ||
|
|
||
| ## Only using some servers? | ||
|
|
||
| Set up only the credentials you need. A server with missing credentials fails | ||
| on first tool call with a clear error; the others are unaffected. You can also | ||
| disable individual servers under `/plugin` → manage plugins. |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,71 @@ | ||||||||||||||
| #!/usr/bin/env bash | ||||||||||||||
| # Launcher for psd-infrastructure MCP servers. | ||||||||||||||
| # Clones the server repo on first use, pulls updates on every launch, | ||||||||||||||
| # then execs the server. Server code lives in internal psd401 repos; | ||||||||||||||
| # users need `gh auth login` (or equivalent git credentials) once. | ||||||||||||||
| set -euo pipefail | ||||||||||||||
|
|
||||||||||||||
| NAME="${1:?usage: run-server.sh <aruba|fortianalyzer|freshservice|docbot>}" | ||||||||||||||
| CACHE_ROOT="${PSD_INFRA_HOME:-$HOME/.psd-infrastructure}/servers" | ||||||||||||||
| DIR="$CACHE_ROOT/$NAME" | ||||||||||||||
|
|
||||||||||||||
| case "$NAME" in | ||||||||||||||
| aruba) REPO="psd401/aruba-mcp" ;; | ||||||||||||||
| fortianalyzer) REPO="psd401/fortianalyzer-mcp" ;; | ||||||||||||||
| freshservice) REPO="psd401/freshservice-mcp" ;; | ||||||||||||||
| docbot) REPO="psd401/DocBot" ;; | ||||||||||||||
| *) echo "unknown server: $NAME" >&2; exit 1 ;; | ||||||||||||||
| esac | ||||||||||||||
|
|
||||||||||||||
| # Optional shared env file (see SECRETS-SETUP.md). Shell-profile exports also work. | ||||||||||||||
| ENV_FILE="${PSD_INFRA_ENV:-$HOME/.config/psd-infrastructure/.env}" | ||||||||||||||
| if [ -f "$ENV_FILE" ]; then | ||||||||||||||
| set -a | ||||||||||||||
| # shellcheck disable=SC1090 | ||||||||||||||
| . "$ENV_FILE" | ||||||||||||||
| set +a | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| log() { echo "[psd-infrastructure/$NAME] $*" >&2; } | ||||||||||||||
|
|
||||||||||||||
| clone_repo() { | ||||||||||||||
| mkdir -p "$CACHE_ROOT" | ||||||||||||||
| if command -v gh >/dev/null 2>&1; then | ||||||||||||||
| gh repo clone "$REPO" "$DIR" -- --quiet >&2 | ||||||||||||||
| else | ||||||||||||||
| git clone --quiet "https://github.com/$REPO.git" "$DIR" >&2 | ||||||||||||||
| fi | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if [ ! -d "$DIR/.git" ]; then | ||||||||||||||
| log "first run: cloning $REPO" | ||||||||||||||
| clone_repo | ||||||||||||||
| else | ||||||||||||||
| # Best-effort update; a failed pull (offline, etc.) falls back to cached code. | ||||||||||||||
| git -C "$DIR" pull --ff-only --quiet >&2 || log "update check failed, using cached copy" | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| cd "$DIR" | ||||||||||||||
|
|
||||||||||||||
| case "$NAME" in | ||||||||||||||
| aruba) | ||||||||||||||
| command -v uv >/dev/null 2>&1 || { log "uv is required (brew install uv)"; exit 1; } | ||||||||||||||
| exec uv run aruba_mcp.py | ||||||||||||||
| ;; | ||||||||||||||
| fortianalyzer) | ||||||||||||||
| command -v bun >/dev/null 2>&1 || { log "bun is required (brew install oven-sh/bun/bun)"; exit 1; } | ||||||||||||||
| bun install --silent >&2 || true | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Running
Suggested change
|
||||||||||||||
| exec bun run src/index.ts | ||||||||||||||
| ;; | ||||||||||||||
| freshservice) | ||||||||||||||
| command -v bun >/dev/null 2>&1 || { log "bun is required (brew install oven-sh/bun/bun)"; exit 1; } | ||||||||||||||
| bun install --silent >&2 || true | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Running
Suggested change
|
||||||||||||||
| exec bun run src/index.ts | ||||||||||||||
| ;; | ||||||||||||||
| docbot) | ||||||||||||||
| command -v bun >/dev/null 2>&1 || { log "bun is required (brew install oven-sh/bun/bun)"; exit 1; } | ||||||||||||||
| bun install --silent >&2 || true | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Running
Suggested change
|
||||||||||||||
| export DOCBOT_DB_PATH="${DOCBOT_DB_PATH:-$DIR/docbot.db}" | ||||||||||||||
| exec bun run src/index.ts | ||||||||||||||
| ;; | ||||||||||||||
| esac | ||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,60 @@ | ||||||||
| --- | ||||||||
| name: aruba-wireless | ||||||||
| description: Query the Aruba wireless Mobility Conductor for AP status, connected clients, running config, system logs, and wireless configuration objects (SSID/VAP/AP-group/role) for read-only network troubleshooting; use when an AP is down, a user can't connect to wifi, or you need to review wireless configuration. | ||||||||
| triggers: | ||||||||
| - "check aruba" | ||||||||
| - "AP is down" | ||||||||
| - "wireless AP offline" | ||||||||
| - "check wifi clients" | ||||||||
| - "who's connected to this SSID" | ||||||||
| - "check wireless config" | ||||||||
| - "aruba controller status" | ||||||||
| - "check AP group" | ||||||||
| - "wireless troubleshooting" | ||||||||
| allowed-tools: Read, Bash | ||||||||
| version: 0.1.0 | ||||||||
|
Comment on lines
+14
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Specifying
Suggested change
|
||||||||
| --- | ||||||||
|
|
||||||||
| # Aruba Wireless Skill | ||||||||
|
|
||||||||
| ## What this covers | ||||||||
|
|
||||||||
| The `aruba` MCP server talks to PSD's Aruba Mobility Conductor for read-only wireless monitoring and config review: AP status, connected clients, running config, system logs, and AOS 8 hierarchical configuration objects (SSID/VAP/AP-group/role). Use it for wireless troubleshooting and config auditing — there is no tool to change config, reboot an AP, or kick a client. All queries run against the conductor; it does not reliably reach through to individual managed devices (see Pitfalls). | ||||||||
|
|
||||||||
| ## Available tools | ||||||||
|
|
||||||||
| | Tool | Purpose | | ||||||||
| |---|---| | ||||||||
| | `aruba_show` | Run any `show ...` AOS CLI command and return the JSON result | | ||||||||
| | `aruba_get_aps` | Query the AP database, optionally filtered by group name or up/down status | | ||||||||
| | `aruba_get_clients` | Query connected clients/users, optionally filtered by SSID substring | | ||||||||
| | `aruba_get_config` | Retrieve running-config, full or a single section by keyword | | ||||||||
| | `aruba_get_logs` | Retrieve system logs by category (system/security/wireless/etc), default 50 entries | | ||||||||
| | `aruba_get_node_config` | Query AOS 8 hierarchical configuration objects (SSID/VAP/AP-group/role) for a specific config path | | ||||||||
|
|
||||||||
| ## Common workflows | ||||||||
|
|
||||||||
| 1. **AP is reported down.** `aruba_get_aps` with `status="down"` (optionally `group="<group-name>"`) to confirm and scope the outage → `aruba_get_logs` with `category="wireless"` to check for recent flap/reboot events around that time → `aruba_show` with a targeted command like `show ap debug system-status ap-name <ap-name>` for detail on that specific AP. | ||||||||
|
|
||||||||
| 2. **User can't connect to a specific SSID.** `aruba_get_clients` with `network="<ssid-substring>"` to see who's currently associated on that network → `aruba_get_logs` with `category="security"` to check for recent auth rejects → `aruba_show` with `command="show aaa authentication dot1x default"` (or the relevant dot1x profile) to check EAP/termination settings. | ||||||||
|
|
||||||||
| 3. **Review a group's wireless profile configuration.** `aruba_get_aps` with `group="<group-name>"` to confirm the AP group exists and see its membership → `aruba_get_node_config` with `config_path="/md/<group-name>"` and one specific `object_type` (e.g. `"ssid_prof"`) to pull just that profile type for the group. Never omit `object_type` or loop this across every object type for the group — see Pitfalls. | ||||||||
|
|
||||||||
| 4. **Config doesn't show up in running-config.** `aruba_get_config` with a `section` keyword first → if that section comes back empty, fall back to `aruba_get_node_config` with the matching `object_type` — SSID/VAP/role config pushed from the conductor to managed devices often isn't visible via `running-config` at all. | ||||||||
|
|
||||||||
| 5. **Ad hoc diagnostic command.** `aruba_show` with any `show ...` command for one-off checks (`show version`, `show ap database long`, etc). Treat the result as conductor-scoped even if you pass `node` — see Pitfalls. | ||||||||
|
|
||||||||
| ## Pitfalls & limits | ||||||||
|
|
||||||||
| - **Never call `aruba_get_node_config` with only a group-level `config_path` and no narrow `object_type`, and never loop it across every object type for a group.** An unscoped group-level pull (e.g. `/md/<group-name>`) can return 90KB+ of JSON in a single response and will blow the context window. Always pass one specific `object_type` (`ssid_prof`, `virtual_ap`, `ap_group`, `role`, etc.) per call, and prefer the smallest group scope that answers the question. | ||||||||
| - `aruba_get_node_config` only accepts group-level `config_path` values (e.g. `/md`, `/md/<group-name>`). A leaf-level path pointing at an individual managed device returns `{"Error": "Invalid config path"}` — don't target a specific controller hostname in `config_path`. | ||||||||
| - The `node` parameter on `aruba_show` is unreliable — targeting a specific managed device can silently return the conductor's own result instead of erroring. Don't trust `aruba_show` output as device-specific just because `node` was set; confirm against an obviously device-specific field (model, uptime) before relying on it. | ||||||||
| - `aruba_show` commands must start with `show ` (validated server-side) — other AOS CLI syntax is rejected. | ||||||||
| - This server is read-only against a production wireless controller — there is no create/update/delete tool of any kind. | ||||||||
| - Failed or empty API calls come back as `{"_error": "..."}` or `{"_raw": "..."}` inside the JSON rather than raising — check for those keys before treating a result as structured data. | ||||||||
| - SSID/VAP/role configuration pushed from the conductor to managed devices frequently doesn't appear via `aruba_get_config`'s running-config section search — use `aruba_get_node_config` for those object types instead. | ||||||||
| - The server authenticates with a single shared read-only service account — there's no per-user credential, so don't ask IT staff for their own controller login. | ||||||||
|
|
||||||||
| ## Setup | ||||||||
|
|
||||||||
| See `../../SECRETS-SETUP.md` for credential setup. This server needs `ARUBA_HOST`, `ARUBA_USERNAME`, `ARUBA_PASSWORD` (optional: `ARUBA_PORT`, defaults to `4343`; `ARUBA_VERIFY_SSL`, defaults to `false`). | ||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If
ghis installed but not authenticated (or authenticated to a different account/org),gh repo clonewill fail and immediately exit the script due toset -e. Adding a fallback to standardgit cloneon failure, and cleaning up any partial directory, makes the cloning process much more robust.