MCP Security
Security hardening profile for Forge MCP (Streamable HTTP).
Baseline
Use MCP with explicit exposure and strict transport settings:
[mcp]
enabled = true
path = "/mcp"
session_ttl = "1h"
allowed_origins = ["https://your-app.example"]
require_protocol_version_header = true
Hardening Checklist
- Keep MCP disabled by default (
enabled = false) in environments that do not need it. - Restrict
allowed_originsfor browser-based clients. - Require
MCP-Protocol-Versionheader and reject unsupported versions. - Use short
session_ttlto limit stale session reuse. - Prefer private tools by default; use
publiconly when necessary. - Add
require_role("...")for sensitive tools. - Add
rate_limit(...)andtimeoutto every external-facing tool.
Authentication
Forge MCP tools reuse Forge auth middleware:
- JWT verification via HMAC or RSA/JWKS
- tools are private by default unless marked
public - issuer/audience validation
- role-based authorization via
require_role("...")
Recommended RSA/JWKS setup:
[auth]
jwt_algorithm = "RS256"
jwks_url = "https://your-provider.com/.well-known/jwks.json"
jwt_issuer = "https://your-provider.com"
jwt_audience = "your-app"
OAuth Security
When mcp.oauth = true, Forge adds OAuth 2.1 Authorization Code + PKCE:
- PKCE (S256 only): prevents authorization code interception. Plain method intentionally unsupported.
- Authorization codes: 60s TTL, single-use (atomic exchange prevents replay).
- CSRF protection: server-generated token validated via HttpOnly cookie on the authorize page.
- Redirect URI validation: exact string match against registered URIs (RFC 8252 localhost port exception).
- Token audience: OAuth-issued JWTs include
aud: "forge:mcp", scoping them to MCP endpoints only. - Client-bound refresh tokens: refresh tokens are bound to the OAuth client that created them.
- Rate limits: client registration (10/min per IP, 1000 client cap), login attempts (5 failures/min per IP).
- Clickjacking:
X-Frame-Options: DENYand CSPframe-ancestors 'none'on the authorize page. - Code leakage:
Referrer-Policy: no-referreron authorization redirects. - HTTPS required for OAuth. The
forge_sessioncookie set during the authorize flow is always emitted with theSecureattribute. Browsers refuse to sendSecurecookies over plain HTTP, so OAuth on a non-TLS gateway will fail at the cookie step. Terminate TLS at the gateway (or at a reverse proxy that forwards to it) and usehttps://redirect URIs in production. Local development onhttp://localhostis the only case browsers exempt — it works without TLS, but anything else does not.
Error Policy
- Protocol errors (
-32600,-32601,-32602,-32603) for malformed requests and server-side internal failures. - Rate limiting returns error code
-32029with a retry-after hint. - Tool validation/business failures should return MCP tool results with
isError: true.
This separation helps MCP clients and models recover from tool input errors while still treating protocol faults as transport-level failures.
Endpoints
MCP transport
| Method | Path | Notes |
|---|---|---|
POST | /_api/mcp (default) | JSON-RPC request/notification payload. Returns 200 with result or 202 for notification-only payloads. |
GET | /_api/mcp | Not used for stream transport in v1 — returns 405. |
The path is controlled by mcp.path in forge.toml (default: /mcp, mounted under /_api).
OAuth 2.1 (requires mcp.oauth = true)
All OAuth endpoints are mounted under /_api and bypass the Forge auth middleware.
| Method | Path | Notes |
|---|---|---|
GET | /.well-known/oauth-authorization-server | Authorization server metadata (RFC 8414). Advertises all OAuth endpoints. |
GET | /.well-known/oauth-protected-resource | Protected resource metadata (RFC 9728). Points back to this server as the authorization server. |
POST | /_api/oauth/register | Dynamic client registration (RFC 7591). Rate-limited to 10 req/min per IP; 1 000 client cap. |
GET | /_api/oauth/authorize | Authorization endpoint. Renders consent page; reads forge_session cookie for single-click re-auth. |
POST | /_api/oauth/authorize | Authorization form submit. Validates CSRF token, credentials, PKCE challenge; issues authorization code (60 s TTL, single-use); redirects to redirect_uri. |
POST | /_api/oauth/token | Token endpoint. Exchanges authorization code + PKCE verifier for access + refresh tokens, or refreshes using a refresh token. Response includes aud: "forge:mcp". |
When OAuth is disabled, /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource return 404 with {"error":"oauth_not_supported"} so MCP clients can detect the configuration and fall back to unauthenticated or bring-your-own JWT flows.
Transport Notes
Forge MCP v1 is Streamable HTTP only.
- POST handles MCP JSON-RPC request/notification payloads.
- GET is not used for stream transport in v1 and returns
405. - Notification/response payloads accepted by the endpoint return
202.