resk-mcp · Documentation
Secure Model Context Protocol layer
resk-mcp (PyPI: mcp-resk) wraps Python MCP servers with authentication, request screening, rate limiting, token budgets and audit tracking — without replacing the official Model Context Protocol SDK.
On this page
What resk-mcp does
An MCP server is not an ordinary internal API. The caller is a language model that discovers tools at runtime and invokes them semi-autonomously, so the usual assumptions — a human on the other end, a stable capability set, structured payloads only — do not hold. resk-mcp adds the controls that assumption gap requires, on top of the official mcp Python SDK:
- It pins down who is calling. Every request to the secured endpoint must carry a valid JWT signed with your server's secret; the
user_idclaim becomes the identity used for rate limiting and logs. - It pins down how much. Per-caller rate limits (via
slowapi) and a per-request token budget (via a token estimator) bound the work a single client can trigger. - It screens what crosses the boundary. Parameters are validated against a Pydantic schema, then scanned for prompt-injection phrases and personally identifiable information before any tool runs.
- It records everything. Calls to tools, resources and prompts are counted per name and exposed through a web dashboard and a JSON API.
The library does not reimplement MCP. It subclasses FastMCP, keeps your existing tools, resources and prompts, and mounts a secured dispatcher on the FastAPI app at /mcp_secure.
How a request flows
client ── POST /mcp_secure ──▶ rate limit (slowapi)
│
▼
Bearer token present?
│
▼
verify_jwt_token() ── invalid ──▶ -33000 AUTH_ERROR
│
▼
payload shape (Pydantic)
│
▼
detect_prompt_injection(params) ── hit ──▶ -33003
detect_pii(params) ── hit ──▶ -33003
context_manager.is_within_limits ── over ──▶ -33002
│
▼
dispatch: tool/<name> · resource/<name> · prompt/<name>
│
▼
counter++ (dashboard) → JSON-RPC result
Detection order matters when you debug a rejection: the HTTP rate limiter runs first (it is a FastAPI dependency on the route), then authentication, then payload validation, then the injection/PII/context checks. Details are on the security model page.
Feature summary
| Capability | What it enforces | Details |
|---|---|---|
| JWT authentication | HS256 (configurable) token on every /mcp_secure request; user_id claim required | Authentication |
| Input validation | JSON-RPC envelope and params type checks with Pydantic | validation helpers |
| Prompt-injection screening | Keyword and heuristic scan of string values in params | Injection screening |
| PII screening | Pattern scan for emails, phone numbers, card numbers and similar identifiers | PII screening |
| Rate limiting | count/period per JWT user_id, falling back to client IP | Rate limiting |
| Token budgets | Estimated tokens per request (MAX_TOKEN_PER_REQUEST) | Token budget |
| Schema validation | Optional JSON Schema checks per tool, plus Pydantic-generated schemas | Schema validation |
| Audit dashboard | Counters per tool/resource/prompt, server info, uptime, optional login | Dashboard |
| TLS | Optional keyfile/certfile passed to uvicorn | TLS |
Quick start
Install the package, export one secret, register a tool and start the server:
-
Install and create
.env:pip install mcp-resk # .env (never commit this file) JWT_SECRET="$(python -c 'import secrets; print(secrets.token_urlsafe(48))')" -
Write
main.py:from resk_mcp import SecureMCPServer, create_jwt_token server = SecureMCPServer(name="my-secure-mcp") @server.tool(name="calculator/add") async def add(a: int, b: int) -> int: """Adds two numbers.""" return a + b if __name__ == "__main__": print("Bearer", create_jwt_token(user_id="[email protected]")) server.run_server(port=8001) -
Call the secured endpoint with the token printed at startup:
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}}' -
Enable the dashboard (
DASHBOARD_AUTH_ENABLED=trueplus a non-defaultDASHBOARD_PASSWORD) and openhttp://localhost:8001/dashboardto watch the interaction counters. It returns 404 while disabled — by design.
Note
create_jwt_token() reads JWT_SECRET from the environment (or config.yaml). If the secret is missing, resk_mcp.config raises at import time — that is intentional, the library refuses to start without a signing key.
HTTP surface
| Path | Method | Purpose |
|---|---|---|
/mcp_secure | POST | Authenticated dispatcher for MCP calls — method tool/<name> (resources and prompts are registered but their POST execution is not implemented yet) |
/dashboard | GET | Web dashboard — 404 unless DASHBOARD_AUTH_ENABLED=true; redirects to /login without a valid session |
/login | GET | Dashboard login page |
/api/dashboard/auth/login | POST | Exchange dashboard credentials for a session cookie |
/api/dashboard/auth/logout | POST | Invalidate the dashboard session |
/api/dashboard/interactions | GET | Counters per tool/resource/prompt (JSON) |
/api/dashboard/server-info | GET | Server name, registered capabilities, uptime, auth and rate-limit settings |
/ | GET | Redirects to /login |
The FastAPI-generated /docs, /redoc and /openapi.json routes are disabled by default because they publish the full tool schema. Set RESK_MCP_DOCS=true only in a trusted environment.
Scope and limitations
Being precise about the boundary is part of the design:
- In-process, not a gateway. resk-mcp is a Python library you embed in your server. It does not proxy third-party MCP servers, and it cannot protect a server you do not control.
- Detection is basic by design. PII and prompt-injection checks are pattern-based. They catch the common cases and are meant to be one layer, not the only one — pair them with model-side filtering (for example resk-llm on the client side) and normal application authorization.
- Requests are screened, not responses. Tool output can still carry injected instructions into the model context; filter it on the consumer side or with
MCPSecurityManager.check_response()(see the API reference). - No built-in token issuance service. You mint tokens with
create_jwt_token()or your own identity provider, using the same secret and algorithm.
Where to go next
Installation
Requirements, virtualenv, first server, first authenticated call, source install.
Configuration
Every environment variable and config.yaml key, with defaults and precedence.
Security model
What is enforced, in which order, and how to harden a deployment.
API reference
Public imports, decorators, helpers, wire format and error codes.