resk-mcp · Documentation
Installation
From an empty directory to a protected MCP endpoint answering authenticated calls. About five minutes.
On this page
Requirements
- Python 3.11 or newer. The repository requires
>=3.11; Python 3.10 reached end of life and is no longer supported. The 0.1.0 metadata on PyPI still advertises 3.10 — trust the project'spyproject.tomland use 3.11+. - pip (bundled with Python) or uv (recommended by the project because dependency resolution is faster).
- A POSIX shell. On Windows, run inside WSL — the library itself is pure Python, but the examples and commands assume a Unix shell.
- No database, no broker, no external service. State (counters, dashboard sessions) is held in process memory.
Install the package
mkdir my-secure-mcp && cd my-secure-mcp
python3 -m venv .venv
source .venv/bin/activate
pip install mcp-resk
With uv, the same thing:
uv venv
source .venv/bin/activate
uv pip install mcp-resk
Verify that the import name resolves (the distribution is mcp-resk, the module is resk_mcp):
python -c "import resk_mcp; print(resk_mcp.__version__)"
# 0.1.0
Create the JWT secret
The library refuses to load without a signing key: resk_mcp.config raises ValueError("JWT_SECRET is required...") at import time when no secret is configured. Generate a high-entropy value and keep it out of version control:
python -c 'import secrets; print(secrets.token_urlsafe(48))'
Write it to .env next to your server entry point:
# .env
JWT_SECRET="paste-the-generated-value-here"
RATE_LIMIT="100/minute"
MAX_TOKEN_PER_REQUEST="4000"
LOG_LEVEL="INFO"
python-dotenv is loaded by resk_mcp.config, so the .env file is picked up automatically from the working directory. Add .env to .gitignore before your first commit.
Security
A leaked JWT_SECRET lets an attacker mint tokens for any user_id. Rotate it immediately if it is exposed — every previously issued token becomes invalid, which is the desired behaviour.
Write the server
Create main.py. Tools, resources and prompts are registered with the usual decorators; resk-mcp only adds tracking around them.
from dotenv import load_dotenv
load_dotenv() # before importing resk_mcp, so JWT_SECRET is set
from resk_mcp import SecureMCPServer, create_jwt_token
server = SecureMCPServer(name="my-secure-mcp", title="My Secure MCP")
@server.tool(name="calculator/add")
async def add(a: int, b: int) -> int:
"""Adds two numbers."""
return a + b
@server.resource(uri="info/version")
async def version() -> dict:
"""Returns the application version."""
return {"version": "1.0"}
if __name__ == "__main__":
token = create_jwt_token(user_id="[email protected]")
print(f"Test token: Bearer {token}")
server.run_server() # host/port from settings, default 0.0.0.0:8001
Start it:
python main.py
You should see uvicorn bind on 0.0.0.0:8001 and the tool registration log line. run_server() takes optional overrides (host, port, ssl_keyfile, ssl_certfile) and forwards any remaining keyword arguments to uvicorn.run().
Make a first authenticated call
Export the token printed at startup, then send a JSON-RPC body. Note the method format: tool/<registered-name>, with the tool arguments directly in params.
export TOKEN="paste-the-token"
curl -s 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}}'
Successful response (the id is echoed, the payload is under result):
{"id": 1, "result": 5}
Now remove the header and repeat the call. The server answers with an authentication error rather than executing the tool:
{"id": 1, "error": {"code": -33000, "message": "Invalid or expired token"}}
Note
The secured endpoint expects {id, method, params}, not the full {"jsonrpc":"2.0", ...} envelope: RawMCPRequest declares exactly three fields and rejects methods that do not start with tool/, resource/ or prompt/. Extra fields are ignored, so a standard JSON-RPC body with a correct method still works as long as it also carries id and params.
Enable the dashboard
The dashboard fails closed: /dashboard returns 404 while authentication is disabled. Enable it with a real password:
# .env
DASHBOARD_AUTH_ENABLED="true"
DASHBOARD_USERNAME="admin"
DASHBOARD_PASSWORD="a-long-random-value" # "admin" is rejected at startup
Restart the server and open http://localhost:8001/dashboard. See the dashboard guide for the endpoints and production caveats.
TLS
For local testing, a self-signed certificate is enough:
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem \
-sha256 -days 365 -nodes -subj "/CN=localhost"
Then point the server at it, either through the environment or the call:
# .env
SSL_KEYFILE="./key.pem"
SSL_CERTFILE="./cert.pem"
server.run_server(port=8443)
When both paths are set and the files exist, uvicorn serves HTTPS. In production, prefer terminating TLS at a reverse proxy (nginx, Caddy, Traefik) and keep the Python process on a private interface.
Install from source
Use an editable install when you want to modify the library or run its test suite:
git clone https://github.com/Resk-Security/resk-mcp.git
cd resk-mcp
uv venv
source .venv/bin/activate
uv sync # installs the project plus its dev dependency group
uv run pytest
With plain pip, use pip install -e ., then pip install pytest pytest-asyncio pytest-mock flake8 mypy for the checks. Test layout and per-module commands are described in README.dev.md.
The repository ships a config.yaml with every option commented, plus examples/example_server.py — a fuller walkthrough that combines SecureMCPServer, ToolSchemaValidator and MCPSecurityManager.
If you use a custom configuration file, point the library at it before importing:
export RESK_MCP_CONFIG_FILE="/etc/resk-mcp/config.yaml"
Deployment notes
- One process, one worker. Rate-limit counters, interaction counters and dashboard sessions live in process memory. With multiple uvicorn workers, each worker keeps its own counters and rate limits are enforced per worker — use a single worker or move the limits to your gateway.
- Bind privately. Set
SERVER_HOST="127.0.0.1"and let the reverse proxy expose/mcp_secureover TLS. - Keep FastAPI docs off.
RESK_MCP_DOCSdefaults tofalse;/docs,/redocand/openapi.jsonare removed from the app unless you opt in. - Supervise the process with systemd, Docker or your platform runner. A minimal container example (not shipped by the project):
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY main.py config.yaml ./
RUN useradd -r -u 10001 app
USER app
EXPOSE 8001
CMD ["python", "main.py"]
Upgrade and uninstall
pip install --upgrade mcp-resk
pip uninstall mcp-resk
Before upgrading across minor versions, re-read the configuration reference: defaults are stable, but new security checks can reject payloads that previously passed.