Troubleshooting
Common problems and how to diagnose them.
Startup errors
scoped-mcp fails fast at startup and exits with a clear message. The ops log
(stderr / --ops-log) shows the sequence of initialization steps and where
it stopped.
Missing AGENT_ID or AGENT_TYPE
error: AGENT_ID environment variable is not set
Both must be set before starting scoped-mcp. In Claude Code settings.json,
set them in the env block. On the command line:
AGENT_ID=research-01 AGENT_TYPE=research scoped-mcp --manifest manifests/research-agent.yml
Missing required credentials
error: Missing required environment variable(s): INFLUXDB_TOKEN, GRAFANA_URL
scoped-mcp lists every missing credential in one error and exits without starting partially. Set the missing env vars and restart. The credential reference table in the README lists required and optional vars for each module.
Manifest not found or invalid
error: [Errno 2] No such file or directory: 'manifests/research-agent.yml'
error: manifest validation error: modules: field required
Check the path passed to --manifest. Paths are relative to the working
directory at startup, not the manifest’s location. Use an absolute path to
avoid ambiguity.
Module not recognized
error: Unknown module 'redis' — not registered. Available: filesystem, sqlite, ...
Custom modules must be importable from the Python environment. See
examples/custom-module/ for the registration pattern.
Confirming successful startup
On clean startup, the ops log emits four events in sequence:
{"event": "startup", "logger": "ops", "manifest": "manifests/ops-agent.yml", ...}
{"event": "identity_resolved", "logger": "ops", "agent_id": "ops-01", "agent_type": "ops", ...}
{"event": "manifest_loaded", "logger": "ops", "modules": ["influxdb", "grafana", "ntfy"], ...}
{"event": "server_ready", "logger": "ops", "transport": "stdio", ...}
If server_ready does not appear, look at the last ops event for the failure point.
To write ops logs to a file for easier inspection:
scoped-mcp --manifest manifests/ops-agent.yml --ops-log /tmp/ops.jsonl
Scope violations
Reading a scope violation in the audit log
Every scope violation is logged as a scope_violation event with status: blocked:
{
"event": "scope_violation",
"logger": "audit",
"level": "warning",
"agent_id": "build-01",
"tool": "filesystem_read_file",
"args": {"path": "/data/agents/research-01/private.txt"},
"status": "blocked",
"error": "Path '/data/agents/research-01/private.txt' is outside the agent scope for 'build-01'. Expected prefix: /data/agents/build-01",
"elapsed_ms": 0.12,
"timestamp": "..."
}
The error field contains the full scope message. The args field shows
exactly what the agent passed. No backend operation occurs after a violation.
“I got a ScopeViolation but the path looks correct”
Check that base_path in the manifest resolves to the same directory the
agent is writing to. A common issue: base_path: /tmp/scoped-mcp-data resolves
differently if a symlink is involved. Prefer absolute paths without symlinks.
For traversal-style paths (../, symlinks in the path), scoped-mcp resolves
the full path before checking — the resolved path must fall under
{base_path}/agents/{agent_id}/.
Filtering violations in the audit log
# All scope violations for a specific agent
cat audit.jsonl | jq 'select(.event == "scope_violation" and .agent_id == "build-01")'
# All tool calls that were blocked
cat audit.jsonl | jq 'select(.status == "blocked")'
# All tool calls by tool name
cat audit.jsonl | jq 'select(.event == "tool_call" and .tool == "filesystem_write_file")'
Audit and ops log flags
By default, both log streams go to stderr as JSON-L. To write to files:
scoped-mcp \
--manifest manifests/ops-agent.yml \
--audit-log /var/log/scoped-mcp/ops-audit.jsonl \
--ops-log /var/log/scoped-mcp/ops.jsonl
Stderr output continues even when file paths are set — both destinations receive all events. The file output is useful for log shipping (Loki, Splunk, ELK) without losing terminal visibility.
Audit events have "logger": "audit". Ops events have "logger": "ops".
Filter by logger when both streams go to the same destination.
Tools not appearing in Claude Code
If scoped-mcp starts but tools don’t appear in the Claude Code tool list:
- Check that
server_readyappears in the ops log — if not, startup failed silently. - Verify the manifest module names match the built-in module registry (see README).
- Check that the
modefield is set correctly —mode: readgives only read tools; write-only modules likentfyneed nomodefield. - Restart Claude Code after changing
settings.json— MCP servers are started once per session.
Vault token renewal failing
If the ops log shows repeated vault_token_renewal_failed (escalating to critical
after 3 consecutive failures), the agent’s background token renewal is not succeeding.
Diagnose by transport and cause:
- Check credential health directly — call
scoped_mcp_statusand read thecredentialsblock, or (under--transport http)curl -s localhost:<port>/health.token_healthy: falsewith a risingconsecutive_failuresconfirms the renewal loop is stuck.seconds_to_expiry_estshows how long until hard expiry. 403 Forbiddenon renew-self — the AppRole token lacks theauth/token/renew-selfcapability. Vault’s built-indefaultpolicy grants it, so this only happens when the AppRole setstoken_no_default_policy = true. Re-add the grant (seeexamples/vault/vault-policy.hcl) or droptoken_no_default_policy.- Token dies ~24h after start even though renewal returns 200 —
renew-selfcan only extend a token up to itstoken_max_ttl; it can never exceed that ceiling. Settoken_periodon the AppRole (periodic tokens renew indefinitely) or enable scoped-mcp’s app-side self-heal withSCOPED_MCP_VAULT_REAUTH=1. Re-auth requires a reusable secret_id (secret_id_num_uses=0) — never enable it for a single-use secret_id, as a failed re-login would burn the only credential. - Confirm self-heal fired — with re-auth enabled, look for
vault_token_reauthenticatedin the ops log after a failure.vault_token_reauth_failedmeans the fresh login also failed (Vault unreachable or a bad secret_id) — the credential stays degraded and the configured Matrix alert fires.
Set SCOPED_MCP_ALERT_MATRIX_{HOMESERVER,TOKEN,ROOM} so a credential-degradation
transition is surfaced out-of-band (independent of Vault) rather than only landing in an
ops-log file nothing tails. An ops_alert_unconfigured warning at startup means no alert
channel is set.
Agent sees no tools / 401 on /mcp
Under --transport http, a fresh client that suddenly sees no tools at all is almost
always a bearer-auth failure, not a Vault or module problem: the request is rejected at
the bearer before any tool (including scoped_mcp_status) can be dispatched.
- Check for a 401 burst — repeated
401 Unauthorizedon/mcpand abearer_auth_401_burstwarning in the ops log confirm the client’s bearer token is wrong or unresolved. - Most common cause — the client
.mcp.jsonusesAuthorization: Bearer ${SCOPED_MCP_BEARER_TOKEN}but the launch environment (e.g. a headlessclaude -pinvocation) doesn’t exportSCOPED_MCP_BEARER_TOKEN, so the placeholder is sent literally and every request 401s. Ensure the variable is present in the client’s launch env, matching the value the server was started with. /health(unauthenticated) still returns200/503in this state — the process and its credentials are fine; only the client’s auth is broken.
Credential values appearing in logs
This should not happen — the sanitization processor redacts values whose keys match
known sensitive suffixes (_TOKEN, _PASSWORD, _SECRET, _KEY, etc.) and a
fixed set of common names (token, password, api_key, etc.).
If you observe a credential value in a log entry, open a security issue via the
private channels in SECURITY.md rather than a public GitHub issue.