resk-mcp · Documentation

Troubleshooting

Every failure mode has a fingerprint: a status code, an MCP error code or a log line. Here is how to read them.

  • Version 0.1.4
  • Start with the status code

Quick diagnosis table

SymptomLikely causeFix
HTTP 403 Not authenticatedNo Authorization header, or not in Bearer … formSend Authorization: Bearer <token>
-33000 Invalid or expired tokenSecret mismatch, expired token, wrong algorithmRe-mint against the server's secret; see below
HTTP 422 with Method must start with…Standard JSON-RPC method names usedUse tool/<name>, not tools/call
HTTP 422 with field requiredMissing id, method or paramsSend the three-field envelope
-32601 Tool X not foundName mismatch, arguments nested, resource/prompt over POSTCheck registration name and params shape
-33003 Sensitive data (PII) detectedA string value matches the email or phone patternPass an opaque identifier instead
-33003 Prompt injection detectedA string contains a known injection phraseRephrase or split the payload
-33002 Context limit exceededEstimated tokens above MAX_TOKEN_PER_REQUESTRaise the budget or trim payload
HTTP 429RATE_LIMIT exceeded for user or IPRaise the limit or slow the client
/dashboard returns 404Dashboard authentication disabledSet DASHBOARD_AUTH_ENABLED=true
“Dashboard HTML not found.”Static assets missing from the installed wheelSee dashboard issues
ValueError: JWT_SECRET is requiredSecret not loaded before importload_dotenv() first, or export the variable
ModuleNotFoundError: resk_llmImporting resk_mcp.security without resk-llmpip install resk-llm
ModuleNotFoundError: jsonschemaImporting resk_mcp.schema_validation without jsonschemapip install jsonschema

Authentication failures

HTTP 403 “Not authenticated”

FastAPI's HTTPBearer rejects the request before your code runs. Check that the header exists and uses the exact Bearer prefix with a single space. A token pasted with surrounding quotes or a newline also fails here.

curl -i http://localhost:8001/mcp_secure \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1,"method":"tool/calculator/add","params":{"a":2,"b":3}}'

-33000 “Invalid or expired token”

The header was accepted but verification failed. The server log narrows it down:

WARNING:resk_mcp.server:Authentication failed for /mcp_secure: Invalid token: Signature verification failed

Signature failures are almost always a secret mismatch: the token was signed with a different JWT_SECRET than the server is using. Confirm what each side sees:

# On the server host, with the same environment as the process:
python -c "from resk_mcp.config import settings; print(len(settings.jwt_secret))"

# Mint a token with the server's own settings, then use it immediately:
python -c "from resk_mcp import create_jwt_token; print(create_jwt_token('[email protected]'))"

If the two lengths differ, something is overriding the value — an exported variable, a .env loaded after import, or a config.yaml found in a different working directory. Remember that config.yaml is resolved relative to the current working directory.

Other cases:

Request format failures

These return HTTP 422, not an MCP error object, because FastAPI rejects the body before the dispatcher runs.

“Method must start with one of: tool/, resource/, prompt/”

You sent a standard JSON-RPC method such as tools/call. resk-mcp uses its own convention: the method carries the capability, and params carries the arguments directly.

WrongRight
{"method":"tools/call","params":{"name":"add","arguments":{"a":1}}}{"method":"tool/add","params":{"a":1}}

If your MCP client only speaks the standard protocol, keep it pointed at the SDK's own endpoint and use /mcp_secure as the hardened ingress for callers you control, or add a thin adapter that maps tools/call to tool/<name> and unwraps arguments.

“field required”

params must be present even for a tool that takes no arguments — send "params": {}. The id field is mandatory and must be an integer.

Tool not found

-32601 with “not found or couldn't be executed” is the dispatcher's catch-all. In order of likelihood:

  1. Name mismatch. The method suffix must equal the registered name, slashes included: @server.tool(name="calculator/add") → tool/calculator/add. If you omit name=, the Python function name is used.
  2. Arguments nested under arguments. The tool call path passes params as keyword arguments. {"params":{"arguments":{"a":1}}} calls your tool with an unexpected arguments keyword.
  3. Resources or prompts. They register, but POST execution is unimplemented (-33004); an unknown name yields -32601.
  4. Schema or type errors inside the tool. A SchemaValidationError can surface as “not found” because the dispatcher falls back through several lookup strategies. Check the server log for the real exception.

