From 87f6d9f03f29f5406489269f5ed442ffaff6db07 Mon Sep 17 00:00:00 2001 From: Reese Herber Date: Wed, 15 Jul 2026 15:20:46 -0700 Subject: [PATCH] feat: add psd-infrastructure plugin (Aruba, FortiAnalyzer, Freshservice, DocBot) Four MCP servers bundled via a clone-and-pull launcher plus companion skills. Server code auto-updates from internal psd401 repos on every launch; the plugin omits its version field so every marketplace commit ships to users with auto-update enabled. Marketplace 2.22.1 -> 2.23.0. --- .claude-plugin/marketplace.json | 18 ++- CHANGELOG.md | 1 + .../.claude-plugin/plugin.json | 48 ++++++++ plugins/psd-infrastructure/README.md | 84 +++++++++++++ plugins/psd-infrastructure/SECRETS-SETUP.md | 63 ++++++++++ .../psd-infrastructure/scripts/run-server.sh | 71 +++++++++++ .../skills/aruba-wireless/SKILL.md | 60 ++++++++++ .../skills/docbot-docs/SKILL.md | 110 ++++++++++++++++++ .../skills/fortianalyzer-logs/SKILL.md | 99 ++++++++++++++++ .../skills/freshservice-tickets/SKILL.md | 89 ++++++++++++++ 10 files changed, 642 insertions(+), 1 deletion(-) create mode 100644 plugins/psd-infrastructure/.claude-plugin/plugin.json create mode 100644 plugins/psd-infrastructure/README.md create mode 100644 plugins/psd-infrastructure/SECRETS-SETUP.md create mode 100755 plugins/psd-infrastructure/scripts/run-server.sh create mode 100644 plugins/psd-infrastructure/skills/aruba-wireless/SKILL.md create mode 100644 plugins/psd-infrastructure/skills/docbot-docs/SKILL.md create mode 100644 plugins/psd-infrastructure/skills/fortianalyzer-logs/SKILL.md create mode 100644 plugins/psd-infrastructure/skills/freshservice-tickets/SKILL.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index d82379b..21d2e3f 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -7,10 +7,26 @@ }, "metadata": { "description": "PSD Plugin Marketplace for Claude Code and Claude Cowork — coding workflows, productivity agents, and district automation", - "version": "2.22.1", + "version": "2.23.0", "pluginRoot": "./plugins" }, "plugins": [ + { + "name": "psd-infrastructure", + "source": "./plugins/psd-infrastructure", + "description": "PSD network and systems infrastructure — Aruba wireless, FortiAnalyzer log search, Freshservice tickets, and DocBot documentation as MCP servers with companion skills; server code auto-updates from psd401 repos on launch", + "category": "infrastructure", + "keywords": [ + "infrastructure", + "networking", + "aruba", + "fortianalyzer", + "freshservice", + "documentation", + "psd", + "education" + ] + }, { "name": "psd-coding-system", "source": "./plugins/psd-coding-system", diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c006d6..4faf45d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- **New plugin: `psd-infrastructure`** — network and systems tooling as four bundled MCP servers with companion skills: Aruba wireless (`aruba-wireless`, 6 tools), FortiAnalyzer (`fortianalyzer-logs`, 25 tools), Freshservice (`freshservice-tickets`, 15 tools), DocBot (`docbot-docs`, 30 tools). Server code 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)) and is cloned/`git pull`ed by the launcher on every server start, so server-side changes ship to users without a plugin release. The plugin deliberately omits the `version` field so every marketplace commit counts as a new plugin version (no forgotten-bump staleness). Credentials via env vars or `~/.config/psd-infrastructure/.env` (see the plugin's SECRETS-SETUP.md); requires `gh`, `bun`, `uv`. Marketplace **2.22.1 → 2.23.0**. - **Cloud routines for triage, lfg, and pr-fix** (no plugin version bump — routines are infrastructure, not part of `/plugin install` distribution): - **`routines/triage`** — autonomous FreshService → GitHub triage. Runs twice daily (cron `0 6,18 * * *`). Polls software-dev workspace (ID 13), filters by `[claude-routine-triaged]` private-note marker, classifies tickets to `psd401/aistudio` / `psd401/psd-workflow-automation` / `psd401/psd-claude-plugins`, runs full Phase 1.5 diagnosis fan-out (`repo-research-analyst` + `git-history-analyzer` + `bug-reproduction-validator`), files an issue, posts private note (with diagnosis brief) + public reply to FreshService. Per-fire cap of 5 tickets. - **`routines/lfg`** — autonomous end-to-end implementation for issues labeled `lfg-ready`. Runs every 6 hours (cron `0 */6 * * *`). Label state machine: `lfg-ready` → `lfg-in-progress` → `lfg-pr-open` / `lfg-blocked`. Branches `claude/lfg-issue--` from `dev` (PSD convention), runs research + implement + test + validate + security-audit, opens PR targeting `dev`. One issue per fire. Honors `lfg-skip` opt-out label. diff --git a/plugins/psd-infrastructure/.claude-plugin/plugin.json b/plugins/psd-infrastructure/.claude-plugin/plugin.json new file mode 100644 index 0000000..eccf165 --- /dev/null +++ b/plugins/psd-infrastructure/.claude-plugin/plugin.json @@ -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}" } + } + } +} diff --git a/plugins/psd-infrastructure/README.md b/plugins/psd-infrastructure/README.md new file mode 100644 index 0000000..56f2e1f --- /dev/null +++ b/plugins/psd-infrastructure/README.md @@ -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. diff --git a/plugins/psd-infrastructure/SECRETS-SETUP.md b/plugins/psd-infrastructure/SECRETS-SETUP.md new file mode 100644 index 0000000..1ff564a --- /dev/null +++ b/plugins/psd-infrastructure/SECRETS-SETUP.md @@ -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. diff --git a/plugins/psd-infrastructure/scripts/run-server.sh b/plugins/psd-infrastructure/scripts/run-server.sh new file mode 100755 index 0000000..1ab8b82 --- /dev/null +++ b/plugins/psd-infrastructure/scripts/run-server.sh @@ -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 }" +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 + 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 + 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 + export DOCBOT_DB_PATH="${DOCBOT_DB_PATH:-$DIR/docbot.db}" + exec bun run src/index.ts + ;; +esac diff --git a/plugins/psd-infrastructure/skills/aruba-wireless/SKILL.md b/plugins/psd-infrastructure/skills/aruba-wireless/SKILL.md new file mode 100644 index 0000000..7efa350 --- /dev/null +++ b/plugins/psd-infrastructure/skills/aruba-wireless/SKILL.md @@ -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 +--- + +# 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=""`) 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 ` for detail on that specific AP. + +2. **User can't connect to a specific SSID.** `aruba_get_clients` with `network=""` 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=""` to confirm the AP group exists and see its membership → `aruba_get_node_config` with `config_path="/md/"` 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/`) 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/`). 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`). diff --git a/plugins/psd-infrastructure/skills/docbot-docs/SKILL.md b/plugins/psd-infrastructure/skills/docbot-docs/SKILL.md new file mode 100644 index 0000000..557a9f7 --- /dev/null +++ b/plugins/psd-infrastructure/skills/docbot-docs/SKILL.md @@ -0,0 +1,110 @@ +--- +name: docbot-docs +description: Audit and maintain PSD's BookStack documentation and Freshservice knowledge base articles for broken links, outdated content, security exposures, and accessibility issues, and manage the propose/approve/apply change workflow for fixes; use when auditing docs, fixing a stale or broken KB article, or reviewing a proposed documentation change. +triggers: + - "audit our documentation" + - "check bookstack for broken links" + - "audit the freshservice kb" + - "find outdated docs" + - "propose a fix for this page" + - "what changes are pending review" + - "approve this doc change" + - "show audit trends" + - "check page health" + - "docbot" +allowed-tools: Read, Bash +version: 0.1.0 +--- + +# DocBot Docs Skill + +## What this covers + +The `docbot` MCP server audits BookStack documentation and Freshservice Solutions KB articles using 13 parallel checkers (broken links, outdated content, accessibility, security, SEO, spelling, and more), and tracks issues over time in a local SQLite database with fingerprint-based deduplication. Fixes to BookStack pages go through a propose → approve → apply workflow so nothing is written without review; Freshservice audits are read-only through this server today. It can also capture and embed page screenshots, and spot-check version/product claims against live search results. + +## Available tools + +### Audit — BookStack +| Tool | Purpose | +|---|---| +| `docbot_audit_all` | Audit every BookStack page across all books | +| `docbot_audit_book` | Audit all pages in one book | +| `docbot_audit_page` | Audit a single page in detail | + +### Audit — Freshservice +| Tool | Purpose | +|---|---| +| `docbot_fs_audit_all` | Audit every Freshservice solution article | +| `docbot_fs_audit_category` | Audit all articles in a Freshservice category | +| `docbot_fs_audit_folder` | Audit all articles in a Freshservice folder | +| `docbot_fs_audit_article` | Audit one Freshservice article in detail | + +### Browse & search +| Tool | Purpose | +|---|---| +| `docbot_search` | Search BookStack documentation by query | +| `docbot_get_page` | Retrieve a BookStack page (HTML or Markdown) | +| `docbot_get_page_markdown` | Retrieve a page as clean Markdown — use this before proposing a change | +| `docbot_list_books` | List all BookStack books | +| `docbot_fs_search` | Search Freshservice solutions by query | +| `docbot_fs_get_article` | Retrieve a Freshservice article's content | +| `docbot_fs_list_categories` | List Freshservice solution categories and folders | + +### Change workflow (BookStack only) +| Tool | Purpose | +|---|---| +| `docbot_propose_change` | Submit a proposed change (original + proposed Markdown, issue types, explanation, sources) for review | +| `docbot_list_pending_changes` | List proposed changes, filterable by status | +| `docbot_get_change` | View a proposed change's full diff and details | +| `docbot_approve_change` | Manually approve a proposed change | +| `docbot_reject_change` | Reject a proposed change with a reason | +| `docbot_apply_change` | Push an approved change to BookStack | + +### Screenshots +| Tool | Purpose | +|---|---| +| `docbot_capture_screenshot` | Capture a screenshot of a web page via headless Chromium | +| `docbot_embed_screenshot` | Capture a screenshot and embed it in a BookStack page | + +### Validation +| Tool | Purpose | +|---|---| +| `docbot_check_validity` | Check a page's version/product references against live search results | +| `docbot_validate_term` | Check whether a specific term, version, or product is outdated | + +### History & health +| Tool | Purpose | +|---|---| +| `docbot_issue_history` | View issue history across audit runs, filterable by page or type | +| `docbot_dismiss_issue` | Mark an issue as a false positive (by fingerprint) | +| `docbot_undismiss_issue` | Remove a dismissal so the issue reappears in future audits | +| `docbot_list_dismissed` | List all currently dismissed issues | +| `docbot_audit_trends` | Show audit run and issue trends over time | +| `docbot_page_health` | Show open/resolved issue summary for a page or all pages | + +## Common workflows + +1. **Audit a book and fix a broken link.** `docbot_audit_book` to find issues → `docbot_get_page_markdown` to pull the exact current content → `docbot_propose_change` with the original and proposed Markdown, issue types, explanation, and sources → `docbot_approve_change` → `docbot_apply_change` to push the fix to BookStack. + +2. **Full documentation health sweep.** `docbot_audit_all` for a baseline → `docbot_audit_trends` to see whether issue counts are climbing or shrinking → `docbot_page_health` on any page that keeps resurfacing to see its open/resolved history. + +3. **Clean up a Freshservice KB category.** `docbot_fs_list_categories` to see categories and folders → `docbot_fs_audit_category` (or `docbot_fs_audit_folder` for a narrower scope) → `docbot_fs_get_article` to pull the flagged article's content for hand-editing in Freshservice. + +4. **Review pending changes before they ship.** `docbot_list_pending_changes` with `status: "pending"` → `docbot_get_change` on each to read the full diff, sources, and confidence score → `docbot_approve_change` or `docbot_reject_change` with a reason. + +5. **Suppress a known false positive.** `docbot_issue_history` to find the issue's fingerprint → `docbot_dismiss_issue` with a reason → `docbot_undismiss_issue` later if the content changes and the issue should be re-flagged; `docbot_list_dismissed` shows what's currently suppressed. + +## Pitfalls & limits + +- The propose → approve → apply workflow only writes to BookStack. Freshservice audits (`docbot_fs_audit_*`) are read-only through this server — fix flagged KB articles by hand in Freshservice. +- `docbot_apply_change` conflict-checks the live page against the original Markdown the change was proposed against; if someone else edited the page in the meantime it returns a conflict instead of applying. Only pass `force: true` after confirming the newer content is safe to overwrite. +- `docbot_approve_change` is a manual override, not a second opinion — it bypasses automated review. Read the diff with `docbot_get_change` before approving. +- `docbot_dismiss_issue` and `docbot_undismiss_issue` key off an issue's fingerprint, not a numeric ID — get the fingerprint from `docbot_issue_history` or an audit report first. +- `docbot_audit_all` walks every book and can take a while on a large doc set; scope to `docbot_audit_book` or `docbot_audit_page` when you already know where the problem is. +- `docbot_check_validity` / `docbot_validate_term` need web search credentials configured and are disabled by default — they'll say so plainly if not set up. +- `docbot_capture_screenshot` / `docbot_embed_screenshot` only accept HTTP/HTTPS URLs and need headless Chromium available on the machine running the server. +- DocBot's local SQLite database is created automatically on first run under each user's own copy of the server — audit history, dismissals, and trends are local to whoever ran the audit unless the database path is pointed at a shared location. + +## Setup + +See `../../SECRETS-SETUP.md` for credential setup. This server needs `BOOKSTACK_URL`, `BOOKSTACK_TOKEN_ID`, `BOOKSTACK_TOKEN_SECRET` (required); Freshservice tools additionally need `FRESHSERVICE_DOMAIN`, `FRESHSERVICE_API_KEY`; `GEMINI_API_KEY` and web-search credentials enable optional AI-assisted and validity-check features. diff --git a/plugins/psd-infrastructure/skills/fortianalyzer-logs/SKILL.md b/plugins/psd-infrastructure/skills/fortianalyzer-logs/SKILL.md new file mode 100644 index 0000000..16500eb --- /dev/null +++ b/plugins/psd-infrastructure/skills/fortianalyzer-logs/SKILL.md @@ -0,0 +1,99 @@ +--- +name: fortianalyzer-logs +description: Search FortiAnalyzer traffic/event/security logs, check alerts, audit managed devices, and run or retrieve reports for network incident investigation; use when tracing firewall traffic, chasing a security alert, or pulling a compliance report. +triggers: + - "search fortianalyzer logs" + - "check firewall logs" + - "what alerts fired today" + - "pull logs for this incident" + - "check FAZ alerts" + - "download the security report" + - "run the weekly traffic report" + - "check device firmware in fortianalyzer" + - "trace traffic for this IP" + - "fortianalyzer" +allowed-tools: Read, Bash +version: 0.1.0 +--- + +# FortiAnalyzer Logs Skill + +## What this covers + +The `fortianalyzer` MCP server talks to FortiAnalyzer for log search, alerts, reports, managed device/ADOM inventory, and system status. Use it to trace firewall/security events, follow up on alerts, audit device compliance, and generate or retrieve reports. For anything not covered by a dedicated tool, `faz_api_call` makes a raw API call. + +## Available tools + +### Log search & archives +| Tool | Purpose | +|---|---| +| `search_logs` | Search traffic/event/utm/virus/webfilter/ips/dlp/app-ctrl/siem logs with a filter and time window (async — polls until done) | +| `get_log_fields` | List filterable field names for a log type; use before writing a `search_logs` filter | +| `list_log_files` | List archived log files (.zst) by device/vdom/time coverage, for windows that have aged out of live search | +| `download_log_file` | Download a raw archived log file found via `list_log_files` | + +### Alerts +| Tool | Purpose | +|---|---| +| `get_alerts` | List alert events with severity, source, and acknowledgement status | +| `acknowledge_alert` | Mark an alert acknowledged (mutating) | + +### Reports +| Tool | Purpose | +|---|---| +| `list_reports` | List available report templates/layouts and their IDs | +| `run_report` | Trigger a report by layout ID and wait for it to finish | +| `get_report_data` | Fetch results of a report that already ran (no re-run) | +| `list_generated_reports` | List previously generated report artifacts, including ones older than current log retention | +| `download_report` | Download a generated report artifact (pdf/csv/json/xml/html) | + +### Devices & ADOMs +| Tool | Purpose | +|---|---| +| `list_devices` | List managed devices in an ADOM (name, IP, serial, firmware, status) | +| `get_device` | Full detail on one managed device | +| `add_device` | Add a device to FAZ management (mutating) | +| `delete_device` | Remove a device from FAZ management (mutating, does not factory-reset) | +| `list_adoms` | List ADOMs (administrative domains) | +| `get_adom` | Detail on one ADOM | + +### System, tasks & policy +| Tool | Purpose | +|---|---| +| `get_system_status` | FAZ hostname, firmware, serial, uptime | +| `get_ha_status` | HA cluster mode and sync state | +| `get_task_list` | List background tasks (log fetches, report runs, device syncs) | +| `get_task_detail` | Status/progress of one background task by ID | +| `list_policy_packages` | List firewall policy packages in an ADOM | +| `get_policy_package` | Detail on one policy package | +| `get_fortiguard_status` | FortiGuard license and signature database status | + +### Escalation +| Tool | Purpose | +|---|---| +| `faz_api_call` | Raw JSON-RPC call for anything the dedicated tools don't cover. Set `apiver: 3` for `/logview`, `/report`, `/eventmgmt` URLs. | + +## Common workflows + +1. **Chase an alert to root cause.** `get_alerts` to find the triggered alert and its source/time → `search_logs` with a time window around the alert and a filter on the relevant IP/device → `acknowledge_alert` once you've confirmed the cause. + +2. **Build a targeted log query.** `get_log_fields` for the log type to see what's filterable → `search_logs` with `filter` and `time_range` (or `start_time`/`end_time` for an absolute window). Narrow the filter or time window if the search times out. + +3. **Recover logs that have aged out of search.** `list_log_files` with `since`/`until` to find archives covering the window → `download_log_file` to pull the archive for offline analysis. + +4. **Run and retrieve a report.** `list_reports` to find the layout ID → `run_report` to trigger it and wait for completion → `download_report` for the artifact. For a report already run (yours or scheduled), skip straight to `get_report_data` or find it in `list_generated_reports`. + +5. **Audit device or firmware posture.** `list_adoms` → `list_devices` per ADOM → `get_device` for detail on a specific unit → `get_fortiguard_status` to confirm signature databases are current. + +## Pitfalls & limits + +- `search_logs` is async under the hood; it polls up to `timeout_seconds` (default 120s) before giving up. Narrow the time window or add a filter if it times out on a broad query. +- `search_logs` count fields (`total_count`, `scanned_logs`) are only reliable when a `filter` is set — unfiltered searches under-report how much was actually scanned. +- Live search only covers what's still on disk. If a window predates retention, use `list_log_files`/`download_log_file` for raw archives, or `list_generated_reports` for a report that already captured that data. +- `add_device`, `delete_device`, and `acknowledge_alert` are mutating — confirm before running them. +- `faz_api_call` needs `apiver: 3` for `/logview`, `/report`, and `/eventmgmt` URLs; omitting it on those namespaces returns an invalid-request error, not a helpful one. +- `download_log_file` and `download_report` write files to local disk (temp directory by default) — clean up afterward since log/report contents can include sensitive traffic data. + +## Setup + +See `../../SECRETS-SETUP.md` for credential setup. This server needs `FAZ_HOST`, `FAZ_USERNAME`, `FAZ_PASSWORD` (optional: `FAZ_ADOM`, defaults to `root`). diff --git a/plugins/psd-infrastructure/skills/freshservice-tickets/SKILL.md b/plugins/psd-infrastructure/skills/freshservice-tickets/SKILL.md new file mode 100644 index 0000000..77c9db7 --- /dev/null +++ b/plugins/psd-infrastructure/skills/freshservice-tickets/SKILL.md @@ -0,0 +1,89 @@ +--- +name: freshservice-tickets +description: Read, triage, and act on Freshservice tickets via the freshservice MCP server — ticket lookup, replies/notes/closing, requester history, similar-ticket search, and KB articles; use for individual ticket work, not team-wide reporting. +triggers: + - "look up ticket 12345" + - "what's the context on this ticket" + - "find similar tickets" + - "requester history for this person" + - "sync the ticket cache" + - "reply to this ticket" + - "close this ticket" + - "search the knowledge base for" + - "what's on my freshservice queue" + - "who else has hit this issue before" +allowed-tools: Read, Bash +version: 0.1.0 +--- + +# Freshservice Tickets Skill + +## What this covers + +The `freshservice` MCP server talks to Freshservice for individual ticket work: reading and acting on tickets, checking your queue, and searching a local ticket cache for requester history and similar past tickets. psd-productivity ships a separate `freshservice-manager` skill that calls the Freshservice REST API through bash scripts for team-wide reporting (daily/weekly summaries, approvals, agent lookups across workspaces). This skill covers the MCP server's tools instead — they're richer for single-ticket triage (`freshservice_get_ticket_context`, `freshservice_find_similar_tickets`, `freshservice_requester_history`, `freshservice_sync_cache`). When both plugins are installed, prefer these MCP tools for ticket-level work and reach for `freshservice-manager` for cross-workspace reporting. + +## Available tools + +### Your queue & watchlist +| Tool | Purpose | +|---|---| +| `freshservice_my_tickets` | List tickets assigned to you, optionally filtered by status (open/pending/resolved/closed) | +| `freshservice_my_watchlist` | List tickets where you're mentioned in a private note but not the assigned agent | + +### Ticket detail & history +| Tool | Purpose | +|---|---| +| `freshservice_get_ticket` | Fetch a ticket by ID with its full conversation thread and inline images | +| `freshservice_get_ticket_activities` | Get a ticket's activity log — status changes, assignments, and other events | + +### Ticket actions (mutating) +| Tool | Purpose | +|---|---| +| `freshservice_create_ticket` | Create a new ticket (subject, description, requester email, optional priority/status/category) | +| `freshservice_update_ticket` | Update status, priority, assigned agent, group, or category on an existing ticket | +| `freshservice_reply_to_ticket` | Send a public reply to the requester | +| `freshservice_add_note` | Add a note to a ticket (private by default) | +| `freshservice_close_ticket` | Resolve a ticket and add a resolution note in one call | + +### Context & search (backed by the local ticket cache) +| Tool | Purpose | +|---|---| +| `freshservice_get_ticket_context` | One-shot bundle for a ticket ID: current ticket, requester profile, requester's ticket history, and similar past tickets | +| `freshservice_find_similar_tickets` | Search the local cache by keyword for tickets similar to a query | +| `freshservice_requester_history` | Get a requester's recent ticket history by email | +| `freshservice_sync_cache` | Trigger an incremental sync of the local ticket cache from Freshservice | + +### Knowledge base +| Tool | Purpose | +|---|---| +| `freshservice_search_articles` | Search Freshservice knowledge base articles by keyword | +| `freshservice_get_article` | Retrieve a specific KB article by ID | + +## Common workflows + +1. **Triage an incoming ticket.** `freshservice_get_ticket_context` with the ticket ID to see the ticket, requester profile, their ticket history, and similar resolved tickets in one call → if a similar ticket shows a known fix, `freshservice_reply_to_ticket` or `freshservice_add_note` → `freshservice_close_ticket` to resolve with a note. + +2. **Check your queue at the start of a shift.** `freshservice_my_tickets` (filtered to `open`) for your active tickets → `freshservice_my_watchlist` for tickets you're mentioned in but not assigned → `freshservice_get_ticket` on any that need a full read. + +3. **Chase down a recurring issue.** `freshservice_find_similar_tickets` with keywords from the current ticket → `freshservice_get_ticket` on the closest matches to read the full resolution → `freshservice_reply_to_ticket` or `freshservice_add_note` referencing the known fix. + +4. **Research a requester before responding.** `freshservice_requester_history` with their email to see recent tickets → `freshservice_get_ticket_activities` on a specific past ticket if you need the exact timeline of what changed and when. + +5. **Check the knowledge base before opening a new ticket.** `freshservice_search_articles` with the topic → `freshservice_get_article` on the best match → if nothing resolves it, `freshservice_create_ticket`. + +Run `freshservice_sync_cache` when cache-backed results look stale — e.g., `freshservice_find_similar_tickets` is missing a ticket you know exists, or a just-created ticket doesn't show up in `freshservice_my_watchlist`. + +## Pitfalls & limits + +- `freshservice_sync_cache` only refreshes ticket metadata (status, priority, subject, resolution notes). It does not backfill conversation text for new tickets — that happens once in the background when the MCP server starts. If a cache-backed result is missing a "last agent reply" line, that's why; re-running `freshservice_sync_cache` won't fix it. +- `freshservice_find_similar_tickets` and the similar-tickets section of `freshservice_get_ticket_context` only search the local cache, never live Freshservice — a ticket created moments ago won't appear until a sync (and, for its conversation text, the background backfill) has caught up. +- `freshservice_get_ticket_context` has an internal ~30-second budget across its sub-lookups. If it runs long, it returns what it has with `[PARTIAL RESULT]` appended rather than hanging. +- `freshservice_create_ticket`, `freshservice_update_ticket`, `freshservice_reply_to_ticket`, and `freshservice_add_note` write to Freshservice immediately — there's no built-in confirmation step, so confirm details before calling them. +- `freshservice_close_ticket` sets status to Resolved and adds the resolution note as two separate calls. If the note fails after the status update succeeds, the tool still reports both outcomes — read the returned text rather than assuming both succeeded. +- `freshservice_reply_to_ticket` and `freshservice_add_note` convert plain text to HTML (newlines become `
`). Don't pass pre-formatted HTML — it will be escaped, not rendered. +- `freshservice_my_tickets` and `freshservice_my_watchlist` are scoped to the agent identity the server authenticated as (`FRESHSERVICE_AGENT_EMAIL`), not necessarily the person driving the session — check who that account is if results look off. +- Right after the MCP server starts, cache-backed tools (`freshservice_my_watchlist`, `freshservice_find_similar_tickets`, the history/similar sections of `freshservice_get_ticket_context`) may return sparse or empty results until the initial background sync finishes. + +## Setup + +See `../../SECRETS-SETUP.md` for credential setup. This server needs `FRESHSERVICE_API_KEY`, `FRESHSERVICE_DOMAIN`, and `FRESHSERVICE_AGENT_EMAIL`.