MCPSafe.io
RegistryThreatsMethodologyDocsPricingScanSign in
MCPSafe.io

Security checks for MCP servers — public packages and private repos, fast or deep.

Legal

Privacy PolicyCookie PolicyTerms of ServiceSecurity disclosure

Resources

State of MCP SecuritySupportSystem statusMade in Germany 🇩🇪

© 2026 MCPSafe. All rights reserved.

GDPR — Privacy Policy
← Threat Catalog

Configuration & Environment

Protected Resource Metadata Endpoint Missing

HIGHCWE: CWE-1390Rule: MCP-277

MCP servers that verify OAuth tokens but do not expose `/.well-known/oauth-protected-resource` violate the MCP Authorization Specification and prevent clients from discovering which authorization servers issued the tokens they should accept.

What it is

This is a weak authentication-discovery defect (CWE-1390) where the server enforces JWT or OAuth bearer authentication on tool calls but never publishes a Protected Resource Metadata (PRM) document at `/.well-known/oauth-protected-resource`. RFC 9728 and the MCP Authorization Specification (2025-11-25) make PRM mandatory: clients use it to learn the resource identifier and the list of authorization servers whose tokens this resource accepts. Without it, clients cannot verify the audience claim of incoming tokens against the resource they intended to call, opening token-confusion and audience-confusion attacks.

Why it matters for MCP

MCP servers are routinely accessed by multiple authorization servers in agent ecosystems — a single tool surface might be reachable through Claude.ai, a partner orchestrator, and a self-hosted IDE. The MCP spec makes PRM the single source of truth for which authorization servers a server trusts; without it, clients have no way to verify they are not being routed to a token-issuing impostor. Because MCP servers expose tools that perform high-impact actions on user data, audience-confusion attacks are particularly damaging.

Vulnerable example

example.py
1
import jwt
2
from fastmcp import FastMCP
3
4
mcp = FastMCP("my-mcp")
5
6
@mcp.tool()
7
def list_files() -> list[str]:
8
    return ["a.txt", "b.txt"]
9
10
# VULNERABLE: JWT enforced, but no PRM endpoint published.
11
# Clients cannot discover which auth servers this resource trusts.
12
def auth(token: str):
13
    return jwt.decode(token, "secret", algorithms=["HS256"])

Secure example

example.py
1
from fastapi import FastAPI
2
import jwt
3
from fastmcp import FastMCP
4
5
mcp = FastMCP("my-mcp")
6
app = FastAPI()
7
8
@app.get("/.well-known/oauth-protected-resource")
9
def prm():
10
    return {
11
        "resource": "https://my-mcp.example.com",
12
        "authorization_servers": ["https://auth.example.com"],
13
        "bearer_methods_supported": ["header"],
14
    }
15
16
@mcp.tool()
17
def list_files() -> list[str]:
18
    return ["a.txt", "b.txt"]
19
20
def auth(token: str):
21
    return jwt.decode(token, "secret", algorithms=["HS256"])

How MCPSafe detects this

MCPSafe fires when ALL three conditions hold: (1) the file registers MCP tools; (2) the file configures OAuth or JWT verification (`jwt.decode`, `jwt.verify`, `OAuth2*Bearer`, `authorization_endpoint`, `jsonwebtoken`); (3) the file does NOT contain the literal string `.well-known/oauth-protected-resource` or `.well-known/oauth-authorization-server`. The v1 detection is single-file: if the PRM route is served from a sibling module, an API gateway rule, or a reverse proxy, the rule will still fire — use a `# nosem` annotation in that case. The fix is to add the metadata endpoint to the MCP server module itself so the discovery contract is reviewable in source.

See the full threat catalog for every documented detection.

Further reading

  • MCP Authorization Specification (2025-11-25)
  • RFC 9728 — OAuth 2.0 Protected Resource Metadata
  • CWE-1390: Weak Authentication

Scan an MCP server for this issue

MCPSafe runs this check — and every other rule in the catalog — on any MCP server you paste in.

Scan now