Skip to main content

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

  1. Keep MCP disabled by default (enabled = false) in environments that do not need it.
  2. Restrict allowed_origins for browser-based clients.
  3. Require MCP-Protocol-Version header and reject unsupported versions.
  4. Use short session_ttl to limit stale session reuse.
  5. Prefer private tools by default; use public only when necessary.
  6. Add require_role("...") for sensitive tools.
  7. Add rate_limit(...) and timeout to 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: DENY and CSP frame-ancestors 'none' on the authorize page.
  • Code leakage: Referrer-Policy: no-referrer on authorization redirects.
  • HTTPS required for OAuth. The forge_session cookie set during the authorize flow is always emitted with the Secure attribute. Browsers refuse to send Secure cookies 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 use https:// redirect URIs in production. Local development on http://localhost is 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 -32029 with 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

MethodPathNotes
POST/_api/mcp (default)JSON-RPC request/notification payload. Returns 200 with result or 202 for notification-only payloads.
GET/_api/mcpNot 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.

MethodPathNotes
GET/.well-known/oauth-authorization-serverAuthorization server metadata (RFC 8414). Advertises all OAuth endpoints.
GET/.well-known/oauth-protected-resourceProtected resource metadata (RFC 9728). Points back to this server as the authorization server.
POST/_api/oauth/registerDynamic client registration (RFC 7591). Rate-limited to 10 req/min per IP; 1 000 client cap.
GET/_api/oauth/authorizeAuthorization endpoint. Renders consent page; reads forge_session cookie for single-click re-auth.
POST/_api/oauth/authorizeAuthorization form submit. Validates CSRF token, credentials, PKCE challenge; issues authorization code (60 s TTL, single-use); redirects to redirect_uri.
POST/_api/oauth/tokenToken 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.