resk-mcp · Documentation
Configuration
Environment variables, config.yaml keys and the precedence rules that decide which value the server actually uses.
On this page
Precedence and loading
Configuration is resolved once, when resk_mcp.config is imported:
Defaults live in
Settings.__init__(100/minute, port8001, 4000 tokens,HS256, 30-minute tokens, INFO logging).config.yamlis read fromRESK_MCP_CONFIG_FILEif set, otherwise from./config.yaml— relative to the current working directory, not to the package.Environment variables (including values loaded from
.envbypython-dotenv) override every YAML value.
Two failure modes are deliberate:
- A missing or unparsable YAML file does not crash the process —
load_config_data()returns an empty dict (an unparsable file logs a parse error). - A missing
JWT_SECRETdoes crash at import withValueError, because every secured request depends on it.
Note
resk_mcp.config calls load_dotenv() itself, but only for the current working directory. If your .env lives elsewhere, load it explicitly before importing the library (load_dotenv("/etc/myapp/.env")) or export the variables in the process environment — once Settings is built, later changes have no effect.
Environment variables
| Variable | Default | Effect |
|---|---|---|
JWT_SECRET | required | HMAC signing key for all tokens. Startup fails if absent. |
JWT_ALGORITHM | HS256 | Any algorithm supported by PyJWT except none, which is rejected at startup. |
JWT_EXPIRATION_MINUTES | 30 | Lifetime of tokens minted by create_jwt_token(). |
RATE_LIMIT | 100/minute | count/period limit applied to /mcp_secure. |
MAX_TOKEN_PER_REQUEST | 4000 | Approximate token budget for the params of one request. |
SERVER_HOST | 0.0.0.0 | Bind address used by run_server(). |
SERVER_PORT | 8001 | Bind port used by run_server(). |
SSL_KEYFILE | unset | TLS private key; enables HTTPS together with the certfile. |
SSL_CERTFILE | unset | TLS certificate chain. |
LOG_LEVEL | INFO | DEBUG … CRITICAL; applied to the root logger when the module loads. |
RESK_MCP_DOCS | false | Exposes FastAPI /docs, /redoc and /openapi.json. |
DASHBOARD_AUTH_ENABLED | false | Enables the dashboard. While false, /dashboard answers 404. |
DASHBOARD_USERNAME | admin | Dashboard login username. |
DASHBOARD_PASSWORD | empty | Dashboard password. Empty or admin is refused when auth is enabled. |
RESK_MCP_CONFIG_FILE | ./config.yaml | Alternative YAML path, read at import time. |
Values are coerced with int() where relevant: a non-numeric MAX_TOKEN_PER_REQUEST or SERVER_PORT raises at startup rather than silently falling back.
config.yaml reference
The repository ships a commented config.yaml. The keys actually read by Settings are:
# Environment variables win over every value below.
jwt:
secret: "" # overridden by JWT_SECRET
algorithm: "HS256" # overridden by JWT_ALGORITHM
expiration_minutes: 30 # overridden by JWT_EXPIRATION_MINUTES
rate_limit:
default: "100/minute" # overridden by RATE_LIMIT
context:
max_tokens: 4000 # overridden by MAX_TOKEN_PER_REQUEST
chars_per_token_approx: 4 # loaded into settings, see note below
server:
host: "0.0.0.0" # overridden by SERVER_HOST
port: 8001 # overridden by SERVER_PORT
docs: false # overridden by RESK_MCP_DOCS
# ssl_keyfile: "./key.pem" # overridden by SSL_KEYFILE
# ssl_certfile: "./cert.pem" # overridden by SSL_CERTFILE
logging:
level: "INFO" # overridden by LOG_LEVEL
dashboard:
auth:
enabled: false # overridden by DASHBOARD_AUTH_ENABLED
username: "admin" # overridden by DASHBOARD_USERNAME
password: "" # overridden by DASHBOARD_PASSWORD
session_expire_minutes: 60
Do not ship the sample secret
The example file in the repository contains secret: "test-secret-key-for-development". If you copy it, replace the value or — better — leave it empty and set JWT_SECRET in the environment so the secret never lands in version control.
Rate limits
RATE_LIMIT uses the slowapi count/period syntax: 5/second, 100/minute, 1000/hour, 20/day. The limit is attached to the /mcp_secure route itself.
The rate-limit key is derived in this order:
- the
user_idclaim of a validly signed bearer token, so one tenant cannot exhaust another's quota; - the client IP address when the token is missing or invalid.
Because invalid tokens fall back to IP, an unauthenticated flood is still throttled — but it is throttled per IP, not per identity. If you run behind a proxy, make sure the proxy sets the forwarded client address correctly, or every request will look like it comes from the proxy.
Exceeding the limit returns HTTP 429 from slowapi, with a JSON body describing the limit — this happens before the MCP envelope is built, so it is not an MCP error code.
Token budget
Each request's params are serialized and measured by TokenBasedContextManager. The estimate is len(json) // 4 over the JSON representation — a fast approximation, not a tokenizer. The chars_per_token_approx YAML key is parsed into settings but is not currently passed to the manager, which hardcodes 4 characters per token; keep that in mind if you change it.
Requests above MAX_TOKEN_PER_REQUEST are rejected with MCP error -33002 before dispatch. The comparison is inclusive: a request estimated at exactly the limit is accepted.
The same class can be used directly to size a budget elsewhere in your application:
from resk_mcp import TokenBasedContextManager
budget = TokenBasedContextManager(max_tokens=8000)
budget.is_within_limits({"query": "hello"}) # True
budget.get_remaining_tokens(current_params_tokens=1200) # 6800
JWT settings
JWT_ALGORITHMis validated at startup:noneraisesValueError("JWT_ALGORITHM=none is not allowed."). KeepHS256unless you have a concrete reason to change it — with an asymmetric algorithm you must also distribute the public key.JWT_EXPIRATION_MINUTESapplies to tokens minted by the library. Shorter lifetimes reduce the blast radius of a stolen token at the cost of more frequent rotation by your client.- There is no refresh-token endpoint: mint a new token with the same secret when the old one expires.
Server and TLS
run_server() resolves its arguments in this order: explicit call arguments, then settings (SERVER_HOST/SERVER_PORT/SSL_*), then uvicorn defaults. TLS is enabled only when both key and certificate paths are set. If you configure both but a file is missing, the server logs the missing path and returns without starting — a half-configured TLS setup never degrades silently to plaintext. If neither is set, it serves HTTP.
RESK_MCP_DOCS=true restores the FastAPI documentation routes. Those routes publish the complete MCP tool schema, including parameter names and descriptions, so enable them only on an internal interface.
Dashboard auth
The dashboard is disabled unless DASHBOARD_AUTH_ENABLED is true, and it refuses to start with a weak password:
DASHBOARD_AUTH_ENABLED="true"
DASHBOARD_USERNAME="ops"
DASHBOARD_PASSWORD="$(python -c 'import secrets; print(secrets.token_urlsafe(32))')"
session_expire_minutes has no environment override; set it in config.yaml (dashboard.auth.session_expire_minutes, default 60). The session cookie is HttpOnly and SameSite=Lax, and Secure only when SSL_CERTFILE is configured — if TLS ends at a proxy, the cookie will not carry the Secure flag. See the dashboard guide.
Keys that are not wired
The shipped config.yaml also contains a validation: block (pii_detection_level, prompt_injection_detection, prompt_injection_models). These keys are documentation of intent only: Settings does not read them, and the secured endpoint always runs both PII and injection checks with no switch. Do not expect pii_detection_level: "off" to disable anything.
Similarly, integrations.external_api is a placeholder — nothing in the library consumes it.
Reading settings in code
Every module shares one settings instance. Import it rather than re-reading environment variables:
from resk_mcp.config import settings
print(settings.server_port) # 8001
print(settings.rate_limit) # "100/minute"
print(settings.jwt_expiration_minutes)
print(repr(settings)) # secret is masked: jwt_secret='****'
Settings.__repr__ masks the signing key, so it is safe to log the object — but never log settings.jwt_secret directly.