resk-mcp · Documentation

Configuration

Environment variables, config.yaml keys and the precedence rules that decide which value the server actually uses.

  • Version 0.1.4
  • Env overrides YAML
  • Config loaded at import

Precedence and loading

Configuration is resolved once, when resk_mcp.config is imported:

  1. Defaults live in Settings.__init__ (100/minute, port 8001, 4000 tokens, HS256, 30-minute tokens, INFO logging).

  2. config.yaml is read from RESK_MCP_CONFIG_FILE if set, otherwise from ./config.yaml — relative to the current working directory, not to the package.

  3. Environment variables (including values loaded from .env by python-dotenv) override every YAML value.

Two failure modes are deliberate:

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

VariableDefaultEffect
JWT_SECRETrequiredHMAC signing key for all tokens. Startup fails if absent.
JWT_ALGORITHMHS256Any algorithm supported by PyJWT except none, which is rejected at startup.
JWT_EXPIRATION_MINUTES30Lifetime of tokens minted by create_jwt_token().
RATE_LIMIT100/minutecount/period limit applied to /mcp_secure.
MAX_TOKEN_PER_REQUEST4000Approximate token budget for the params of one request.
SERVER_HOST0.0.0.0Bind address used by run_server().
SERVER_PORT8001Bind port used by run_server().
SSL_KEYFILEunsetTLS private key; enables HTTPS together with the certfile.
SSL_CERTFILEunsetTLS certificate chain.
LOG_LEVELINFODEBUG … CRITICAL; applied to the root logger when the module loads.
RESK_MCP_DOCSfalseExposes FastAPI /docs, /redoc and /openapi.json.
DASHBOARD_AUTH_ENABLEDfalseEnables the dashboard. While false, /dashboard answers 404.
DASHBOARD_USERNAMEadminDashboard login username.
DASHBOARD_PASSWORDemptyDashboard password. Empty or admin is refused when auth is enabled.
RESK_MCP_CONFIG_FILE./config.yamlAlternative 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:

  1. the user_id claim of a validly signed bearer token, so one tenant cannot exhaust another's quota;
  2. 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

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.