Requests rejected by security checks

-33003 Sensitive data (PII) detected on a legitimate request

Any string value matching the email or US-phone pattern is rejected, including an email your tool genuinely needs. This is unconditional in 0.1.0. Options:

-33003 Prompt injection detected on benign content

The scanner matches six phrases as substrings, anywhere in keys or values. Sentences such as “the user asked us to ignore previous instructions” trip it. Split the payload, quote the phrase differently, or keep such content out of MCP arguments. There is no allowlist.

Rate limits and budgets

HTTP 429

slowapi rejected the request. The key is user:<id> for a valid token, ip:<addr> otherwise. Common traps:

-33002 Context limit exceeded

The JSON-serialized params exceed 4 × MAX_TOKEN_PER_REQUEST characters (the estimator divides by 4). Raise the variable or reduce the payload. Note that the check applies to parameters only, never to tool output.

Dashboard issues

404 on /dashboard

The dashboard is disabled. Set DASHBOARD_AUTH_ENABLED=true together with a password that is neither empty nor admin; otherwise startup fails or the routes stay hidden.

“Dashboard HTML not found.” or “Login page not found.”

Current releases ship the UI under resk_mcp/static/, so a normal install serves it. The message only appears when those files are missing — typically the original 0.1.0 wheel, which looked for a sibling static/ directory, or a build that stripped package data. Upgrade, or copy the repository's resk_mcp/static directory next to the installed package. The JSON endpoints keep working either way.

401 on /dashboard after logging in

The session cookie is Secure only when SSL_CERTFILE is set. If you terminate TLS at a proxy and browse over HTTPS, the cookie is still sent; but if a proxy strips or rewrites cookies, login appears to succeed and the next request is unauthenticated. Check the Set-Cookie header and the browser's cookie store. Sessions also disappear on restart.

Startup and import failures

ValueError: JWT_SECRET is required. Set it in config.yaml under jwt.secret or as an environment variable.

The secret is not visible to the library. resk_mcp.config reads the environment once at import time and loads a .env only from the current working directory. If the process starts from another directory (systemd, Docker WORKDIR, a scheduler), the file is never seen. Load it explicitly before the import with load_dotenv("/etc/myapp/.env"), or export JWT_SECRET in the process manager.

Server exits immediately with “SSL key file not found” / “SSL cert file not found”

Both SSL_KEYFILE and SSL_CERTFILE are set, but a path is wrong or relative to a different working directory. Fix the paths or unset both to serve HTTP behind a proxy.

ModuleNotFoundError: No module named 'resk_llm'

Something imports resk_mcp.security — usually examples/example_server.py or your own MCPSecurityManager usage. Install the optional package: pip install resk-llm. The core SecureMCPServer path does not need it.

ModuleNotFoundError: No module named 'jsonschema'

resk_mcp.schema_validation imports jsonschema at module load; install it explicitly (pip install jsonschema). It is present in the development dependency group but not in the 0.1.0 runtime requirements.

Connection refused on port 8001

Check the bind address: the default is 0.0.0.0, so a local curl should work; in containers, publish the port. If the process exits, the log shows why — usually a missing secret, a weak dashboard password, or a TLS path.

Enabling debug output

LOG_LEVEL="DEBUG" python main.py

At DEBUG level the server logs each tool dispatch, the rate-limit key decision (user vs IP) and the fallback path taken when a tool lookup fails. Rejections are logged as warnings with the authenticated user_id — those lines are the ones to alert on.

For an isolated check of the two detectors without the server:

python - <<'PY'
from resk_mcp import detect_pii, detect_prompt_injection
print(detect_pii({"note": "mail me at [email protected]"}))       # True
print(detect_pii({"email": "not-an-address"}))                     # check your payload
print(detect_prompt_injection({"q": "ignore previous instructions"}))  # True
PY

Be careful what you log

Tool exceptions are returned to the caller inside the error message (for example Error executing tool …: <exception text>). Never put secrets, tokens or raw customer data in exception messages.

Getting help