Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Sales Demos Mcp

CSecurity

Connect Claude Code to this repo's OpenShift clusters, AAP instances, RHDH portal, Automation Orchestrator, and Grafana Cloud over MCP — up to ten servers, one skill. Generates per-environment kubeconfigs for OpenShift, auto-creates bearer tokens for AAP, extracts portal MCP tokens, registers the AO and Grafana Cloud MCP servers, then verifies every server answers. TRIGGER when: the user asks to set up, connect, refresh or fix the MCP servers, says an openshift-sandbox, openshift-demo, opensh...

2 stars
0 votes
0 copies
0 views
Added 10/6/2026
devopspythonrustgoshellbashnodekubernetesgitapibackend

Works with

claude codecliapimcp

Security Analysis

C67/100
criticalExfiltrates credentials via HTTP — exact pattern from Snyk ToxicSkills study
criticalSends environment variables or credentials to an external URL
mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned 10/6/2026

$npx -y skills add ericcames/sales.demos --skill sales-demos-mcp --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Sales Demos Mcp?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Sales Demos Mcp
[![Security: C — Skills Directory](https://www.skillsdirectory.com/api/skills/ericcames-sales-demos-mcp/badge)](https://www.skillsdirectory.com/skills/ericcames-sales-demos-mcp)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
name: sales-demos-mcp
description: "Connect Claude Code to this repo's OpenShift clusters, AAP instances, RHDH portal, Automation Orchestrator, and Grafana Cloud over MCP — up to ten servers, one skill. Generates per-environment kubeconfigs for OpenShift, auto-creates bearer tokens for AAP, extracts portal MCP tokens, registers the AO and Grafana Cloud MCP servers, then verifies every server answers. TRIGGER when: the user asks to set up, connect, refresh or fix the MCP servers, says an openshift-sandbox, openshift-demo, openshift-edge, aap-sandbox, aap-demo, portal-sandbox, portal-demo, ao-sandbox, ao-demo, or grafana MCP server is failing or shows no tools, or has just repointed an environment or rotated a token. SKIP: if the user wants to install OpenShift Virtualization or apply AAP configuration — that is sales-demos-setup — or wants to deploy the AAP MCP server into a cluster, which is playbooks/mcp_server.yml run by sales-demos-setup."
---

# sales-demos-mcp

Makes the clusters, AAP instances, RHDH portal, and Grafana Cloud directly
queryable from Claude Code. Before this, every question about an environment
cost a `curl`, a vault read and a JSON parse; after it, `namespaces_list`,
`job_templates_list`, or a portal catalog query is a tool call.

**No playbook, by design.** This touches the laptop — it writes kubeconfigs,
creates tokens, and configures your MCP client. It must never run from AAP,
which is the same reasoning that keeps `sales-demos-collections-sync`,
`sales-demos-first-time` and `sales-demos-ee-build` playbook-free.

## What it sets up

**Up to ten servers — nine per-environment, one global/external:**

| Server | Auth | Access | Source |
|---|---|---|---|
| `openshift-sandbox` | kubeconfig | read-write | `.mcp.json` (committed) |
| `openshift-demo` | kubeconfig | read-only | `.mcp.json` (committed) |
| `openshift-edge` | kubeconfig | read-write | `.mcp.json` (committed) |
| `aap-sandbox` | bearer token | read-write (server-side, `group_vars/sandbox/mcp.yml`) | `.mcp.json` (committed) |
| `aap-demo` | bearer token | **read-only** (server-side, `group_vars/demo/mcp.yml`) | `.mcp.json` (committed) |
| `portal-sandbox` | static token | read-only | `.mcp.json` (committed) |
| `portal-demo` | static token | read-only | `.mcp.json` (committed) |
| `ao-sandbox` | JWT (auto-refreshed) | read-only | `claude mcp add --scope local` |
| `ao-demo` | JWT (auto-refreshed) | read-only | `claude mcp add --scope local` |
| `grafana` | service account token | read-only | `claude mcp add --scope local` |

**There is no `aap-edge`, and that is not an oversight to fix in passing.**
`edge` runs AAP, so the server is plausible, but adding it is a posture
decision, not a usage-line fix — the same call #405 made about that script.

**There is no `ao-edge` either, for a different reason: AO is not installed on
`edge`.** Measured 2026-09-11 — `openshift-edge` has no Route in the
`automation-orchestrator` namespace. Posture is not the blocker here: the AO
server is read-only everywhere, and `make-ao-mcp.sh` accepts any environment
name. `.claude/settings.json` already allowlists `mcp__ao-edge__*` (added with
the server in #465), so once `/sales-demos-orchestrator` has run against `edge`,
`bash utilities/make-ao-mcp.sh edge` is the whole job. Until then the script
stops at the Route lookup, which is the correct failure.

**One server per environment, named after it, is the whole design.** #16 is the
precedent: when two environments were not kept distinct, `--limit demo`
silently resolved to sandbox's hostname and sandbox's token with no warning at
all. A single server whose target changed underneath you would reintroduce
exactly that, so the environment is in the server's *name* and you pick it by
picking the tool.

`demo` is read-only on both servers, because it is the environment customers
watch, and **the two guards live in different places and must move together**:

- **OpenShift** — `--read-only` on `openshift-demo` in `.mcp.json`, enforced by
  the client. You can see it in the tracked file.
- **AAP** — `aap_mcp_allow_write_operations` in `inventory/group_vars/<env>/mcp.yml`
  (`true` for sandbox, `false` for demo), enforced by the server. It is *not*
  visible in `.mcp.json`, which is why this table names the file. Nothing
  overrides it during setup: `setup.yml` deploys demo read-only from the start.

Changing the AAP value means editing `mcp.yml` and re-running `mcp_server.yml`
(or the **AAP Ecosystem - Install MCP Server** template), which deletes and
recreates the server so the new permission actually takes effect. The token and
URL do not change, so no Claude restart is needed. See #102.

### Grafana Cloud server

`grafana` is a **single server**, not per-environment — Grafana Cloud is an
external SaaS instance that survives RHDP rebuilds. That is the whole point of
choosing it over self-hosted (#260). The service account token has the Viewer
role (read-only, matching the governance thesis). See
[`grafana-plan`](https://ericcames.github.io/sales.demos-docs/plan/grafana-plan/).

### OpenShift servers

`.mcp.json` is committed and defines the three OpenShift servers. `demo` has
`--read-only`, which removes the nine mutating tools and keeps every
investigative one — including `vm_guest_info` and `vm_troubleshoot`. `sandbox`
and `edge` carry all 25.

The `kubevirt` toolset is enabled because this repo is an OpenShift
Virtualization demo. It supplies `vm_create`, `vm_clone`, `vm_lifecycle`,
`vm_guest_info` and `vm_troubleshoot`.

### AAP servers

The AAP MCP servers **run in the cluster** (deployed by `playbooks/mcp_server.yml`,
which `setup.yml` calls). The client side needs a bearer token, and tokens must
not go in tracked files.

`.mcp.json` defines `aap-sandbox` and `aap-demo` as stdio servers (#515). Each
entry calls `utilities/aap-mcp-stdio.sh <env>`, which reads the token and route
URL from gitignored files in `.aap/` and bridges stdio to the remote server via
`npx supergateway`. The credential stays out of the tracked file — same pattern
as the kubeconfig paths for OpenShift servers.

**A local-scope entry of the same name outranks `.mcp.json`** (precedence is
local > project > user). Before #515 these servers were registered with
`claude mcp add --scope local` as HTTP entries with the URL inline, and one left
behind keeps the server on its old cluster through every regeneration and
restart. `make-aap-mcp.sh` removes it and then asserts, through
`claude mcp get`, that the project entry is the one that wins; it fails rather
than trust the removal (#603).

`utilities/make-aap-mcp.sh` automates the full flow: resolve credentials from
the vault, create a personal access token via the gateway API, find the MCP
route, and write `.aap/<env>.token` and `.aap/<env>.url` — the full `/mcp`
endpoint, since the route root answers `404` (#603). Token scope is always
`write` — server-side enforcement (`aap_mcp_allow_write_operations`) is the
real guard, not the token scope.

**These tokens do not clean themselves up.** They are the documented exception
in CLAUDE.md — an MCP client needs a durable credential, so the `always:` block
rule does not apply. The script prints cleanup instructions; retiring them is
manual.

## Preflight Check

Every one must pass.

```bash
ENV=${ENV:-sandbox}

test -s "$HOME/secrets/.vault_pass_sales_demos" \
  && echo "✅ vault password file" \
  || echo "❌ ~/secrets/.vault_pass_sales_demos missing — see /sales-demos-first-time step 2"

head -c 15 playbooks/group_vars/all/secrets.yml 2>/dev/null | grep -q '^\$ANSIBLE_VAULT' \
  && echo "✅ secrets.yml is vault-encrypted" \
  || echo "❌ secrets.yml is NOT encrypted — stop, do not commit"

command -v npx >/dev/null \
  && echo "✅ npx ($(node --version)) — needed to launch kubernetes-mcp-server" \
  || echo "❌ npx not found — install Node.js, or fetch the pinned binary from https://github.com/containers/kubernetes-mcp-server/releases"

command -v uvx >/dev/null \
  && echo "✅ uvx ($(uvx --version 2>/dev/null || echo 'unknown')) — needed to launch mcp-grafana" \
  || echo "❌ uvx not found — install uv (https://docs.astral.sh/uv/getting-started/installation/)"

test -f .mcp.json \
  && echo "✅ .mcp.json present" \
  || echo "❌ .mcp.json missing — it is committed; you may be outside the repo root"

# All MCP credentials (kubeconfig, AAP URL, AO registration) still point AT
# this environment. Generating them once is not enough: repointing an
# environment (via connection.yml, local.yml, or vault edits) does NOT
# regenerate these files (#161, #533). The failure is otherwise a DNS error
# naming a dead cluster, which says nothing about credentials.
bash utilities/check-mcp-staleness.sh "$ENV" 2>&1 || true

test -d "inventory/group_vars/$ENV" \
  && echo "✅ environment '$ENV' exists" \
  || echo "❌ no such environment '$ENV'"
```

If any fails, stop and tell the user which one and the fix beside it.

## Collect inputs

| Variable | Default | Meaning |
|---|---|---|
| `ENV` | `sandbox` | Which environment to (re)generate servers for |

Never prompt for a token and never pass one on the command line — that puts it
in shell history. Everything is read from the vault.

## Run

### Step 1 — OpenShift kubeconfig

```bash
bash utilities/make-kubeconfig.sh sandbox
```

Generate `demo` or `edge` too only if the user is actually working against
them. `demo` is the environment customers watch, and a stale kubeconfig for it
is harmless whereas a confidently wrong one is not. `edge` is the bare-metal
SNO — it is persistent rather than ephemeral, so its kubeconfig goes stale far
less often than an RHDP one.

The file lands at `.kube/<env>.kubeconfig`, mode `0600`. `.gitignore` covers
`.kube/`, and the script writes the token only after locking the file down, so
the credential is never briefly world-readable.

### Step 2 — AAP MCP server

Run **after** the kubeconfig exists — the script needs it to find the MCP route.

```bash
bash utilities/make-aap-mcp.sh sandbox
```

For `demo`:

```bash
bash utilities/make-aap-mcp.sh demo
```

The script creates a personal access token, finds the `aap-mcp` route via the
kubeconfig, and writes `.aap/<env>.token` and `.aap/<env>.url`. `.mcp.json`
references these through the `aap-mcp-stdio.sh` wrapper, so the server comes
online on the next Claude Code restart — no `claude mcp add` needed.

### Portal (RHDH) servers

The RHDH self-service portal exposes an MCP endpoint natively (#555) via the
`backstage-plugin-mcp-actions-backend` plugin, with `software-catalog-mcp-tool`
and `techdocs-mcp-tool` as action sources. `portal.yml` enables the plugins,
generates a static bearer token, and stores it in a `portal-mcp-token` Secret.

`.mcp.json` defines `portal-sandbox` and `portal-demo` as stdio servers. Each
entry calls `utilities/portal-mcp-stdio.sh <env>`, which reads the token and
route URL from gitignored files in `.portal/` and bridges stdio to the remote
endpoint via `npx supergateway`. Same pattern as the AAP servers.

`utilities/make-portal-mcp.sh` reads the portal Route and the `portal-mcp-token`
Secret from the cluster via the kubeconfig, and writes `.portal/<env>.token` and
`.portal/<env>.url`. Simpler than the AAP flow — the token already exists in the
Secret, so no API call creates one.

The token dies with the portal deployment (which dies with the RHDP env). On a
fresh bootstrap, `portal.yml` generates a new token and creates a new Secret.
`make-portal-mcp.sh` picks up the new one. No vault entry, no rotation — same
ephemeral-environment reasoning as the AAP MCP PAT.

### Step 3 — Portal MCP server

Run **after** the kubeconfig exists — the script needs it to find the portal
Route and Secret.

```bash
bash utilities/make-portal-mcp.sh sandbox
```

For `demo`:

```bash
bash utilities/make-portal-mcp.sh demo
```

The script reads the portal Route and `portal-mcp-token` Secret, then writes
`.portal/<env>.token` and `.portal/<env>.url`. `.mcp.json` references these
through the `portal-mcp-stdio.sh` wrapper, so the server comes online on the
next Claude Code restart.

### Step 4 — Automation Orchestrator MCP server

Run **after** the kubeconfig exists — the script needs it to find the AO Route.

```bash
bash utilities/make-ao-mcp.sh sandbox
```

For `demo`:

```bash
bash utilities/make-ao-mcp.sh demo
```

The script resolves the AO admin password from the vault (it is the AAP admin
password, #143), finds the AO Route via the kubeconfig, verifies login works,
and registers the server with `claude mcp add --scope local`. The server
manages JWT refresh transparently — no token to retire.

**Prerequisite:** `pip install mcp` — the server is a Python MCP server using
the official SDK.

### Step 5 — Grafana Cloud MCP server

Independent of the kubeconfig and AAP steps — Grafana Cloud is an external
service, not tied to any RHDP environment.

```bash
bash utilities/make-grafana-mcp.sh
```

The script reads `grafana_cloud_url` and `grafana_cloud_sa_token` from the
vault and registers the server with `claude mcp add --scope local`.

**Prerequisite:** a Grafana Cloud account with a service account token
(Viewer role). See [`grafana-plan`](https://ericcames.github.io/sales.demos-docs/plan/grafana-plan/)
for the manual browser setup steps.

**Restart Claude Code after running for the first time.** MCP servers are
launched at startup; a server that was not registered then stays absent until
the client is relaunched.

## Verify — ask the server, not the config

### OpenShift

**A generated kubeconfig is not proof.** It proves a file was written, not that
the cluster accepts it. Ask the target:

```bash
KUBECONFIG=$PWD/.kube/sandbox.kubeconfig oc whoami
KUBECONFIG=$PWD/.kube/sandbox.kubeconfig oc get nodes -o name
```

Then prove the MCP server itself starts and answers, independent of the client:

```bash
python3 - <<'PY'
import json, subprocess, os, sys
kc = os.path.abspath(".kube/sandbox.kubeconfig")
p = subprocess.Popen(
    ["npx","-y","kubernetes-mcp-server@0.0.66","--kubeconfig",kc,
     "--toolsets","core,config,kubevirt","--disable-multi-cluster"],
    stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)
send = lambda o: (p.stdin.write(json.dumps(o)+"\n"), p.stdin.flush())
send({"jsonrpc":"2.0","id":1,"method":"initialize","params":{
    "protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"verify","version":"0"}}})
info = json.loads(p.stdout.readline())["result"]["serverInfo"]
send({"jsonrpc":"2.0","method":"notifications/initialized"})
send({"jsonrpc":"2.0","id":2,"method":"tools/list"})
tools = json.loads(p.stdout.readline())["result"]["tools"]
send({"jsonrpc":"2.0","id":3,"method":"tools/call",
      "params":{"name":"namespaces_list","arguments":{}}})
got = json.loads(p.stdout.readline())
p.terminate()
ok = info.get("name") == "kubernetes-mcp-server" and len(tools) >= 20 and "result" in got
print(f"server        : {info.get('name')} {info.get('version')}")
print(f"tools exposed : {len(tools)}")
print(f"live call     : {'namespaces_list returned data' if 'result' in got else got}")
print("\nMCP VERIFIED" if ok else "\nVERIFICATION FAILED — do not report success")
sys.exit(0 if ok else 1)
PY
```

Expect 25 tools on `sandbox`. If it returns 16, the `--read-only` flag is being
applied to the wrong server — check which entry in `.mcp.json` was launched.

### AAP

Verify the AAP MCP server by hitting its endpoint directly, with the same token
and URL files the stdio bridge reads:

```bash
ENV=${ENV:-sandbox}
AAP_MCP_URL=$(cat .aap/$ENV.url)      # already ends in /mcp
AAP_MCP_TOKEN=$(cat .aap/$ENV.token)

curl -sk -o /dev/null -w '%{http_code}\n' -X POST "$AAP_MCP_URL" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H "Authorization: Bearer $AAP_MCP_TOKEN" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"verify","version":"0"}}}'
```

`200` and a body naming `"serverInfo":{"name":"aap"}` is the pass. **The endpoint
requires the bearer token** (#515), so a `401` from a request without the
`Authorization` header proves nothing about the server (#594). **A `503` means
the Route is admitted but the pod is not serving yet** — wait and retry rather
than assuming a misconfiguration. Measured on a working sandbox: **140 tools**,
including `job_templates_launch_create`, `workflow_job_templates_launch_create`
and `jobs_stdout_retrieve`.

### Portal (RHDH)

Verify the portal MCP endpoint is reachable:

```bash
ENV=${ENV:-sandbox}
PORTAL_URL=$(cat .portal/$ENV.url)
PORTAL_TOKEN=$(cat .portal/$ENV.token)

curl -sk -o /dev/null -w '%{http_code}\n' -X POST "$PORTAL_URL" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H "Authorization: Bearer $PORTAL_TOKEN" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"verify","version":"0"}}}'
```

`200` is the pass, with `"serverInfo":{"name":"backstage"}` in the body. The
portal is read-only — it exposes the software catalog and TechDocs, not mutating
operations.

**The `Accept` header is not optional**, and it is the line this block used to
be missing (#789). The endpoint is Streamable HTTP and rejects a request that
does not name *both* content types, so without it a perfectly healthy server
answers `406 Not Acceptable` and the documented check cannot pass. A `401
Illegal token` is the failure this check is actually for — the token in
`.portal/<env>.token` no longer matches the `portal-mcp-token` Secret, which is
what happens when the portal is redeployed. Both are "not 200" and only one is
a real problem, so read the body, not just the number.

### Automation Orchestrator

`claude mcp add` registers the server, but its tools only load when Claude Code
starts — **restart first**, then ask the server rather than re-reading the
config. Two calls, in this order:

1. **`mcp__ao-<env>__version`** — proves the server started, logged in and
   reached the API. The pass is `"api_version": "v1"`. Measured on sandbox
   2026-09-11: `info_version` `1.1.0`.
2. **`mcp__ao-<env>__proxies_aap_job_templates`** — proves AO's AAP integration
   works, which is what makes AO useful. The pass is `count` greater than 0;
   measured on sandbox: **33**. A `count` of 0 or an error with `version`
   passing means the MCP server is fine and AO is not connected to AAP — that
   is `/sales-demos-orchestrator-config`, not this skill.

Measured on a working sandbox: **32 tools**, all read-only. A tool whose name is
missing after a restart is the server failing to start. The usual cause is the
SDK: `python3 -c 'import mcp'` fails, and `pip install mcp` fixes it. Otherwise
re-run `make-ao-mcp.sh <env>`, which tests the AO login before it registers
anything.

### Grafana Cloud

Verify the Grafana MCP server starts and can reach the Grafana Cloud instance:

```bash
python3 - <<'PY'
import json, subprocess, sys, os

vault_pass = os.path.expanduser("~/secrets/.vault_pass_sales_demos")
vault_id = f"sales.demos@{vault_pass}"

import yaml
raw = subprocess.check_output(
    ["ansible-vault", "view", "playbooks/group_vars/all/secrets.yml",
     "--vault-id", vault_id], text=True)
secrets = yaml.safe_load(raw)
url = secrets["grafana_cloud_url"]
token = secrets["grafana_cloud_sa_token"]

p = subprocess.Popen(
    ["uvx", "mcp-grafana"],
    stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
    text=True, env={**os.environ, "GRAFANA_URL": url,
                    "GRAFANA_SERVICE_ACCOUNT_TOKEN": token})
send = lambda o: (p.stdin.write(json.dumps(o)+"\n"), p.stdin.flush())
send({"jsonrpc":"2.0","id":1,"method":"initialize","params":{
    "protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"verify","version":"0"}}})
info = json.loads(p.stdout.readline())["result"]["serverInfo"]
send({"jsonrpc":"2.0","method":"notifications/initialized"})
send({"jsonrpc":"2.0","id":2,"method":"tools/list"})
tools = json.loads(p.stdout.readline())["result"]["tools"]
send({"jsonrpc":"2.0","id":3,"method":"tools/call",
      "params":{"name":"list_datasources","arguments":{}}})
got = json.loads(p.stdout.readline())
p.terminate()
ok = len(tools) >= 10 and "result" in got
print(f"server        : {info.get('name')} {info.get('version')}")
print(f"tools exposed : {len(tools)}")
print(f"live call     : {'list_datasources returned data' if 'result' in got else got}")
print("\nGRAFANA MCP VERIFIED" if ok else "\nVERIFICATION FAILED — do not report success")
sys.exit(0 if ok else 1)
PY
```

Finally, confirm the client sees all servers:

```bash
claude mcp list
```

## If it fails

| Symptom | Cause | Fix |
|---|---|---|
| `✔ Connected` but calls fail or return no data | stdio servers (OpenShift, Grafana, AO) start locally; the check mark proves the process launched, not that the cluster is reachable | Verify with `oc whoami` or a live tool call (`namespaces_list`, `nodes_top`); if the cluster is dead, the kubeconfig is stale |
| MCP server shows as failed at startup | Kubeconfig does not exist yet | Run the generator, then restart Claude Code |
| `401 Unauthorized` on a tool call | Token in the vault is stale or the environment expired | Update `env_secrets.<env>.openshift_api_token`, re-run the generator |
| `could not resolve <env> token` | Vault password wrong, or `env_secrets.<env>` missing | `/sales-demos-first-time` step 2 |
| `dial tcp: no such host` | The RHDP environment has expired | Check `connection.yml` points at a live cluster — both had expired once before (#101) |
| Tools present but every call fails | Kubeconfig points at a different cluster than you think | `oc whoami --show-server` with `KUBECONFIG` set |
| AAP MCP returns `503` | Route admitted, pod not serving yet | Wait — `oc get deploy aap-mcp -n aap`; this is normal for ~60s after deploy |
| `aap-<env>` still dials the previous cluster after a restart, though `.aap/<env>.url` is current | A pre-#515 local-scope registration outranks `.mcp.json` (#603) | `claude mcp remove aap-<env> -s local` from the main checkout, restart. `check-mcp-staleness.sh <env>` names every such shadow |
| AAP MCP returns `401` to a request with **no** `Authorization` header | The command is wrong, not the server: the endpoint requires the bearer token (#594) | Use the Verify → AAP block as written. Do **not** mint a new token for this |
| AAP MCP returns `401` **with** `Authorization: Bearer $(cat .aap/<env>.token)` | Token expired or deleted | Re-create it: `bash utilities/make-aap-mcp.sh <env>`, then restart Claude Code. Retire the old token by hand; these do not clean themselves up |
| AAP MCP write tools missing | `aap_mcp_allow_write_operations` is false for this environment | Intentional on `demo`. Changing it needs a delete-and-recreate — re-run `mcp_server.yml`, which handles that |
| `npx: command not found` | Node not installed | See preflight; a standalone binary is the alternative |
| `no aap-mcp route` from make-aap-mcp.sh | MCP server not deployed | Run `/sales-demos-setup` or `playbooks/mcp_server.yml` first |
| Portal MCP returns `401 Illegal token` | Stored token no longer matches the `portal-mcp-token` Secret — usual cause is the portal being redeployed | `bash utilities/make-portal-mcp.sh <env>` re-reads the Secret; only re-run `portal.yml` if the Secret itself is missing |
| Portal MCP returns `406 Not Acceptable` | The request omitted `Accept: application/json, text/event-stream` — the server is fine (#789) | Fix the command, not the server. Use the Verify → Portal block as written |
| `no portal route` from make-portal-mcp.sh | Portal not deployed | Run `/sales-demos-setup` or `playbooks/portal.yml` first |
| Portal MCP `no such host` | RHDP environment expired | Same as OpenShift — repoint and re-bootstrap |
| Grafana `grafana_cloud_url not set` | Vault keys missing or still CHANGEME | `ansible-vault edit` and add real values — see the [grafana plan](https://ericcames.github.io/sales.demos-docs/plan/grafana-plan/) |
| Grafana MCP tools present but calls fail | Token expired or revoked | Create a new SA token in the Grafana Cloud UI, update the vault |
| `uvx: command not found` | uv not installed | See preflight; install from https://docs.astral.sh/uv/ |

Never paste a live token into a commit message, issue, or PR. This repo is
public — see `CLAUDE.md`.

Attribution

ericcamesericcames
View sourceSee grades on GitHubMore from ericcames →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Terraform Module Library

Build reusable Terraform modules for AWS, Azure, and GCP infrastructure following infrastructure-as-code best practices. Use when creating infrastructure modules, standardizing cloud provisioning, or implementing reusable IaC components.

401991 votes

sematext-otel

Wire a service's OpenTelemetry output to Sematext Cloud. Walks through region, App-type, instrumentation flow (managed OTLP endpoint vs Sematext Agent), and signal selection (traces/metrics/logs), then produces the exact env-var block and points at a runnable reference example in this repo. Invoke when instrumenting a new app for Sematext.

01 votes

Deployment Patterns

Deployment workflows, CI/CD pipeline patterns, Docker containerization, health checks, rollback strategies, and production readiness checklists for web applications. Use when setting up deployment infrastructure or planning releases.

2699140 votes

Babysit

Watch a pull request or review cycle until it is ready to merge. Use when asked to babysit, monitor, or keep checking PR comments, reviews, and CI until all actionable issues are resolved.

971540 votes

V7 Roster

Interact with the Paperclip control plane API for task coordination and governance. Use when checking assignments, updating issue status, posting comments, delegating work, managing routines, or calling Paperclip API endpoints.

953190 votes
View all in devops →