mcp-app-admin — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited mcp-app-admin (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
This skill covers operating mcp-app solutions — connecting the admin CLI, verifying a deployment end-to-end, managing users and credentials, and registering MCP clients. It applies regardless of how the solution was deployed — Cloud Run, Docker, bare metal, k8s, or any other environment.
This skill is the third stage of the mcp-app solution lifecycle:
author-mcp-app.
URL that passes a minimal health check. Owned by neither mcp-app skill — the operator's environment supplies the route (a deployment skill, plugin, script, or context).
already passed GET /health → {"status": "ok"}.
This skill does NOT deploy. It does NOT discover deployments. It does NOT decide where the URL came from. If the operator does not yet have a healthy URL to point at, that is a stage-2 problem — either the operator's agent environment provides a deployment route, or the operator handles deploy out-of-band — and this skill waits.
Verification of a freshly deployed instance is non-optional. Whenever this skill is invoked immediately after a deploy (or whenever the operator suspects the running instance has changed), the verification ladder in Step 3 is mandatory before declaring the deploy successful. GET /health returning ok is necessary but not sufficient — probe and (when the solution declares one) safe-tool --invoke are the actual contracts the operator relies on. Skipping past them with raw curl or cloud CLI inspection is the wrong move; this skill is the route, not a fallback.
Every mcp-app solution supports six recurring journeys across three audiences (developer, operator, end user). The author-mcp-app skill enumerates the full map. This skill owns journeys 4–6:
signing key, point the admin CLI at the deployed instance (or the local store), persist the per-app config.
revoke, issue tokens, read profiles (users get-profile), rotate profile fields (users update-profile).
probe for the framework layer, tools list/show/call for discovery and ad-hoc invocation, safe-tool --invoke for the opinionated end-to-end smoke test (when declared), and register to emit Claude Code, Gemini CLI, and Claude.ai URL-form commands.
Journeys 1–3 (install, run locally, deploy) are outside this skill's scope. Install and local-run live with the app's own README and (for agents) author-mcp-app. Deploy lives with the operator's own deployment tooling — a deployment skill or plugin, in-repo scripts, or operator/user-level context describing how this operator deploys mcp-app solutions. This skill assumes a healthy URL produced by some stage-2 path; it does not produce one.
connect, verify, and register the first user.
a credential, add a user, or investigate an issue. May not remember how the app was deployed. Should be able to reconstruct the operational state from the app's own docs and the per-app admin CLI config.
on behalf of a human. Relies on structured output (--json), deterministic commands, and self-documenting CLI help (profile field descriptions).
The returning operator is the most frequently neglected audience and the one this skill most explicitly serves. If a workflow requires the operator to remember something not captured in the app's docs or the admin CLI's persistent config, flag it as a gap.
At the start of any session involving admin operations, check the current connection state before doing anything else:
my-solution-admin health # remote — confirms URL and auth
my-solution-admin users list # local or remote — confirms store accessDo not assume the admin CLI is pointed at the right target. The connection may be stale from a previous session, pointed at a different environment, or not configured at all. Verify first.
Prefer the per-app admin CLI (my-solution-admin) over the generic CLI (mcp-app). The per-app CLI stores connection config per app in ~/.config/{name}/setup.json — each app remembers its own target independently. The generic CLI stores one connection at a time and overwrites on each connect.
The framework tracks one connection per app (a single deployment environment, whether local or remote). If the same app is deployed to multiple environments, connect switches between them but only remembers the last one configured.
Most "my MCP connection failed" reports do not require guessing at signing keys, poking at curl commands, or reading framework source. Run one command first and let its output narrow the problem:
my-solution-admin probeProbe walks the full stack: /health for liveness, admin signing verification, and an MCP tools/list round-trip using a short-lived token minted for an existing user. The point at which probe fails tells you which layer is broken.
| Probe outcome | Likely cause | Next step |
|---|---|---|
Health: failed / connection error | Service not running, wrong URL, networking | Check the deploy (logs, process, ingress). Not an auth problem. |
Health: healthy but admin calls fail (e.g. users list) | Admin signing chain broken — the CLI's SIGNING_KEY doesn't match the deployed server's | Re-retrieve the signing key (Step 1) and reconnect. |
Health: healthy, admin works, but MCP round-trip returns 401/403 | User auth: either the user is revoked, or the solution's backend credential on the user's profile is invalid or expired | Check my-solution-admin users update-profile --help for the solution-specific profile field names, then rotate with my-solution-admin users update-profile [email protected] <field> <new-value>. If the user was revoked, issue a new token via tokens create. |
Probe succeeds (MCP: ok, tools listed) | Deployed service is fully functional | Failure is client-side: stale connector config, wrong URL, wrong header format, or a claude.ai / Claude Code probe idiosyncrasy. Re-register the client from scratch (Step 5) and compare against a fresh register --user EMAIL output. |
The server returns the same {"error": "Invalid or revoked token"} body for at least three distinct causes: (a) JWT signature doesn't verify, (b) the user has been revoked, (c) the solution-specific backend credential on the user's profile is invalid or expired. Curl output alone cannot distinguish these. Probe exercises each layer with known-good inputs (the admin CLI's own signing key, a freshly minted token) and isolates the failing layer without ambiguity.
mcp-app is responsible for signing-chain integrity and user access control (revocation, token issuance). The framework is not responsible for whether the solution's backend credential — the token or API key the solution uses to call its upstream on behalf of the user — is still valid. That credential lives on the user's profile, its schema is declared by the solution, and its validity depends on whatever upstream service the solution targets. When probe reports admin + MCP working but the client still misbehaves at a tool-call level, assume a solution-specific issue and consult the solution's docs or admin CLI help.
The signing key is required for admin operations on remote instances. Its location depends on how the solution was deployed:
own secret-resolution mechanism (typically a cloud secret manager); retrieve via mcp-app signing-key show or the provider's surface.
directly in Secret Manager; retrieve via gcloud secrets versions access latest --secret=....
tool puts it; consult that tool's documentation or CLI.
wherever the operator put it when setting up the deploy; investigate the deploy configuration.
For anything other than mcp-app deploy, the app admin CLI is connected manually:
my-solution-admin connect <url> --signing-key "$(retrieve-it-somehow)"Start with the deployment configuration. Look at how the solution was deployed and how SIGNING_KEY was configured:
Manager using the secret name from gapp config. Run from inside the solution's repo directory (gapp resolves the solution from the working directory):
cd <solution-repo> && gapp secrets get <secret-name> --plaintextstored there by the deployment tool or manually:
# GCP
gcloud secrets versions access latest --secret=SECRET_ID --project=PROJECT_ID
# AWS
aws secretsmanager get-secret-value --secret-id SECRET_ID terraform output -raw signing_keydocker-compose.yml for thesecret source (file path, env var, Docker secret).
write-only from the UI. If this is the only copy, generate a new key, update the CI secret, and redeploy.
environment or the shell/systemd/supervisor config.
Generate a new one, store it wherever the deployment expects it, redeploy, and re-register all users (existing tokens become invalid):
python3 -c 'import secrets; print(secrets.token_urlsafe(32))'An agent operating in a sandboxed environment may find that the plaintext signing-key fetch — whether via an MCP tool that returns the secret value, a gcloud secrets versions access call, or any other direct retrieval — is denied by the environment's permission policy. This is intentional: pulling a live production credential into an agent's transcript context needs explicit per-action authorization.
When this happens, do not downgrade verification. A common failure mode is to skip connect, skip probe, skip safe-tool --invoke, and substitute curl /health plus cloud-CLI inspection of the running revision. That is not equivalent — it verifies the process is alive but not that the deployment serves tools end-to-end, and it leaves the actual operator workflow (connect, probe, manage users, register clients) un-exercised.
Use the user-executed pattern instead. Ask the user to run the connect command themselves so the secret never enters the agent's context. The exact mechanism depends on the agent platform — most platforms expose some way for the user to run a shell command in-session whose output stays out of the agent's transcript (for example, a !-prefix in some interactive agents). When that's available, ask the user to run something like:
my-solution-admin connect <url> --signing-key "$(<retrieval cmd>)"…where <retrieval cmd> is the appropriate gcloud secrets versions access ..., aws secretsmanager get-secret-value ..., deployment-tool secret-get command, or terraform output -raw ... for the operator's environment. After the user runs that, the per-app connect config persists the URL and key for this session and future ones; the agent can then proceed with probe and the rest of Step 3 normally without ever having seen the key.
If the agent's platform offers no in-session shell execution, ask the user to run the equivalent command in their own terminal and confirm when connect has succeeded. Then continue.
The point: a permission denial is a routing signal, not a signal to substitute a weaker verification path.
Check if the app's own admin CLI is available:
which my-solution-adminIf not found, install the package. Check pyproject.toml for [project.scripts] to find the entry point name:
pipx install git+https://github.com/owner/my-solution.git
# or from a local clone:
pipx install -e .Then connect:
# Per-app CLI (preferred) — local or remote
my-solution-admin connect local
my-solution-admin connect https://your-service --signing-key xxx
# Generic CLI (fallback) — remote only
mcp-app connect https://your-service --signing-key xxxconnect local is only available on the per-app CLI — the generic CLI doesn't know which app's store to locate.
The verification ladder has six rungs. Stop at the first one that reveals the issue; otherwise climb until the deployment is fully validated.
tools/list round-trip).
loaded and tool names match expectations.
tool needs a sanity check.
test (only if the solution declared one).
arguments when steps 2–5 leave a gap.
my-solution-admin probeOutput:
URL: https://your-service
Health: healthy
MCP: ok (probed as [email protected])
Tools (3):
do_thing
list_items
get_statusThis hits /health for liveness, then does an MCP tools/list round-trip using a short-lived token minted for an existing user. If it reports tools, the framework layer is operational — health, admin auth, user auth, MCP transport, and tool wiring all work.
If no users are registered yet, probe reports liveness only and tells you it can't do the MCP round-trip. Register a user first (Step 4), then probe again.
For structured output (agent consumption):
my-solution-admin probe --jsonprobe confirms the framework layer. tools list is a quick sanity check that the right tools are exposed:
my-solution-admin tools list
my-solution-admin tools show <name>
my-solution-admin tools list --json # for agent consumptiontools show <name> renders the description, arguments with type/required-ness, and a copy-pasteable tools call invocation example. Useful when you don't remember a tool's schema or want to debug a specific tool's contract before calling it.
Requires a registered user. Unlike probe, which can report liveness-only when the deployment has no users, tools list and tools show mint a per-user probe token to do the MCP tools/list round-trip. On a deployment with no registered users they exit with a clean error pointing at users add rather than degrading to a liveness check — because there's no useful "tools list" output to render without the round-trip. Run Step 4 (users add) first if you haven't.
Probe stops at tools/list. It does not exercise the solution's tool handlers, the upstream credential on the user's profile, or the response-shape contract a real client depends on. safe-tool --invoke does — when the solution declared a safe tool:
my-solution-admin safe-tool # show declaration only
my-solution-admin safe-tool --invoke # full end-to-end round-trip
my-solution-admin safe-tool --invoke --jsonThe CLI prints the JSON-RPC request body before sending so an operator can copy and replay it manually in a debugger or another terminal, with a different bearer token, etc. After the call it prints the HTTP status code and the full response body.
`safe-tool --invoke` requires a registered user for the same reason as tools list/show: the round-trip mints a per-user probe token. The bare safe-tool (no --invoke) only fetches the declaration metadata and does not need a user — handy for confirming the safe tool is declared on a fresh deployment before you've added any users.
#### Failure-mode table
probe | safe-tool --invoke | Likely problem |
|---|---|---|
| ❌ | n/a | Framework layer — see the probe troubleshooting table above. |
| ✅ | supported: false | Solution didn't declare a safe tool. Either accept this and rely on probe, or declare one. |
| ✅ | non-200 status | Solution-specific tool failed. Inspect result.body for the JSON-RPC error. |
| ✅ | 200 but empty / missing | Tool wired but upstream credential is invalid/expired/revoked. Use users update-profile to rotate the credential field. |
| ✅ | 200 with expected shape | Full stack verified. Done. |
Note: --json is the canonical agent-consumption path. The envelope is versioned (schema_version: "1") and additive-only — consumers must tolerate unknown fields.
#### Privacy note for agent operators
safe-tool --invoke returns a curated, low-PII response that is safe for an agent to read. `tools call` is not curated and may return user content. When agent-driven validation must not see user-authored data, prefer safe-tool --invoke and rely on the response envelope contract.
When safe-tool --invoke isn't declared, or you need to invoke a different tool with custom arguments to isolate an issue:
my-solution-admin tools call <name> --arg k=v --arg k2=v2
my-solution-admin tools call <name> --body '{"k": "v"}'
my-solution-admin tools call <name> --body @args.json
my-solution-admin tools call <name> --user [email protected] # mint as alice--arg is for scalars (booleans, numbers, strings, null) and type-coerces from the tool schema. Use --body for objects and arrays.
tools call may return PII or user-authored content. Operators running it interactively are implicitly accepting that.
# Liveness
curl https://your-service/health
# Admin auth
my-solution-admin users list
# User auth — tools/list with a user token
curl -X POST https://your-service/ \
-H "Authorization: Bearer USER_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}'# Data-owning app — no profile needed
my-solution-admin users add [email protected]
# API-proxy app — profile via typed flags (field names are solution-specific; see `users add --help`)
my-solution-admin users add [email protected] --<field> <value>users add rejects existing users. If the user already exists, use update-profile instead.
To discover what profile fields the app expects, check the --help output — field names and descriptions are generated from the app's Pydantic profile model:
my-solution-admin users add --helpmy-solution-admin users get-profile [email protected]
my-solution-admin users get-profile [email protected] --jsonWhen the solution has a registered profile model, the per-app CLI shows every declared field with its current value or (missing), and tags any extra stored keys as (not in profile model). This is how you confirm what's actually stored before rotating, or verify a credential rotation landed.
Field names are solution-specific — discover them from my-solution-admin users update-profile --help, which lists valid keys generated from the solution's profile model.
# Typed key (expand=True apps) — key is validated, tab-completable
my-solution-admin users update-profile [email protected] <field> <new-value>
# JSON merge (expand=False apps)
my-solution-admin users update-profile [email protected] '{"<field>": "<new-value>"}'Use this to rotate backend credentials without re-registering the user. Only the specified field is changed — other profile fields are preserved.
# List all users
my-solution-admin users list
# Revoke a user (invalidates all their tokens immediately)
my-solution-admin users revoke [email protected]
# Issue a new token for an existing user
my-solution-admin tokens create [email protected]clients cannot refresh tokens automatically.
users revokesets a cutoff timestamp, and all tokens issued before that moment are rejected.
tokens create toreactivate the user.
users add or tokens create is what theuser configures in their MCP client.
Use register to generate ready-to-paste commands:
# With a real token (mints one for the user)
my-solution-admin register --user [email protected]
# With a placeholder (operator substitutes later)
my-solution-admin registerThis outputs commands for Claude Code, Gemini CLI, and the Claude.ai URL form, with the URL and token already substituted.
For structured output:
my-solution-admin register --user [email protected] --jsonFilter by client or scope:
my-solution-admin register --user [email protected] --client claude --scope userstdio (local):
claude mcp add my-solution -- my-solution-mcp stdio --user local
gemini mcp add my-solution -- my-solution-mcp stdio --user localHTTP (remote):
claude mcp add --transport http my-solution \
https://your-service/ \
--header "Authorization: Bearer USER_TOKEN"Claude.ai / Claude mobile:
https://your-service/?token=USER_TOKENHowever the solution was deployed — bare gcloud, docker, systemd, a CI pipeline, anything — the admin flow is the same: connect the admin CLI at the URL, supply the signing key, and use the normal admin commands.
my-solution-admin connect https://my-service.example.com --signing-key xxx
my-solution-admin users add [email protected]
my-solution-admin probeThe generic mcp-app CLI works the same way when the per-app admin CLI isn't installed (see below). Both interfaces hit the same admin REST API exposed by the running service.
If the app's own admin CLI (my-solution-admin) isn't installed — e.g., managing a deployed instance from a machine without the app's repo — use the generic mcp-app CLI:
mcp-app connect https://your-service --signing-key xxx
mcp-app users add [email protected] --profile '{"<field>": "<value>"}'
mcp-app users get-profile [email protected]
mcp-app probe
mcp-app register my-solution --user [email protected]The generic CLI works but doesn't have typed profile flags, model-aware get-profile output, or connect local. Always prefer the per-app CLI when available.
service does not automatically connect the admin CLI to it. After deploying, explicitly run connect before admin operations.
tool concern. Admin endpoints and the CLI are part of the framework.
key — they don't pass through the deployed service.
tooling.** This skill can't prescribe a single command — investigate the deployment configuration to trace where the key lives.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.